BIND 10 trac2862, updated. 0d8bee5c0cc3cf65a0a14890d7ff76de328e4316 [2862] Pass the update command to the list manager
BIND 10 source code commits
bind10-changes at lists.isc.org
Fri Jul 12 13:15:26 UTC 2013
The branch, trac2862 has been updated
discards da78d2eb08150ae1558904cce44cc1d12f440edd (commit)
discards e9589fb4bb49847625c0005a17d9fc897593d8b7 (commit)
discards 114fc9c5ee887ea847011070568fc408c2b8e265 (commit)
discards 65805a60410390ead4f52dc89d259999a65f0af0 (commit)
discards acc09a174a347e837a0677fbb92e5f1ce496d5a1 (commit)
discards 0196cde54a656135e1ddfb589109ebcdeff1e75c (commit)
discards c403a965a2250eec7a500eb9c9d98f7df391e9c3 (commit)
discards f15ddd7b42b7a7b1079438c135bf946a8850f8da (commit)
discards 509c49f787fe648c70e0bc8eb0b2ab159bd91fd4 (commit)
discards f77c763e59c2f49e3f8b749d5eda1dad5a4c272c (commit)
discards 6321090617d7c3eff5a76d4b701e6d4a969ecfa5 (commit)
discards 49dd2cda64271b7fc0407159120b1ffb09f698b7 (commit)
discards 3a13cb67361874642c75748e19ae2d008c1d032d (commit)
discards 249acbdc02708dd9c6ea8912a3b29e88aae09160 (commit)
discards 16a1ed85d4bf1135008baad41952adcf2195b9a3 (commit)
via 0d8bee5c0cc3cf65a0a14890d7ff76de328e4316 (commit)
via 2aa3e8e3ad750691dc97badcac032ef1ca92737f (commit)
via 661dae35e38a4b4a7d3f22b785509ceb528003e1 (commit)
via 7276a2eddfc2fefb354a4848aa4376b9dbeb675e (commit)
via 3194a2019cc116c9c668cf4adbf879e06b927879 (commit)
via 7cd60567cc43fe83a74a7799373e5e6410d435f3 (commit)
via d6c9e292ff83ad12b82b8aa73e59ae05d23ee65a (commit)
via 617aa41e35a33420f00dae8bb9f39918208dc1a9 (commit)
via cb814de47219dbc310f277838106d5b4d7e17668 (commit)
via 228807659e177e95f4a3dbedc6978dcb8a02a2c3 (commit)
via ef523ea6235d16e0676d0263c964570134a59750 (commit)
via eb01e3dce1476254ec6720798c861e709d32c9be (commit)
via 87c3781cf964f63b044153a39b05518f72d8b80e (commit)
via 02e0859b64a98789b422bc57c457825bd991749f (commit)
via d26eac8dfc0780bd6211ff90f9d922bbe2bf7edb (commit)
via 465c3330313daf9f80471056dc41eb53eaaa3531 (commit)
via 01f159a6b4277ccbbb76b18e1de811a0a71b80cd (commit)
via c2dfa3796558372cb69c9bdad6963835c18b3937 (commit)
via 54c8dec80aa3198a5f6dd7123c1ca415a85a27f8 (commit)
via 009e306698d3c8018fc2a83aa2c9cb6bd787a875 (commit)
via dd01c785f0d8eb616ee4179299dc53e0d2c0015c (commit)
via 82c997a72890a12af135ace5b9ee100e41c5534e (commit)
via 4ae6b8af324d8e7bcd13f3e6ce5a632f3d704ccf (commit)
via 5a67a8982baa1fd6b796c063eeb13850c633702c (commit)
via 67a39b06a802b161dd6c4c93892bf3309d30d892 (commit)
via 3430cf9ae922d5af87fef9ca4c202db482f29391 (commit)
via e746e58a9fec019dd8a944baeed75c685a6cdf28 (commit)
via 196c9f80b51bb0c4d9e9f6540fc0c5258b69cd06 (commit)
via 7f4bf2e2f9b9eaf3410bf22f918478cf4a5f0ec2 (commit)
via e45f325ec7438e0ecd09d37a7d938348232b994d (commit)
via ec4df20fd7827f622fd9aecf21faae072337b32d (commit)
via 4ef6830ed357ceb859ebb3e5e821a064bd8797bb (commit)
via 016c6b97353dd57158d1da0f574912f2823e74e1 (commit)
via d406f5702264d689e6c1b7dd6c285b7d5ab06db0 (commit)
via bfcf73f7b86036c774b90e1df948e12399269385 (commit)
via a8a6595db650be70a46d4cd78f9cf8202b2c74a1 (commit)
via 5b7b932c171d48009f722ea20ecdbe852ad2b8aa (commit)
via bc25129780f807429a8c9f4cec569ed35f6b066c (commit)
via 48aa711b39c5c57d569d6738257ed9e5b4d34995 (commit)
via dbabac08932ffda4307f7176be732b8aea655e41 (commit)
via 4bebf8b48d3e6b9b9b1b50618671e772b7e7ebab (commit)
via e1b4215d69747ee068893e167e96428dfdbab1ce (commit)
via 30e7a2c7499274f1feb8a02fe37ab35144c20689 (commit)
via b9c531514beef9d0ee0d706f2850a30ba20c5cd1 (commit)
via 18f8355aae2aabb011a57176ec7381b656e52260 (commit)
via 4d6b150d0605cec1086f05c7557c7990e4524188 (commit)
via 4c9ef2963127e8d887a2ca38435e61a28f1c7c63 (commit)
via 43ea555f6ae497ec40672b951d8a00c437b89c0a (commit)
via 04e36988a16fb36ab84ec548a174b148924ed0d7 (commit)
via 7076a02b24e63b0fa542af760585e6ad95cc421f (commit)
via 73656261a319aad29c3b9c405e8a7a6d1b9445f6 (commit)
via 64b9c9084608a24a11c4acd75bb2bdd1b231cdcc (commit)
via d0d6ad3146dcecd50b276f26ce4ebaa3e6fa84a2 (commit)
via 43e5ea02d51f60045318fde277b77d3603f5194f (commit)
via c0153581c3533ef045a92e68e0464aab00947cbb (commit)
via 962c4c299ee7fe733267e7ffdb1cf3f6ff3ec394 (commit)
via 0583652b5a391ec078361254b6d473fa8ffdb4a2 (commit)
via 79154442df1acbc7b00e57091b94a97086134a71 (commit)
via 81ddf65784bb1089f4c6761f018ed1ada5a45ad2 (commit)
via 675e72e818b0a0e61ee8e4c62611ce41e1b5fd98 (commit)
via 6cc73bde44fc16387788cf53906d1c9ecb4fd947 (commit)
via 2cf3214c48f9825347dad3b3bca6e6e6d8b8b6e2 (commit)
via b61e894db16aa458c9d3d405210c357e1fdde657 (commit)
via dc342c8d7ff4eff388bc596e625e54ac9617c09c (commit)
via 326b3dc5b7383a9357dfe2f9d75e1e05b780153e (commit)
via ca87835d1ec6e3393a98dcad72b6c96d43331e39 (commit)
via ca3f4ecf2c5dc37dab7d9e89f4bc41ee000f12c1 (commit)
via fcea4de8a92427f8b0910048ecb3c7c420d6daf9 (commit)
via 8288bf44eb68ee5f7f098beeff11e9372065b757 (commit)
via 6d1f4e151097981b21acb32175756afadf432fe6 (commit)
via d3c2b9a2fde279369ebac19e23153963fa855d1b (commit)
via 313bac393cc4a097be0bdeaac4e21bbef1ba0aa2 (commit)
via 4d0494f1618492afa57a70d036d63a6ac67ee894 (commit)
via 3945e7047b3e94919601d9d4bfca36e54310c43c (commit)
via f623694374d70db2e5cae9f78b44fc74d4580f9e (commit)
via cf1828a29fe1afd7382ebf41417b98c027ac5bfd (commit)
via 4ab333e8a81f1d83d03a1f78100823293ff3415b (commit)
via e6bd8c1c59c87dbbed3160350e063031839d6425 (commit)
via 680e748149d99afa27cf4a5a7de83d1652db06f9 (commit)
via 2ae1f990955e7a2b5aabef0ec93448c6c7e8c89f (commit)
via 338cfe287476bdbb2867cf3e745738435a0e2462 (commit)
via 23059244dc6933f7eed78bf46b6f7f8233301618 (commit)
via 5c03ce929bbda1f0fb271b35b5b3dbe1a12696c9 (commit)
via 6d40ecfc230408137edcc43f2653c70fe12589d6 (commit)
via ed2fb666188ea2c005b36d767bb7bb2861178dcc (commit)
via f33bdd59c6a8c8ea883f11578b463277d01c2b70 (commit)
via a3e8b0ad37ebb999e29c390332045bc9babfdc3c (commit)
via 45e404c1b58487d180e91d4b4eb0f5059902b190 (commit)
via c04fb71fa44c2a458aac57ae54eeb1711c017a49 (commit)
via e773dac499b952107dfc5e34290ec421b8bbf7c6 (commit)
via a50149705d6fe5e9b50ab963560ae9eee7458c45 (commit)
via 5e61d601d9424aac561c0661061e19188ed8d93f (commit)
via 57a25a015e23dca348c1e1a3c5821d64981277b1 (commit)
via 41d5b2ead11a334e89a7f04ab38571e2795083d0 (commit)
via 1a0742f1fd61234a6ec395729b0c4c4cad64aaac (commit)
via e7c52fec35a1f6a49351f58877d21fd43b4082e0 (commit)
via 971e77005e139347d5d4fedf0cc87bfb26b2685f (commit)
via 6e99f2cb85dd4722f7a5c2d15ebf2458cecc7b32 (commit)
via 18b3c00bb06e51f6a3b16fa634983a43f65b8e97 (commit)
via 79fe7149c3d2cc20d4bef791d27d5316702de53b (commit)
via 60a57d2997cfd0bfc27d580df175b31589fdedbf (commit)
via bf1529f69378453d8fb0b75cabb5d560e278d1c3 (commit)
via 95d6573794927ff46242625736d894ca4758f55a (commit)
via 20b0efb43cff8020da949df03ce13d58ee6e4eac (commit)
via 5b0a2957d3a806ce84ad86dd7789edf3ef495d92 (commit)
via b4b1eca7131cedca72a8973718881c1f18d2010f (commit)
via d63e340ac318b68e1a04458ba9669f159d6eb508 (commit)
via 1954874770a322999e04315397c25a56cd1f1b3a (commit)
via c77a043a8a6f6216499cc9697d5881c65bd9ad2d (commit)
via 58875c2089c61e5174db855530e9de237920c669 (commit)
via 2bde65105b86a512aa798cac5bcad723bafb38c9 (commit)
via 418f35f58458de0480d6a58299f06721481d9196 (commit)
via ba91cff481836c5c8c1993174727646213cea60b (commit)
via d72ba709f11dee0d0bb90868fc042bd8caaf8626 (commit)
via b4bf719aa55ad684dbfa5faa39aa93ebf89bac18 (commit)
via 49ee4eaf84ad72a044cf4957af016d1e8e15cab0 (commit)
via db3f2d81e975c2c82e575ba2947340f520f763a9 (commit)
via cc8e7feaf34f1028438854453b4811d7283852c0 (commit)
via 8b7ae5faff5a12fb2841e8b35b43253c0d67c22f (commit)
via 7c20771f4296820b7efc22b19a0783e7d226736a (commit)
via ae6f706d85ae90ca971d2cfef2abfe7e5bc7856d (commit)
via 9a306b863a628351a84cfafdabe5700d4840be6a (commit)
via 42e6736faa21f98de53a4ac0bc52306041581d91 (commit)
via dc50d73a4980ad1900e0445a1ea0dd46bf37245c (commit)
via 017278ca8084b9420fa94a2e9cc44aac9ebcae04 (commit)
via e61a5182aa295c0a96428d09e1d259c9b8b09d0d (commit)
via 8e94ab0a3baabd2b20f181243626168df4a56ec1 (commit)
via 390c1a763df3dbfcf0f24394464c0db89aaede9f (commit)
via ca59c5c8d5f309657b3f24f9edcc7100fd9d07a5 (commit)
via e9ac9994ee59019ec0689b94ef4d5cb15c0e5699 (commit)
via 53f6ad9e37ee9df633af7256bcda6bafd3cdc1a2 (commit)
via 32f82dfe9ca5b9a66104b0cec748a16dc648a0d0 (commit)
via 78981c6ff6b82a580786f332e58b5d3cfeed4e2c (commit)
via c25adf51ab0d73e162e520592daaa3e70ead8c15 (commit)
via e09ae1968e2c225fb42175e3962168953d4399e1 (commit)
via a57ce9dae4185a4542401a5ff6212f0bdf752488 (commit)
via be6122f520871344324be1ceb9f118195d508bba (commit)
via 0a4612cfe59ee2149b885550e467f4de42236bbb (commit)
via 03db051a722775e59b803bd44e02d1faad2a0a9a (commit)
via 5b6251fb9bb2cd793fd9f86719d1e359aa427cca (commit)
via 6e6cff79121a939cc961c8f36a489cbb2d0b7074 (commit)
via 7ef14b47d3bd773155e4b6d9c5a739b0213e8f28 (commit)
via dac0b87d5b0466e3aaf0f49ec7b2362606c03415 (commit)
This update added new revisions after undoing existing revisions. That is
to say, the old revision is not a strict subset of the new revision. This
situation occurs when you --force push a change and generate a repository
containing something like this:
* -- * -- B -- O -- O -- O (da78d2eb08150ae1558904cce44cc1d12f440edd)
\
N -- N -- N (0d8bee5c0cc3cf65a0a14890d7ff76de328e4316)
When this happens we assume that you've already had alert emails for all
of the O revisions, and so we here report only the revisions in the N
branch from the common base, B.
Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.
- Log -----------------------------------------------------------------
commit 0d8bee5c0cc3cf65a0a14890d7ff76de328e4316
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date: Fri Jul 12 11:35:07 2013 +0200
[2862] Pass the update command to the list manager
Pass the sgmtinfo-update command to the client list manager and ack the
segment when done.
Not tested, as there's interaction with the command channel and threads
and external modules. Too much plumbing and too little functionality to
test.
commit 2aa3e8e3ad750691dc97badcac032ef1ca92737f
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date: Fri Jul 12 11:12:11 2013 +0200
[2862] Doxygen about listsReconfigured
commit 661dae35e38a4b4a7d3f22b785509ceb528003e1
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date: Fri Jul 12 11:05:29 2013 +0200
[2862] Hack in a way to receive foreign commands
Provide a callback to receive commands for foreign modules. The current
implementation is somewhat broken and unsatisfactory, but let's leave
that up for later. We need a way now.
commit 7276a2eddfc2fefb354a4848aa4376b9dbeb675e
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date: Fri Jul 12 09:51:11 2013 +0200
[2862] Some error handling in the segment update
commit 3194a2019cc116c9c668cf4adbf879e06b927879
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date: Wed Jul 10 12:33:37 2013 +0200
[2862] Reset the segment upon command
commit 7cd60567cc43fe83a74a7799373e5e6410d435f3
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date: Wed Jul 10 11:21:02 2013 +0200
[2862] Test the SEGMENT_INFO_UPDATE command
commit d6c9e292ff83ad12b82b8aa73e59ae05d23ee65a
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date: Wed Jul 10 11:20:42 2013 +0200
[2862] Remove unnecessary public: section
It's already in public section there.
commit 617aa41e35a33420f00dae8bb9f39918208dc1a9
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date: Wed Jul 10 10:27:10 2013 +0200
[2862] Remove stray TODO
commit cb814de47219dbc310f277838106d5b4d7e17668
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date: Wed Jul 10 10:01:39 2013 +0200
[2862] Put the command into the queue
commit 228807659e177e95f4a3dbedc6978dcb8a02a2c3
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date: Tue Jul 9 15:44:44 2013 +0200
[2862] Define command to pass the segment info
commit ef523ea6235d16e0676d0263c964570134a59750
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date: Tue Jul 9 14:58:23 2013 +0200
[2862] Scan for remote segments after reconfiguration
commit eb01e3dce1476254ec6720798c861e709d32c9be
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date: Tue Jul 9 14:55:31 2013 +0200
[2862] Subscribe in case of remote segment
commit 87c3781cf964f63b044153a39b05518f72d8b80e
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date: Tue Jul 9 14:48:11 2013 +0200
[2862] Subscribe and unsubscribe on MCCS
commit 02e0859b64a98789b422bc57c457825bd991749f
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date: Tue Jul 9 13:56:48 2013 +0200
[2862] Auxiliary function to get list of classes
commit d26eac8dfc0780bd6211ff90f9d922bbe2bf7edb
Author: Michal 'vorner' Vaner <vorner at vorner.cz>
Date: Mon Jul 8 12:14:50 2013 +0200
[2862] Tests for subscribing to the readers
-----------------------------------------------------------------------
Summary of changes:
.gitignore | 4 +
ChangeLog | 36 +
configure.ac | 23 +-
doc/Doxyfile | 1 +
doc/design/datasrc/auth-local.txt | 137 +--
doc/design/datasrc/auth-mapped.txt | 15 +-
doc/design/datasrc/memmgr-mapped-init.txt | 25 +-
doc/design/datasrc/memmgr-mapped-reload.txt | 40 +-
doc/design/datasrc/overview.txt | 70 +-
doc/devel/mainpage.dox | 45 +-
m4macros/ax_boost_for_bind10.m4 | 14 +
src/bin/auth/datasrc_clients_mgr.h | 14 +
.../auth/tests/datasrc_clients_builder_unittest.cc | 16 +-
src/bin/auth/tests/datasrc_clients_mgr_unittest.cc | 4 +-
src/bin/d2/Makefile.am | 9 +-
src/bin/d2/d2_cfg_mgr.cc | 132 +++
src/bin/d2/d2_cfg_mgr.h | 175 +++
src/bin/d2/d2_config.cc | 605 ++++++++++
src/bin/d2/d2_config.h | 941 +++++++++++++++
src/bin/d2/d2_controller.h | 2 +-
src/bin/d2/d2_messages.mes | 40 +-
src/bin/d2/d2_process.cc | 19 +-
src/bin/d2/d2_process.h | 6 +-
src/bin/d2/d2_update_message.h | 4 +
src/bin/d2/d_cfg_mgr.cc | 240 ++++
src/bin/d2/d_cfg_mgr.h | 331 ++++++
src/bin/d2/d_controller.cc | 25 +-
src/bin/d2/d_controller.h | 24 +-
src/bin/d2/d_process.h | 24 +-
src/bin/d2/dhcp-ddns.spec | 210 +++-
src/bin/d2/dns_client.cc | 247 ++++
src/bin/d2/dns_client.h | 192 +++
src/bin/d2/main.cc | 3 +-
src/bin/d2/ncr_msg.cc | 459 ++++++++
src/bin/d2/ncr_msg.h | 487 ++++++++
src/bin/d2/tests/.gitignore | 1 +
src/bin/d2/tests/Makefile.am | 13 +
src/bin/d2/tests/d2_cfg_mgr_unittests.cc | 1238 ++++++++++++++++++++
src/bin/d2/tests/d2_controller_unittests.cc | 28 +-
src/bin/d2/tests/d2_process_unittests.cc | 17 +-
src/bin/d2/tests/d_cfg_mgr_unittests.cc | 386 ++++++
src/bin/d2/tests/d_test_stubs.cc | 126 +-
src/bin/d2/tests/d_test_stubs.h | 217 +++-
src/bin/d2/tests/dns_client_unittests.cc | 408 +++++++
src/bin/d2/tests/ncr_unittests.cc | 401 +++++++
.../d2/tests/test_data_files_config.h.in} | 5 +-
src/bin/memmgr/.gitignore | 1 +
src/bin/xfrin/b10-xfrin.xml | 175 +--
src/bin/xfrin/tests/xfrin_test.py | 1 +
src/bin/xfrin/xfrin.py.in | 22 +-
src/bin/xfrin/xfrin.spec | 204 ++--
src/bin/xfrout/b10-xfrout.xml | 70 +-
src/bin/xfrout/tests/xfrout_test.py.in | 30 +-
src/bin/xfrout/xfrout.py.in | 6 +-
src/bin/xfrout/xfrout.spec.pre.in | 96 +-
src/bin/zonemgr/tests/zonemgr_test.py | 137 ++-
src/bin/zonemgr/zonemgr.py.in | 252 ++--
src/bin/zonemgr/zonemgr_messages.mes | 12 +-
src/lib/Makefile.am | 2 +-
src/lib/dhcpsrv/dhcp_parsers.cc | 2 +-
src/lib/hooks/Makefile.am | 56 +
src/lib/{util => }/hooks/callout_handle.cc | 57 +-
src/lib/{util => }/hooks/callout_handle.h | 54 +-
src/lib/{util => }/hooks/callout_manager.cc | 100 +-
src/lib/{util => }/hooks/callout_manager.h | 185 ++-
.../interprocess_util.cc => hooks/hooks.h} | 44 +-
src/lib/hooks/hooks_component_developer.dox | 483 ++++++++
src/lib/{nsas/nsas_log.cc => hooks/hooks_log.cc} | 8 +-
.../{resolve/resolve_log.h => hooks/hooks_log.h} | 39 +-
src/lib/hooks/hooks_manager.cc | 173 +++
src/lib/hooks/hooks_manager.h | 276 +++++
src/lib/hooks/hooks_messages.mes | 167 +++
src/lib/hooks/library_handle.cc | 76 ++
src/lib/{util => }/hooks/library_handle.h | 38 +-
src/lib/hooks/library_manager.cc | 292 +++++
src/lib/hooks/library_manager.h | 190 +++
src/lib/hooks/library_manager_collection.cc | 114 ++
src/lib/hooks/library_manager_collection.h | 133 +++
src/lib/hooks/pointer_converter.h | 121 ++
src/lib/{util => }/hooks/server_hooks.cc | 95 +-
src/lib/{util => }/hooks/server_hooks.h | 134 +--
src/lib/hooks/tests/Makefile.am | 104 ++
src/lib/hooks/tests/basic_callout_library.cc | 115 ++
.../tests/callout_handle_unittest.cc | 22 +-
.../tests/callout_manager_unittest.cc | 209 +++-
src/lib/hooks/tests/common_test_class.h | 142 +++
.../hooks/tests/framework_exception_library.cc} | 41 +-
src/lib/hooks/tests/full_callout_library.cc | 137 +++
src/lib/{util => hooks}/tests/handles_unittest.cc | 91 +-
src/lib/hooks/tests/hooks_manager_unittest.cc | 454 +++++++
.../hooks/tests/incorrect_version_library.cc} | 25 +-
.../tests/library_manager_collection_unittest.cc | 184 +++
src/lib/hooks/tests/library_manager_unittest.cc | 555 +++++++++
src/lib/hooks/tests/load_callout_library.cc | 119 ++
.../hooks/tests/load_error_callout_library.cc} | 43 +-
.../hooks/tests/marker_file.h.in} | 21 +-
.../hooks/tests/no_version_library.cc} | 24 +-
src/lib/{config => hooks}/tests/run_unittests.cc | 5 +-
.../{util => hooks}/tests/server_hooks_unittest.cc | 174 +--
src/lib/hooks/tests/test_libraries.h.in | 79 ++
.../hooks/tests/unload_callout_library.cc} | 54 +-
src/lib/python/isc/config/ccsession.py | 84 +-
src/lib/python/isc/config/tests/ccsession_test.py | 71 ++
src/lib/python/isc/notify/notify_out.py | 8 +-
src/lib/python/isc/notify/tests/notify_out_test.py | 24 +-
src/lib/python/isc/server_common/.gitignore | 1 +
src/lib/python/isc/statistics/counters.py | 156 ++-
.../python/isc/statistics/tests/counters_test.py | 43 +-
.../isc/statistics/tests/testdata/test_spec2.spec | 96 +-
.../isc/statistics/tests/testdata/test_spec3.spec | 204 ++--
src/lib/util/Makefile.am | 4 -
src/lib/util/hooks/library_handle.cc | 39 -
src/lib/util/tests/Makefile.am | 4 -
.../xfrin/retransfer_slave.conf.orig | 1 -
.../xfrin/retransfer_slave_diffs.conf | 1 -
.../xfrin/retransfer_slave_notify.conf.orig | 1 -
.../xfrin/retransfer_slave_notify_v4.conf | 1 -
tests/lettuce/features/terrain/terrain.py | 2 +-
.../lettuce/features/xfrin_notify_handling.feature | 18 +-
119 files changed, 13180 insertions(+), 1480 deletions(-)
create mode 100644 src/bin/d2/d2_cfg_mgr.cc
create mode 100644 src/bin/d2/d2_cfg_mgr.h
create mode 100644 src/bin/d2/d2_config.cc
create mode 100644 src/bin/d2/d2_config.h
create mode 100644 src/bin/d2/d_cfg_mgr.cc
create mode 100644 src/bin/d2/d_cfg_mgr.h
create mode 100644 src/bin/d2/dns_client.cc
create mode 100644 src/bin/d2/dns_client.h
create mode 100644 src/bin/d2/ncr_msg.cc
create mode 100644 src/bin/d2/ncr_msg.h
create mode 100644 src/bin/d2/tests/d2_cfg_mgr_unittests.cc
create mode 100644 src/bin/d2/tests/d_cfg_mgr_unittests.cc
create mode 100644 src/bin/d2/tests/dns_client_unittests.cc
create mode 100644 src/bin/d2/tests/ncr_unittests.cc
copy src/{lib/config/tests/data_def_unittests_config.h.in => bin/d2/tests/test_data_files_config.h.in} (84%)
create mode 100644 src/lib/hooks/Makefile.am
rename src/lib/{util => }/hooks/callout_handle.cc (64%)
rename src/lib/{util => }/hooks/callout_handle.h (85%)
rename src/lib/{util => }/hooks/callout_manager.cc (66%)
rename src/lib/{util => }/hooks/callout_manager.h (59%)
copy src/lib/{util/unittests/interprocess_util.cc => hooks/hooks.h} (56%)
create mode 100644 src/lib/hooks/hooks_component_developer.dox
copy src/lib/{nsas/nsas_log.cc => hooks/hooks_log.cc} (88%)
copy src/lib/{resolve/resolve_log.h => hooks/hooks_log.h} (56%)
create mode 100644 src/lib/hooks/hooks_manager.cc
create mode 100644 src/lib/hooks/hooks_manager.h
create mode 100644 src/lib/hooks/hooks_messages.mes
create mode 100644 src/lib/hooks/library_handle.cc
rename src/lib/{util => }/hooks/library_handle.h (72%)
create mode 100644 src/lib/hooks/library_manager.cc
create mode 100644 src/lib/hooks/library_manager.h
create mode 100644 src/lib/hooks/library_manager_collection.cc
create mode 100644 src/lib/hooks/library_manager_collection.h
create mode 100644 src/lib/hooks/pointer_converter.h
rename src/lib/{util => }/hooks/server_hooks.cc (70%)
rename src/lib/{util => }/hooks/server_hooks.h (55%)
create mode 100644 src/lib/hooks/tests/Makefile.am
create mode 100644 src/lib/hooks/tests/basic_callout_library.cc
rename src/lib/{util => hooks}/tests/callout_handle_unittest.cc (95%)
rename src/lib/{util => hooks}/tests/callout_manager_unittest.cc (82%)
create mode 100644 src/lib/hooks/tests/common_test_class.h
copy src/{bin/resolver/bench/dummy_work.h => lib/hooks/tests/framework_exception_library.cc} (59%)
create mode 100644 src/lib/hooks/tests/full_callout_library.cc
rename src/lib/{util => hooks}/tests/handles_unittest.cc (94%)
create mode 100644 src/lib/hooks/tests/hooks_manager_unittest.cc
copy src/{bin/resolver/bench/dummy_work.cc => lib/hooks/tests/incorrect_version_library.cc} (65%)
create mode 100644 src/lib/hooks/tests/library_manager_collection_unittest.cc
create mode 100644 src/lib/hooks/tests/library_manager_unittest.cc
create mode 100644 src/lib/hooks/tests/load_callout_library.cc
copy src/{bin/resolver/bench/dummy_work.h => lib/hooks/tests/load_error_callout_library.cc} (59%)
copy src/{bin/resolver/bench/dummy_work.cc => lib/hooks/tests/marker_file.h.in} (75%)
copy src/{bin/resolver/bench/dummy_work.cc => lib/hooks/tests/no_version_library.cc} (72%)
copy src/lib/{config => hooks}/tests/run_unittests.cc (99%)
rename src/lib/{util => hooks}/tests/server_hooks_unittest.cc (52%)
create mode 100644 src/lib/hooks/tests/test_libraries.h.in
copy src/{bin/resolver/bench/naive_resolver.h => lib/hooks/tests/unload_callout_library.cc} (52%)
create mode 100644 src/lib/python/isc/server_common/.gitignore
delete mode 100644 src/lib/util/hooks/library_handle.cc
-----------------------------------------------------------------------
diff --git a/.gitignore b/.gitignore
index 7bc41b4..45f9424 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,6 +11,9 @@ Makefile
Makefile.in
TAGS
+*.log
+*.trs
+
/aclocal.m4
/autom4te.cache/
/config.guess
@@ -30,6 +33,7 @@ TAGS
/missing
/py-compile
/stamp-h1
+/test-driver
/all.info
/coverage-cpp-html
diff --git a/ChangeLog b/ChangeLog
index 62dde2f..1354073 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,39 @@
+641. [func] stephen
+ Added the hooks framework. This allows shared libraries of
+ user-written functions to be loaded at run-time and the
+ functions called during packet processing.
+ (Trac #2980, git 82c997a72890a12af135ace5b9ee100e41c5534e)
+
+640. [func] marcin
+ b10-dhcp-ddns: Implemented DNSClient class which implements
+ asynchronous DNS updates using UDP. The TCP and TSIG support
+ will be implemented at later time. Nevertheless, class API
+ accomodates the use of TCP and TSIG.
+ (Trac #2977, git 5a67a8982baa1fd6b796c063eeb13850c633702c)
+
+639. [bug] muks
+ Added workaround for build failure on Fedora 19 between GCC 4.8.x
+ and boost versions less than 1.54. Fedora 19 currently ships
+ boost-1.53.
+ (Trac #3039, git 4ef6830ed357ceb859ebb3e5e821a064bd8797bb)
+
+638. [bug]* naokikambe
+ Per-zone statistics counters are distinguished by zone class,
+ e.g. IN, CH, and HS. A class name is added onto a zone name in
+ structure of per-zone statistics.
+ (Trac #2884, git c0153581c3533ef045a92e68e0464aab00947cbb)
+
+637. [func] [tmark]
+ Added initial implementation of NameChangeRequest,
+ which embodies DNS update requests sent to DHCP-DDNS
+ by its clients.
+ (trac3007 git f33bdd59c6a8c8ea883f11578b463277d01c2b70)
+
+636. [func] [tmark]
+ Added the initial implementation of configuration parsing for
+ DCHP-DDNS.
+ (Trac #2957, git c04fb71fa44c2a458aac57ae54eeb1711c017a49)
+
635. [func] marcin
b10-dhcp-ddns: Implemented DNS Update message construction.
(Trac #2796, git eac5e751473e238dee1ebf16491634a1fbea25e2)
diff --git a/configure.ac b/configure.ac
index 8a0118e..72a825e 100644
--- a/configure.ac
+++ b/configure.ac
@@ -430,6 +430,7 @@ AC_SUBST(B10_CXXFLAGS)
AC_SEARCH_LIBS(inet_pton, [nsl])
AC_SEARCH_LIBS(recvfrom, [socket])
AC_SEARCH_LIBS(nanosleep, [rt])
+AC_SEARCH_LIBS(dlsym, [dl])
# Checks for header files.
@@ -469,6 +470,17 @@ AM_COND_IF([OS_BSD], [AC_DEFINE([OS_BSD], [1], [Running on BSD?])])
AM_CONDITIONAL(OS_SOLARIS, test $OS_TYPE = Solaris)
AM_COND_IF([OS_SOLARIS], [AC_DEFINE([OS_SOLARIS], [1], [Running on Solaris?])])
+# Deal with variants
+AM_CONDITIONAL(OS_FREEBSD, test $system = FreeBSD)
+AM_COND_IF([OS_FREEBSD], [AC_DEFINE([OS_FREEBSD], [1], [Running on FreeBSD?])])
+AM_CONDITIONAL(OS_NETBSD, test $system = NetBSD)
+AM_COND_IF([OS_NETBSD], [AC_DEFINE([OS_NETBSD], [1], [Running on NetBSD?])])
+AM_CONDITIONAL(OS_OPENBSD, test $system = OpenBSD)
+AM_COND_IF([OS_OPENBSD], [AC_DEFINE([OS_OPENBSD], [1], [Running on OpenBSD?])])
+AM_CONDITIONAL(OS_OSX, test $system = Darwin)
+AM_COND_IF([OS_OSX], [AC_DEFINE([OS_OSX], [1], [Running on OSX?])])
+
+
AC_MSG_CHECKING(for sa_len in struct sockaddr)
AC_TRY_COMPILE([
#include <sys/types.h>
@@ -871,10 +883,14 @@ LIBS=$LIBS_SAVED
AX_BOOST_FOR_BIND10
# Boost offset_ptr is required in one library and not optional right now, so
# we unconditionally fail here if it doesn't work.
-if test "$BOOST_OFFSET_PTR_FAILURE" = "yes"; then
+if test "$BOOST_OFFSET_PTR_WOULDFAIL" = "yes"; then
AC_MSG_ERROR([Failed to compile a required header file. Try upgrading Boost to 1.44 or higher (when using clang++) or specifying --without-werror. See the ChangeLog entry for Trac no. 2147 for more details.])
fi
+if test "$BOOST_STATIC_ASSERT_WOULDFAIL" = "yes" -a X"$werror_ok" = X1; then
+ AC_MSG_ERROR([Failed to use Boost static assertions. Try upgrading Boost to 1.54 or higher (when using GCC 4.8) or specifying --without-werror. See trac ticket no. 3039 for more details.])
+fi
+
# There's a known bug in FreeBSD ports for Boost that would trigger a false
# warning in build with g++ and -Werror (we exclude clang++ explicitly to
# avoid unexpected false positives).
@@ -1313,6 +1329,8 @@ AC_CONFIG_FILES([Makefile
src/lib/datasrc/tests/memory/testdata/Makefile
src/lib/xfr/Makefile
src/lib/xfr/tests/Makefile
+ src/lib/hooks/Makefile
+ src/lib/hooks/tests/Makefile
src/lib/log/Makefile
src/lib/log/interprocess/Makefile
src/lib/log/interprocess/tests/Makefile
@@ -1405,6 +1423,7 @@ AC_OUTPUT([doc/version.ent
src/bin/dhcp4/spec_config.h.pre
src/bin/dhcp6/spec_config.h.pre
src/bin/d2/spec_config.h.pre
+ src/bin/d2/tests/test_data_files_config.h
src/bin/tests/process_rename_test.py
src/lib/config/tests/data_def_unittests_config.h
src/lib/python/isc/config/tests/config_test
@@ -1418,6 +1437,8 @@ AC_OUTPUT([doc/version.ent
src/lib/cc/session_config.h.pre
src/lib/cc/tests/session_unittests_config.h
src/lib/datasrc/datasrc_config.h.pre
+ src/lib/hooks/tests/marker_file.h
+ src/lib/hooks/tests/test_libraries.h
src/lib/log/tests/console_test.sh
src/lib/log/tests/destination_test.sh
src/lib/log/tests/init_logger_test.sh
diff --git a/doc/Doxyfile b/doc/Doxyfile
index bcb9285..57c6ce1 100644
--- a/doc/Doxyfile
+++ b/doc/Doxyfile
@@ -679,6 +679,7 @@ INPUT = ../src/lib/exceptions \
../src/lib/cache \
../src/lib/server_common/ \
../src/bin/sockcreator/ \
+ ../src/lib/hooks/ \
../src/lib/util/ \
../src/lib/util/io/ \
../src/lib/util/threads/ \
diff --git a/doc/design/datasrc/auth-local.txt b/doc/design/datasrc/auth-local.txt
index 79d7399..afe8ad8 100644
--- a/doc/design/datasrc/auth-local.txt
+++ b/doc/design/datasrc/auth-local.txt
@@ -1,26 +1,28 @@
@startuml
-participant auth as "b10-auth"
+participant auth as ":b10-auth"
[-> auth: new/initial config\n(datasrc cfg)
activate auth
-participant list as "Configurable\nClientList"
+participant list as ":Configurable\nClientList"
create list
auth -> list: <<construct>>
auth -> list: configure(cfg)
activate list
-create CacheConfig
-list -> CacheConfig: <<construct>> (cfg)
+participant cache_config as ":CacheConfig"
+create cache_config
+list -> cache_config: <<construct>> (cfg)
-participant zt_segment as "ZoneTable\nSegment\n(Local)"
+participant zt_segment as ":ZoneTable\nSegment\n(Local)"
create zt_segment
list -> zt_segment: <<construct>>
activate zt_segment
-create ZoneTable
-zt_segment -> ZoneTable: <<construct>>
+participant zone_table as ":ZoneTable"
+create zone_table
+zt_segment -> zone_table: <<construct>>
deactivate zt_segment
@@ -30,47 +32,50 @@ note over zt_segment: Local segments are\nalways writable
zt_segment --> list: true
deactivate zt_segment
-loop for each zone in CacheConfig
-list -> CacheConfig: getLoadAction()
-activate CacheConfig
+loop for each zone in cache_config
+list -> cache_config: getLoadAction()
+activate cache_config
-create LoadAction
-CacheConfig -> LoadAction: <<construct>>
+participant la1 as "la1:LoadAction"
+create la1
+cache_config -> la1: <<construct>>
-participant LoadAction.2
+participant la2 as "la2:LoadAction"
-CacheConfig --> list : LoadAction
+cache_config --> list : la1
-deactivate CacheConfig
+deactivate cache_config
-create ZoneWriter
-list -> ZoneWriter: <<construct>> (load_action)
+participant w1 as "w1:ZoneWriter"
+create w1
+list -> w1: <<construct>> (la1)
-participant ZoneWriter.2
+participant w2 as "w2:ZoneWriter"
-list -> ZoneWriter: load()
-activate ZoneWriter
-ZoneWriter -> LoadAction: (funcall)
-activate LoadAction
+list -> w1: load()
+activate w1
+w1 -> la1: (funcall)
+activate la1
-create ZoneData
-LoadAction -> ZoneData: <<construct>> via helpers
+participant zd1 as "zd1:ZoneData"
+create zd1
+la1 -> zd1: <<construct>> via helpers
-participant ZoneData.2
+participant zd2 as "zd2:ZoneData"
-LoadAction --> ZoneWriter: ZoneData
-deactivate LoadAction
-deactivate ZoneWriter
+la1 --> w1: zd1
+deactivate la1
+deactivate w1
-list -> ZoneWriter: install()
-activate ZoneWriter
+list -> w1: install()
+activate w1
-ZoneWriter -> ZoneTable: addZone(ZoneData)
-activate ZoneTable
-ZoneTable --> ZoneWriter: NULL (no old data)
-deactivate ZoneTable
+w1 -> zone_table: addZone(zd1)
+activate zone_table
+zone_table --> w1: NULL (no old data)
+deactivate zone_table
-deactivate ZoneWriter
+deactivate w1
end
@@ -82,55 +87,55 @@ deactivate auth
[-> auth: reload\n(zonename)
activate auth
-auth -> list: getCachedZoneWriter\n(zone_name)
+auth -> list: getCachedw1\n(zone_name)
activate list
-list -> CacheConfig: getLoadAction()
-activate CacheConfig
+list -> cache_config: getLoadAction()
+activate cache_config
-create LoadAction.2
-CacheConfig -> LoadAction.2: <<construct>>
+create la2
+cache_config -> la2: <<construct>>
-CacheConfig --> list : LoadAction.2
+cache_config --> list : la2
-deactivate CacheConfig
+deactivate cache_config
-create ZoneWriter.2
-list -> ZoneWriter.2: <<construct>> (load_action)
+create w2
+list -> w2: <<construct>> (la2)
-list --> auth: ZoneWriter.2
+list --> auth: w2
deactivate list
-auth -> ZoneWriter.2: load()
-activate ZoneWriter.2
-ZoneWriter.2 -> LoadAction.2: (funcall)
-activate LoadAction.2
+auth -> w2: load()
+activate w2
+w2 -> la2: (funcall)
+activate la2
-create ZoneData.2
-LoadAction.2 -> ZoneData.2: <<construct>> via helpers
+create zd2
+la2 -> zd2: <<construct>> via helpers
-LoadAction.2 --> ZoneWriter.2: ZoneData.2
-deactivate LoadAction.2
-deactivate ZoneWriter.2
+la2 --> w2: zd2
+deactivate la2
+deactivate w2
-auth -> ZoneWriter.2: install()
-activate ZoneWriter.2
+auth -> w2: install()
+activate w2
-ZoneWriter.2 -> ZoneTable: addZone(ZoneData.2)
-activate ZoneTable
-ZoneTable --> ZoneWriter.2: ZoneData (old data)
-deactivate ZoneTable
+w2 -> zone_table: addZone(zd2)
+activate zone_table
+zone_table --> w2: zd1 (old data)
+deactivate zone_table
-deactivate ZoneWriter.2
+deactivate w2
-auth -> ZoneWriter.2: cleanup()
-activate ZoneWriter.2
+auth -> w2: cleanup()
+activate w2
-ZoneWriter.2 -> ZoneData: <<destroy>>
-destroy ZoneData
-deactivate ZoneWriter.2
+w2 -> zd1: <<destroy>>
+destroy zd1
+deactivate w2
deactivate auth
diff --git a/doc/design/datasrc/auth-mapped.txt b/doc/design/datasrc/auth-mapped.txt
index d656220..b5b1a39 100644
--- a/doc/design/datasrc/auth-mapped.txt
+++ b/doc/design/datasrc/auth-mapped.txt
@@ -1,20 +1,21 @@
@startuml
-participant auth as "b10-auth"
+participant auth as ":b10-auth"
[-> auth: new/initial config\n(datasrc cfg)
activate auth
-participant list as "Configurable\nClientList"
+participant list as ":Configurable\nClientList"
create list
auth -> list: <<construct>>
auth -> list: configure(cfg)
activate list
+participant CacheConfig as ":CacheConfig"
create CacheConfig
list -> CacheConfig: <<construct>> (cfg)
-participant zt_segment as "ZoneTable\nSegment\n(Mapped)"
+participant zt_segment as ":ZoneTable\nSegment\n(Mapped)"
create zt_segment
list -> zt_segment: <<construct>>
@@ -46,7 +47,7 @@ activate list
list -> zt_segment: reset\n(READ_ONLY,\nsegmentparam)
activate zt_segment
-participant segment as "Memory\nSegment\n(Mapped)"
+participant segment as "seg1:Memory\nSegment\n(Mapped)"
create segment
zt_segment -> segment: <<construct>>
@@ -68,9 +69,9 @@ activate zt_segment
zt_segment -> segment: <<destroy>>
destroy segment
-participant segment.2 as "Memory\nSegment\n(Mapped)\n2"
-create segment.2
-zt_segment -> segment.2: <<construct>>
+participant segment2 as "seg2:Memory\nSegment\n(Mapped)"
+create segment2
+zt_segment -> segment2: <<construct>>
deactivate zt_segment
deactivate list
diff --git a/doc/design/datasrc/memmgr-mapped-init.txt b/doc/design/datasrc/memmgr-mapped-init.txt
index 52ca783..9daea13 100644
--- a/doc/design/datasrc/memmgr-mapped-init.txt
+++ b/doc/design/datasrc/memmgr-mapped-init.txt
@@ -1,20 +1,21 @@
@startuml
-participant memmgr as "memmgr"
+participant memmgr as ":b10-memmgr"
[-> memmgr: new/initial config\n(datasrc cfg)
activate memmgr
-participant list as "Configurable\nClientList"
+participant list as ":Configurable\nClientList"
create list
memmgr -> list: <<construct>>
memmgr -> list: configure(cfg)
activate list
+participant CacheConfig as ":CacheConfig"
create CacheConfig
list -> CacheConfig: <<construct>> (cfg)
-participant zt_segment as "ZoneTable\nSegment\n(Mapped)"
+participant zt_segment as ":ZoneTable\nSegment\n(Mapped)"
create zt_segment
list -> zt_segment: <<construct>>
@@ -39,12 +40,13 @@ activate list
list -> zt_segment: reset\n(READ_WRITE,\nsegmentparam)
activate zt_segment
-participant segment as "Memory\nSegment\n(Mapped)"
+participant segment as "seg1:Memory\nSegment\n(Mapped)"
create segment
zt_segment -> segment: <<construct>>
-participant segment.2 as "Memory\nSegment\n(Mapped)\n2"
+participant segment.2 as "seg2:Memory\nSegment\n(Mapped)"
+participant ZoneTable as ":ZoneTable"
create ZoneTable
zt_segment -> ZoneTable: <<construct>>
@@ -65,17 +67,19 @@ activate list
list -> CacheConfig: getLoadAction()
activate CacheConfig
+participant LoadAction as "la:LoadAction"
create LoadAction
CacheConfig -> LoadAction: <<construct>>
-CacheConfig --> list : LoadAction
+CacheConfig --> list : la
deactivate CacheConfig
+participant ZoneWriter as "zw:ZoneWriter"
create ZoneWriter
-list -> ZoneWriter: <<construct>> (load_action)
+list -> ZoneWriter: <<construct>> (la)
-list --> memmgr: ZoneWriter
+list --> memmgr: zw
deactivate list
@@ -85,17 +89,18 @@ activate ZoneWriter
ZoneWriter -> LoadAction: (funcall)
activate LoadAction
+participant ZoneData as "zd:ZoneData"
create ZoneData
LoadAction -> ZoneData: <<construct>> via helpers
-LoadAction --> ZoneWriter: ZoneData
+LoadAction --> ZoneWriter: zd
deactivate LoadAction
deactivate ZoneWriter
memmgr -> ZoneWriter: install()
activate ZoneWriter
-ZoneWriter -> ZoneTable: addZone(ZoneData)
+ZoneWriter -> ZoneTable: addZone(zd)
activate ZoneTable
ZoneTable --> ZoneWriter: NULL (no old data)
deactivate ZoneTable
diff --git a/doc/design/datasrc/memmgr-mapped-reload.txt b/doc/design/datasrc/memmgr-mapped-reload.txt
index 83dbf46..676e961 100644
--- a/doc/design/datasrc/memmgr-mapped-reload.txt
+++ b/doc/design/datasrc/memmgr-mapped-reload.txt
@@ -1,18 +1,18 @@
@startuml
-participant memmgr as "memmgr"
+participant memmgr as ":b10-memmgr"
[-> memmgr: reload\n(zonename)
activate memmgr
-participant list as "Configurable\nClientList"
+participant list as ":Configurable\nClientList"
memmgr -> list: getCachedZoneWriter\n(zone_name)
activate list
-participant CacheConfig
+participant CacheConfig as ":CacheConfig"
-participant zt_segment as "ZoneTable\nSegment\n(Mapped)"
-participant segment as "Memory\nSegment\n(Mapped)"
-participant segment.2 as "Memory\nSegment\n(Mapped)\n2"
+participant zt_segment as ":ZoneTable\nSegment\n(Mapped)"
+participant segment as "existing:Memory\nSegment\n(Mapped)"
+participant segment2 as "new:Memory\nSegment\n(Mapped)"
list -> zt_segment: isWritable()
activate zt_segment
@@ -22,17 +22,18 @@ deactivate zt_segment
list -> CacheConfig: getLoadAction()
activate CacheConfig
-participant ZoneTable
-participant ZoneWriter
+participant ZoneTable as ":ZoneTable"
+participant ZoneWriter as "zw:ZoneWriter"
+participant LoadAction as "la:LoadAction"
create LoadAction
CacheConfig -> LoadAction: <<construct>>
-CacheConfig --> list: LoadAction
+CacheConfig --> list: la
deactivate CacheConfig
create ZoneWriter
-list -> ZoneWriter: <<construct>> (LoadAction)
-list --> memmgr: ZoneWriter
+list -> ZoneWriter: <<construct>> (la)
+list --> memmgr: zw
deactivate list
memmgr -> ZoneWriter: load()
@@ -40,21 +41,22 @@ activate ZoneWriter
ZoneWriter -> LoadAction: (funcall)
activate LoadAction
-participant ZoneData
+participant ZoneData as "zd_existing\n:ZoneData"
+participant ZoneData2 as "zd_new\n:ZoneData"
-create ZoneData.2
-LoadAction -> ZoneData.2: <<construct>> via helpers
+create ZoneData2
+LoadAction -> ZoneData2: <<construct>> via helpers
-LoadAction --> ZoneWriter: ZoneData.2
+LoadAction --> ZoneWriter: zd_new
deactivate LoadAction
deactivate ZoneWriter
memmgr -> ZoneWriter: install()
activate ZoneWriter
-ZoneWriter -> ZoneTable: addZone(ZoneData.2)
+ZoneWriter -> ZoneTable: addZone(zd_new)
activate ZoneTable
-ZoneTable --> ZoneWriter: ZoneData (old data)
+ZoneTable --> ZoneWriter: zd_existing (old data)
deactivate ZoneTable
deactivate ZoneWriter
@@ -77,8 +79,8 @@ activate zt_segment
zt_segment -> segment: <<destroy>>
destroy segment
-create segment.2
-zt_segment -> segment.2: <<construct>>
+create segment2
+zt_segment -> segment2: <<construct>>
deactivate zt_segment
deactivate list
diff --git a/doc/design/datasrc/overview.txt b/doc/design/datasrc/overview.txt
index 49aae9d..4ee971c 100644
--- a/doc/design/datasrc/overview.txt
+++ b/doc/design/datasrc/overview.txt
@@ -4,65 +4,65 @@ hide members
note "Automatic placement of classes\ndoesn't look good. This diagram\nhas to be improved." as n1
-Auth "1" *-d-> "*" ConfigurableClientList
-Auth -d-> DataSourceClient
-Auth -d-> ZoneWriter
-Auth -d-> ZoneTableAccessor
-Auth -d-> DataSourceStatus
-Auth -d-> ZoneTableIterator
-
-ConfigurableClientList "1" *-d-> "*" DataSourceInfo
+Auth "1" *--> "*" ConfigurableClientList
+Auth --> DataSourceClient
+Auth --> ZoneWriter
+Auth --> ZoneTableAccessor
+Auth --> DataSourceStatus
+Auth --> ZoneTableIterator
+
+ConfigurableClientList "1" *--> "*" DataSourceInfo
ConfigurableClientList ..> ZoneTableSegment : <<reset>>
-ConfigurableClientList ..d-> DataSourceStatus : <<create>>
+ConfigurableClientList ..> DataSourceStatus : <<create>>
ConfigurableClientList ..> ZoneWriter : <<create>>
ConfigurableClientList ..> ZoneTableAccessor : <<create>>
-DataSourceInfo "1" *-u-> "*" DataSourceClient
-DataSourceInfo "1" *-r-> "*" CacheConfig
-DataSourceInfo "1" *-d-> "*" ZoneTableSegment
+DataSourceInfo "1" *--> "*" DataSourceClient
+DataSourceInfo "1" *--> "*" CacheConfig
+DataSourceInfo "1" *--> "*" ZoneTableSegment
ZoneTableAccessor ..> ZoneTableIterator : <<create>>
-ZoneTableAccessorCache -> CacheConfig
+ZoneTableAccessorCache --> CacheConfig
ZoneTableAccessorCache ..> ZoneTableIteratorCache : <<create>>
-ZoneTableAccessorCache -u-o ZoneTableAccessor
+ZoneTableAccessorCache --o ZoneTableAccessor
-ZoneTableIteratorCache -u-o ZoneTableIterator
-ZoneTableIteratorCache -u-> CacheConfig
+ZoneTableIteratorCache --o ZoneTableIterator
+ZoneTableIteratorCache --> CacheConfig
-ZoneWriter -d-> ZoneTableSegment
+ZoneWriter --> ZoneTableSegment
ZoneWriter ..> ZoneData : add/replace
-ZoneTableSegment "1" *-r-> "1" ZoneTableHeader
-ZoneTableSegment "1" *-d-> "1" MemorySegment
+ZoneTableSegment "1" *--> "1" ZoneTableHeader
+ZoneTableSegment "1" *--> "1" MemorySegment
CacheConfig ..> LoadAction
LoadAction ..> ZoneData : create
-LoadAction *-> ZoneDataLoader
+LoadAction *--> ZoneDataLoader
-ZoneDataLoader -> ZoneData
-ZoneDataLoader *-> ZoneDataUpdater
-ZoneDataLoader -> MemorySegment
+ZoneDataLoader --> ZoneData
+ZoneDataLoader *--> ZoneDataUpdater
+ZoneDataLoader --> MemorySegment
-ZoneDataUpdater -> ZoneData
+ZoneDataUpdater --> ZoneData
ZoneDataUpdater ..> RdataSet : create
ZoneDataUpdater ..> RdataSet : add
-ZoneTableHeader "1" *-d-> "1" ZoneTable
-ZoneTable "1" *-d-> "1" ZoneData
-ZoneData "1" *-d-> "1" RdataSet
+ZoneTableHeader "1" *--> "1" ZoneTable
+ZoneTable "1" *--> "1" ZoneData
+ZoneData "1" *--> "1" RdataSet
-loadFromFile -d-o LoadAction
-IteratorLoader -d-o LoadAction
+LoadFromFile --o LoadAction
+IteratorLoader --o LoadAction
-MemorySegmentMapped -d-o MemorySegment
-MemorySegmentLocal -d-o MemorySegment
+MemorySegmentMapped --o MemorySegment
+MemorySegmentLocal --o MemorySegment
-ZoneTableSegmentMapped -d-o ZoneTableSegment
-ZoneTableSegmentLocal -d-o ZoneTableSegment
+ZoneTableSegmentMapped --o ZoneTableSegment
+ZoneTableSegmentLocal --o ZoneTableSegment
-ZoneTableSegmentMapped *-d-> MemorySegmentMapped
-ZoneTableSegmentLocal *-d-> MemorySegmentLocal
+ZoneTableSegmentMapped *--> MemorySegmentMapped
+ZoneTableSegmentLocal *--> MemorySegmentLocal
@enduml
diff --git a/doc/devel/mainpage.dox b/doc/devel/mainpage.dox
index 92f86eb..5c8efa2 100644
--- a/doc/devel/mainpage.dox
+++ b/doc/devel/mainpage.dox
@@ -1,11 +1,33 @@
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
/**
- *
* @mainpage BIND10 Developer's Guide
*
* Welcome to BIND10 Developer's Guide. This documentation is addressed
- * at existing and prospecting developers and programmers, who would like
- * to gain insight into internal workings of BIND 10. It could also be useful
- * for existing and prospective contributors.
+ * at existing and prospecting developers and programmers and provides
+ * information needed to both extend and maintain BIND 10.
+ *
+ * If you wish to write "hook" code - code that is loaded by BIND 10 at
+ * run-time and modifies its behavior you should read the section
+ * @ref hookDevelopersGuide.
+ *
+ * BIND 10 maintanenace information is divided into a number of sections
+ * depending on focus. DNS-specific issues are covered in the
+ * @ref dnsMaintenanceGuide while information on DHCP-specific topics can
+ * be found in the @ref dhcpMaintenanceGuide. General BIND 10 topics, not
+ * specific to any protocol, are discussed in @ref miscellaneousTopics.
*
* If you are a user or system administrator, rather than software engineer,
* you should read <a href="http://bind10.isc.org/docs/bind10-guide.html">BIND10
@@ -13,13 +35,15 @@
*
* Regardless of your field of expertise, you are encouraged to visit
* <a href="http://bind10.isc.org/">BIND10 webpage (http://bind10.isc.org)</a>
+ * @section hooksFramework Hooks Framework
+ * - @subpage hooksComponentDeveloperGuide
*
- * @section DNS
+ * @section dnsMaintenanceGuide DNS Maintenance Guide
* - Authoritative DNS (todo)
* - Recursive resolver (todo)
* - @subpage DataScrubbing
*
- * @section DHCP
+ * @section dhcpMaintenanceGuide DHCP Maintenance Guide
* - @subpage dhcp4
* - @subpage dhcpv4Session
* - @subpage dhcpv4ConfigParser
@@ -39,7 +63,7 @@
* - @subpage dhcpDatabaseBackends
* - @subpage perfdhcpInternals
*
- * @section misc Miscellaneous topics
+ * @section miscellaneousTopics Miscellaneous topics
* - @subpage LoggingApi
* - @subpage LoggingApiOverview
* - @subpage LoggingApiLoggerNames
@@ -47,7 +71,10 @@
* - @subpage SocketSessionUtility
* - <a href="./doxygen-error.log">Documentation warnings and errors</a>
*
- * @todo: Move this logo to the right (and possibly up). Not sure what
- * is the best way to do it in Doxygen, without using CSS hacks.
* @image html isc-logo.png
*/
+/*
+ * @todo: Move the logo to the right (and possibly up). Not sure what
+ * is the best way to do it in Doxygen, without using CSS hacks.
+ */
+
diff --git a/m4macros/ax_boost_for_bind10.m4 b/m4macros/ax_boost_for_bind10.m4
index 577af6b..3a71337 100644
--- a/m4macros/ax_boost_for_bind10.m4
+++ b/m4macros/ax_boost_for_bind10.m4
@@ -28,6 +28,8 @@ dnl cause build failure; otherwise set to "no"
dnl BOOST_MAPPED_FILE_CXXFLAG set to the compiler flag that would need to
dnl compile managed_mapped_file (can be empty).
dnl It is of no use if "WOULDFAIL" is yes.
+dnl BOOST_STATIC_ASSERT_WOULDFAIL set to "yes" if BOOST_STATIC_ASSERT would
+dnl cause build error; otherwise set to "no"
AC_DEFUN([AX_BOOST_FOR_BIND10], [
AC_LANG_SAVE
@@ -146,6 +148,18 @@ if test $BOOST_MAPPED_FILE_WOULDFAIL = yes; then
AC_MSG_RESULT(no)
fi
+# BOOST_STATIC_ASSERT in versions below Boost 1.54.0 is known to result
+# in warnings with GCC 4.8. Detect it.
+AC_MSG_CHECKING([BOOST_STATIC_ASSERT compiles])
+AC_TRY_COMPILE([
+#include <boost/static_assert.hpp>
+void testfn(void) { BOOST_STATIC_ASSERT(true); }
+],,
+[AC_MSG_RESULT(yes)
+ BOOST_STATIC_ASSERT_WOULDFAIL=no],
+[AC_MSG_RESULT(no)
+ BOOST_STATIC_ASSERT_WOULDFAIL=yes])
+
CXXFLAGS="$CXXFLAGS_SAVED"
AC_SUBST(BOOST_INCLUDES)
diff --git a/src/bin/auth/datasrc_clients_mgr.h b/src/bin/auth/datasrc_clients_mgr.h
index b22f6e2..6ea51fa 100644
--- a/src/bin/auth/datasrc_clients_mgr.h
+++ b/src/bin/auth/datasrc_clients_mgr.h
@@ -95,14 +95,28 @@ typedef boost::function<void ()> FinishedCallback;
/// This just holds the data items together, no logic or protection
/// is present here.
struct Command {
+ /// \brief Constructor
+ ///
+ /// It just initializes the member variables of the same names
+ /// as the parameters.
Command(CommandID id, const data::ConstElementPtr& params,
const FinishedCallback& callback) :
id(id),
params(params),
callback(callback)
{}
+ /// \brief The command to execute
CommandID id;
+ /// \brief Argument of the command.
+ ///
+ /// If the command takes no argument, it should be null pointer.
+ ///
+ /// This may be a null pointer if the command takes no parameters.
data::ConstElementPtr params;
+ /// \brief A callback to be called once the command finishes.
+ ///
+ /// This may be an empty boost::function. In such case, no callback
+ /// will be called after completion.
FinishedCallback callback;
};
diff --git a/src/bin/auth/tests/datasrc_clients_builder_unittest.cc b/src/bin/auth/tests/datasrc_clients_builder_unittest.cc
index c4911aa..f3e098a 100644
--- a/src/bin/auth/tests/datasrc_clients_builder_unittest.cc
+++ b/src/bin/auth/tests/datasrc_clients_builder_unittest.cc
@@ -58,6 +58,7 @@ protected:
DataSrcClientsBuilderTest() :
clients_map(new std::map<RRClass,
boost::shared_ptr<ConfigurableClientList> >),
+ write_end(-1), read_end(-1),
builder(&command_queue, &callback_queue, &cond, &queue_mutex,
&clients_map, &map_mutex, generateSockets()),
cond(command_queue, delayed_command_queue), rrclass(RRClass::IN()),
@@ -74,6 +75,7 @@ protected:
std::list<Command> command_queue; // test command queue
std::list<Command> delayed_command_queue; // commands available after wait
std::list<FinishedCallback> callback_queue; // Callbacks from commands
+ int write_end, read_end;
TestDataSrcClientsBuilder builder;
TestCondVar cond;
TestMutex queue_mutex;
@@ -81,7 +83,6 @@ protected:
const RRClass rrclass;
const Command shutdown_cmd;
const Command noop_cmd;
- int write_end, read_end;
private:
int generateSockets() {
int pair[2];
@@ -124,13 +125,24 @@ TEST_F(DataSrcClientsBuilderTest, commandFinished) {
EXPECT_EQ(2, queue_mutex.unlock_count);
// There's one callback in the queue
ASSERT_EQ(1, callback_queue.size());
- EXPECT_EQ(emptyCallsback, callback_queue.front());
+ // Not using EXPECT_EQ, as that produces warning in printing out the result
+ EXPECT_TRUE(emptyCallsback == callback_queue.front());
// And we are woken up.
char c;
int result = recv(read_end, &c, 1, MSG_DONTWAIT);
EXPECT_EQ(1, result);
}
+// Test that low-level errors with the synchronization socket
+// (an unexpected condition) is detected and program aborted.
+TEST_F(DataSrcClientsBuilderTest, finishedCrash) {
+ command_queue.push_back(Command(SHUTDOWN, ConstElementPtr(),
+ emptyCallsback));
+ // Break the socket
+ close(write_end);
+ EXPECT_DEATH_IF_SUPPORTED({builder.run();}, "");
+}
+
TEST_F(DataSrcClientsBuilderTest, runMultiCommands) {
// Two NOOP commands followed by SHUTDOWN. We should see two doNoop()
// calls.
diff --git a/src/bin/auth/tests/datasrc_clients_mgr_unittest.cc b/src/bin/auth/tests/datasrc_clients_mgr_unittest.cc
index ccd78a7..565deb5 100644
--- a/src/bin/auth/tests/datasrc_clients_mgr_unittest.cc
+++ b/src/bin/auth/tests/datasrc_clients_mgr_unittest.cc
@@ -290,7 +290,7 @@ TEST(DataSrcClientsMgrTest, wakeup) {
// Push a callback in and wake the manager
FakeDataSrcClientsBuilder::callback_queue->
push_back(boost::bind(callback, &called, &tag, 1));
- write(FakeDataSrcClientsBuilder::wakeup_fd, "w", 1);
+ EXPECT_EQ(1, write(FakeDataSrcClientsBuilder::wakeup_fd, "w", 1));
mgr.run_one();
EXPECT_TRUE(called);
EXPECT_EQ(1, tag);
@@ -298,7 +298,7 @@ TEST(DataSrcClientsMgrTest, wakeup) {
called = false;
// If we wake up and don't push anything, it doesn't break.
- write(FakeDataSrcClientsBuilder::wakeup_fd, "w", 1);
+ EXPECT_EQ(1, write(FakeDataSrcClientsBuilder::wakeup_fd, "w", 1));
mgr.run_one();
EXPECT_FALSE(called);
diff --git a/src/bin/d2/Makefile.am b/src/bin/d2/Makefile.am
index 6f889db..a00bece 100644
--- a/src/bin/d2/Makefile.am
+++ b/src/bin/d2/Makefile.am
@@ -48,12 +48,16 @@ pkglibexec_PROGRAMS = b10-dhcp-ddns
b10_dhcp_ddns_SOURCES = main.cc
b10_dhcp_ddns_SOURCES += d2_log.cc d2_log.h
-b10_dhcp_ddns_SOURCES += d_process.h
b10_dhcp_ddns_SOURCES += d2_process.cc d2_process.h
b10_dhcp_ddns_SOURCES += d_controller.cc d_controller.h
b10_dhcp_ddns_SOURCES += d2_controller.cc d2_controller.h
+b10_dhcp_ddns_SOURCES += d_cfg_mgr.cc d_cfg_mgr.h
+b10_dhcp_ddns_SOURCES += d2_config.cc d2_config.h
+b10_dhcp_ddns_SOURCES += d2_cfg_mgr.cc d2_cfg_mgr.h
b10_dhcp_ddns_SOURCES += d2_update_message.cc d2_update_message.h
b10_dhcp_ddns_SOURCES += d2_zone.cc d2_zone.h
+b10_dhcp_ddns_SOURCES += dns_client.cc dns_client.h
+b10_dhcp_ddns_SOURCES += ncr_msg.cc ncr_msg.h
nodist_b10_dhcp_ddns_SOURCES = d2_messages.h d2_messages.cc
EXTRA_DIST += d2_messages.mes
@@ -61,9 +65,12 @@ EXTRA_DIST += d2_messages.mes
b10_dhcp_ddns_LDADD = $(top_builddir)/src/lib/log/libb10-log.la
b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
+b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/asiodns/libb10-asiodns.la
b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
+b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/dhcpsrv/libb10-dhcpsrv.la
b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/dns/libb10-dns++.la
+b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
b10_dhcp_ddnsdir = $(pkgdatadir)
b10_dhcp_ddns_DATA = dhcp-ddns.spec
diff --git a/src/bin/d2/d2_cfg_mgr.cc b/src/bin/d2/d2_cfg_mgr.cc
new file mode 100644
index 0000000..07068d6
--- /dev/null
+++ b/src/bin/d2/d2_cfg_mgr.cc
@@ -0,0 +1,132 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <d2/d2_log.h>
+#include <d2/d2_cfg_mgr.h>
+
+#include <boost/foreach.hpp>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::data;
+using namespace isc::asiolink;
+
+namespace isc {
+namespace d2 {
+
+// *********************** D2CfgContext *************************
+
+D2CfgContext::D2CfgContext()
+ : forward_mgr_(new DdnsDomainListMgr("forward_mgr")),
+ reverse_mgr_(new DdnsDomainListMgr("reverse_mgr")),
+ keys_(new TSIGKeyInfoMap()) {
+}
+
+D2CfgContext::D2CfgContext(const D2CfgContext& rhs) : DCfgContextBase(rhs) {
+ if (rhs.forward_mgr_) {
+ forward_mgr_.reset(new DdnsDomainListMgr(rhs.forward_mgr_->getName()));
+ forward_mgr_->setDomains(rhs.forward_mgr_->getDomains());
+ }
+
+ if (rhs.reverse_mgr_) {
+ reverse_mgr_.reset(new DdnsDomainListMgr(rhs.reverse_mgr_->getName()));
+ reverse_mgr_->setDomains(rhs.reverse_mgr_->getDomains());
+ }
+
+ keys_ = rhs.keys_;
+}
+
+D2CfgContext::~D2CfgContext() {
+}
+
+// *********************** D2CfgMgr *************************
+
+D2CfgMgr::D2CfgMgr() : DCfgMgrBase(DCfgContextBasePtr(new D2CfgContext())) {
+ // TSIG keys need to parse before the Domains, so we can catch Domains
+ // that specify undefined keys. Create the necessary parsing order now.
+ addToParseOrder("interface");
+ addToParseOrder("ip_address");
+ addToParseOrder("port");
+ addToParseOrder("tsig_keys");
+ addToParseOrder("forward_ddns");
+ addToParseOrder("reverse_ddns");
+}
+
+D2CfgMgr::~D2CfgMgr() {
+}
+
+bool
+D2CfgMgr::matchForward(const std::string& fqdn, DdnsDomainPtr& domain) {
+ if (fqdn.empty()) {
+ // This is a programmatic error and should not happen.
+ isc_throw(D2CfgError, "matchForward passed an empty fqdn");
+ }
+
+ // Fetch the forward manager from the D2 context.
+ DdnsDomainListMgrPtr mgr = getD2CfgContext()->getForwardMgr();
+
+ // Call the manager's match method and return the result.
+ return (mgr->matchDomain(fqdn, domain));
+}
+
+bool
+D2CfgMgr::matchReverse(const std::string& fqdn, DdnsDomainPtr& domain) {
+ if (fqdn.empty()) {
+ // This is a programmatic error and should not happen.
+ isc_throw(D2CfgError, "matchReverse passed a null or empty fqdn");
+ }
+
+ // Fetch the reverse manager from the D2 context.
+ DdnsDomainListMgrPtr mgr = getD2CfgContext()->getReverseMgr();
+
+ // Call the manager's match method and return the result.
+ return (mgr->matchDomain(fqdn, domain));
+}
+
+
+isc::dhcp::ParserPtr
+D2CfgMgr::createConfigParser(const std::string& config_id) {
+ // Get D2 specific context.
+ D2CfgContextPtr context = getD2CfgContext();
+
+ // Create parser instance based on element_id.
+ DhcpConfigParser* parser = NULL;
+ if ((config_id == "interface") ||
+ (config_id == "ip_address")) {
+ parser = new StringParser(config_id, context->getStringStorage());
+ } else if (config_id == "port") {
+ parser = new Uint32Parser(config_id, context->getUint32Storage());
+ } else if (config_id == "forward_ddns") {
+ parser = new DdnsDomainListMgrParser("forward_mgr",
+ context->getForwardMgr(),
+ context->getKeys());
+ } else if (config_id == "reverse_ddns") {
+ parser = new DdnsDomainListMgrParser("reverse_mgr",
+ context->getReverseMgr(),
+ context->getKeys());
+ } else if (config_id == "tsig_keys") {
+ parser = new TSIGKeyInfoListParser("tsig_key_list", context->getKeys());
+ } else {
+ isc_throw(NotImplemented,
+ "parser error: D2CfgMgr parameter not supported: "
+ << config_id);
+ }
+
+ return (isc::dhcp::ParserPtr(parser));
+}
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
+
diff --git a/src/bin/d2/d2_cfg_mgr.h b/src/bin/d2/d2_cfg_mgr.h
new file mode 100644
index 0000000..f9089cb
--- /dev/null
+++ b/src/bin/d2/d2_cfg_mgr.h
@@ -0,0 +1,175 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef D2_CFG_MGR_H
+#define D2_CFG_MGR_H
+
+#include <asiolink/io_address.h>
+#include <cc/data.h>
+#include <exceptions/exceptions.h>
+#include <d2/d_cfg_mgr.h>
+#include <d2/d2_config.h>
+
+#include <stdint.h>
+#include <string>
+
+namespace isc {
+namespace d2 {
+
+class D2CfgContext;
+/// @brief Pointer to a configuration context.
+typedef boost::shared_ptr<D2CfgContext> D2CfgContextPtr;
+
+/// @brief DHCP-DDNS Configuration Context
+///
+/// Implements the storage container for configuration context.
+/// It provides a single enclosure for the storage of configuration parameters
+/// and any other DHCP-DDNS specific information that needs to be accessible
+/// during configuration parsing as well as to the application as a whole.
+/// It is derived from the context base class, DCfgContextBase.
+class D2CfgContext : public DCfgContextBase {
+public:
+ /// @brief Constructor
+ D2CfgContext();
+
+ /// @brief Destructor
+ virtual ~D2CfgContext();
+
+ /// @brief Creates a clone of this context object.
+ ///
+ /// @return returns a pointer to the new clone.
+ virtual DCfgContextBasePtr clone() {
+ return (DCfgContextBasePtr(new D2CfgContext(*this)));
+ }
+
+ /// @brief Fetches the forward DNS domain list manager.
+ ///
+ /// @return returns a pointer to the forward manager.
+ DdnsDomainListMgrPtr getForwardMgr() {
+ return (forward_mgr_);
+ }
+
+ /// @brief Fetches the reverse DNS domain list manager.
+ ///
+ /// @return returns a pointer to the reverse manager.
+ DdnsDomainListMgrPtr getReverseMgr() {
+ return (reverse_mgr_);
+ }
+
+ /// @brief Fetches the map of TSIG keys.
+ ///
+ /// @return returns a pointer to the key map.
+ TSIGKeyInfoMapPtr getKeys() {
+ return (keys_);
+ }
+
+protected:
+ /// @brief Copy constructor for use by derivations in clone().
+ D2CfgContext(const D2CfgContext& rhs);
+
+private:
+ /// @brief Private assignment operator to avoid potential for slicing.
+ D2CfgContext& operator=(const D2CfgContext& rhs);
+
+ /// @brief Forward domain list manager.
+ DdnsDomainListMgrPtr forward_mgr_;
+
+ /// @brief Reverse domain list manager.
+ DdnsDomainListMgrPtr reverse_mgr_;
+
+ /// @brief Storage for the map of TSIGKeyInfos
+ TSIGKeyInfoMapPtr keys_;
+};
+
+/// @brief Defines a pointer for DdnsDomain instances.
+typedef boost::shared_ptr<DdnsDomainListMgr> DdnsDomainListMgrPtr;
+
+
+
+/// @brief DHCP-DDNS Configuration Manager
+///
+/// Provides the mechanisms for managing the DHCP-DDNS application's
+/// configuration. This includes services for parsing sets of configuration
+/// values, storing the parsed information in its converted form,
+/// and retrieving the information on demand.
+class D2CfgMgr : public DCfgMgrBase {
+public:
+ /// @brief Constructor
+ D2CfgMgr();
+
+ /// @brief Destructor
+ virtual ~D2CfgMgr();
+
+ /// @brief Convenience method that returns the D2 configuration context.
+ ///
+ /// @return returns a pointer to the configuration context.
+ D2CfgContextPtr getD2CfgContext() {
+ return (boost::dynamic_pointer_cast<D2CfgContext>(getContext()));
+ }
+
+ /// @brief Matches a given FQDN to a forward domain.
+ ///
+ /// This calls the matchDomain method of the forward domain manager to
+ /// match the given FQDN to a forward domain.
+ ///
+ /// @param fqdn is the name for which to look.
+ /// @param domain receives the matching domain. Note that it will be reset
+ /// upon entry and only set if a match is subsequently found.
+ ///
+ /// @return returns true if a match is found, false otherwise.
+ /// @throw throws D2CfgError if given an invalid fqdn.
+ bool matchForward(const std::string& fqdn, DdnsDomainPtr &domain);
+
+ /// @brief Matches a given FQDN to a reverse domain.
+ ///
+ /// This calls the matchDomain method of the reverse domain manager to
+ /// match the given FQDN to a forward domain.
+ ///
+ /// @param fqdn is the name for which to look.
+ /// @param domain receives the matching domain. Note that it will be reset
+ /// upon entry and only set if a match is subsequently found.
+ ///
+ /// @return returns true if a match is found, false otherwise.
+ /// @throw throws D2CfgError if given an invalid fqdn.
+ bool matchReverse(const std::string& fqdn, DdnsDomainPtr &domain);
+
+protected:
+ /// @brief Given an element_id returns an instance of the appropriate
+ /// parser.
+ ///
+ /// It is responsible for top-level or outermost DHCP-DDNS configuration
+ /// elements (see dhcp-ddns.spec):
+ /// 1. interface
+ /// 2. ip_address
+ /// 3. port
+ /// 4. forward_ddns
+ /// 5. reverse_ddns
+ ///
+ /// @param element_id is the string name of the element as it will appear
+ /// in the configuration set.
+ ///
+ /// @return returns a ParserPtr to the parser instance.
+ /// @throw throws DCfgMgrBaseError if an error occurs.
+ virtual isc::dhcp::ParserPtr
+ createConfigParser(const std::string& element_id);
+};
+
+/// @brief Defines a shared pointer to D2CfgMgr.
+typedef boost::shared_ptr<D2CfgMgr> D2CfgMgrPtr;
+
+
+}; // end of isc::d2 namespace
+}; // end of isc namespace
+
+#endif // D2_CFG_MGR_H
diff --git a/src/bin/d2/d2_config.cc b/src/bin/d2/d2_config.cc
new file mode 100644
index 0000000..fe5ad0b
--- /dev/null
+++ b/src/bin/d2/d2_config.cc
@@ -0,0 +1,605 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <d2/d2_log.h>
+#include <d2/d2_cfg_mgr.h>
+#include <dhcpsrv/dhcp_parsers.h>
+#include <exceptions/exceptions.h>
+#include <asiolink/io_error.h>
+
+#include <boost/foreach.hpp>
+#include <boost/lexical_cast.hpp>
+
+namespace isc {
+namespace d2 {
+
+// *********************** TSIGKeyInfo *************************
+
+TSIGKeyInfo::TSIGKeyInfo(const std::string& name, const std::string& algorithm,
+ const std::string& secret)
+ :name_(name), algorithm_(algorithm), secret_(secret) {
+}
+
+TSIGKeyInfo::~TSIGKeyInfo() {
+}
+
+
+// *********************** DnsServerInfo *************************
+
+const char* DnsServerInfo::EMPTY_IP_STR = "0.0.0.0";
+
+DnsServerInfo::DnsServerInfo(const std::string& hostname,
+ isc::asiolink::IOAddress ip_address, uint32_t port,
+ bool enabled)
+ :hostname_(hostname), ip_address_(ip_address), port_(port),
+ enabled_(enabled) {
+}
+
+DnsServerInfo::~DnsServerInfo() {
+}
+
+// *********************** DdnsDomain *************************
+
+DdnsDomain::DdnsDomain(const std::string& name, const std::string& key_name,
+ DnsServerInfoStoragePtr servers)
+ : name_(name), key_name_(key_name), servers_(servers) {
+}
+
+DdnsDomain::~DdnsDomain() {
+}
+
+// *********************** DdnsDomainLstMgr *************************
+
+const char* DdnsDomainListMgr::wildcard_domain_name_ = "*";
+
+DdnsDomainListMgr::DdnsDomainListMgr(const std::string& name) : name_(name),
+ domains_(new DdnsDomainMap()) {
+}
+
+
+DdnsDomainListMgr::~DdnsDomainListMgr () {
+}
+
+void
+DdnsDomainListMgr::setDomains(DdnsDomainMapPtr domains) {
+ if (!domains) {
+ isc_throw(D2CfgError,
+ "DdnsDomainListMgr::setDomains: Domain list may not be null");
+ }
+
+ domains_ = domains;
+
+ // Look for the wild card domain. If present, set the member variable
+ // to remember it. This saves us from having to look for it every time
+ // we attempt a match.
+ DdnsDomainMap::iterator gotit = domains_->find(wildcard_domain_name_);
+ if (gotit != domains_->end()) {
+ wildcard_domain_ = gotit->second;
+ }
+}
+
+bool
+DdnsDomainListMgr::matchDomain(const std::string& fqdn, DdnsDomainPtr& domain) {
+ // Clear the return parameter.
+ domain.reset();
+
+ // First check the case of one domain to rule them all.
+ if ((size() == 1) && (wildcard_domain_)) {
+ domain = wildcard_domain_;
+ return (true);
+ }
+
+ // Start with the longest version of the fqdn and search the list.
+ // Continue looking for shorter versions of fqdn so long as no match is
+ // found.
+ // @todo This can surely be optimized, time permitting.
+ std::string match_name = fqdn;
+ std::size_t start_pos = 0;
+ while (start_pos != std::string::npos) {
+ match_name = match_name.substr(start_pos, std::string::npos);
+ DdnsDomainMap::iterator gotit = domains_->find(match_name);
+ if (gotit != domains_->end()) {
+ domain = gotit->second;
+ return (true);
+ }
+
+ start_pos = match_name.find_first_of(".");
+ if (start_pos != std::string::npos) {
+ ++start_pos;
+ }
+ }
+
+ // There's no match. If they specified a wild card domain use it
+ // otherwise there's no domain for this entry.
+ if (wildcard_domain_) {
+ domain = wildcard_domain_;
+ return (true);
+ }
+
+ LOG_WARN(dctl_logger, DHCP_DDNS_NO_MATCH).arg(fqdn);
+ return (false);
+}
+
+// *************************** PARSERS ***********************************
+
+// *********************** TSIGKeyInfoParser *************************
+
+TSIGKeyInfoParser::TSIGKeyInfoParser(const std::string& entry_name,
+ TSIGKeyInfoMapPtr keys)
+ : entry_name_(entry_name), keys_(keys), local_scalars_() {
+ if (!keys_) {
+ isc_throw(D2CfgError, "TSIGKeyInfoParser ctor:"
+ " key storage cannot be null");
+ }
+}
+
+TSIGKeyInfoParser::~TSIGKeyInfoParser() {
+}
+
+void
+TSIGKeyInfoParser::build(isc::data::ConstElementPtr key_config) {
+ isc::dhcp::ConfigPair config_pair;
+ // For each element in the key configuration:
+ // 1. Create a parser for the element.
+ // 2. Invoke the parser's build method passing in the element's
+ // configuration.
+ // 3. Invoke the parser's commit method to store the element's parsed
+ // data to the parser's local storage.
+ BOOST_FOREACH (config_pair, key_config->mapValue()) {
+ isc::dhcp::ParserPtr parser(createConfigParser(config_pair.first));
+ parser->build(config_pair.second);
+ parser->commit();
+ }
+}
+
+isc::dhcp::ParserPtr
+TSIGKeyInfoParser::createConfigParser(const std::string& config_id) {
+ DhcpConfigParser* parser = NULL;
+ // Based on the configuration id of the element, create the appropriate
+ // parser. Scalars are set to use the parser's local scalar storage.
+ if ((config_id == "name") ||
+ (config_id == "algorithm") ||
+ (config_id == "secret")) {
+ parser = new isc::dhcp::StringParser(config_id,
+ local_scalars_.getStringStorage());
+ } else {
+ isc_throw(NotImplemented,
+ "parser error: TSIGKeyInfo parameter not supported: "
+ << config_id);
+ }
+
+ // Return the new parser instance.
+ return (isc::dhcp::ParserPtr(parser));
+}
+
+void
+TSIGKeyInfoParser::commit() {
+ std::string name;
+ std::string algorithm;
+ std::string secret;
+
+ // Fetch the key configuration's parsed scalar values from parser's
+ // local storage.
+ local_scalars_.getParam("name", name);
+ local_scalars_.getParam("algorithm", algorithm);
+ local_scalars_.getParam("secret", secret);
+
+ // @todo Validation here is very superficial. This will expand as TSIG
+ // Key use is more fully implemented.
+
+ // Name cannot be blank.
+ if (name.empty()) {
+ isc_throw(D2CfgError, "TSIG Key Info must specify name");
+ }
+
+ // Algorithme cannot be blank.
+ if (algorithm.empty()) {
+ isc_throw(D2CfgError, "TSIG Key Info must specify algorithm");
+ }
+
+ // Secret cannot be blank.
+ if (secret.empty()) {
+ isc_throw(D2CfgError, "TSIG Key Info must specify secret");
+ }
+
+ // Currently, the premise is that key storage is always empty prior to
+ // parsing so we are always adding keys never replacing them. Duplicates
+ // are not allowed and should be flagged as a configuration error.
+ if (keys_->find(name) != keys_->end()) {
+ isc_throw(D2CfgError, "Duplicate TSIG key specified:" << name);
+ }
+
+ TSIGKeyInfoPtr key_info(new TSIGKeyInfo(name, algorithm, secret));
+
+ // Add the new TSIGKeyInfo to the key storage.
+ (*keys_)[name]=key_info;
+}
+
+// *********************** TSIGKeyInfoListParser *************************
+
+TSIGKeyInfoListParser::TSIGKeyInfoListParser(const std::string& list_name,
+ TSIGKeyInfoMapPtr keys)
+ :list_name_(list_name), keys_(keys), parsers_() {
+ if (!keys_) {
+ isc_throw(D2CfgError, "TSIGKeyInfoListParser ctor:"
+ " key storage cannot be null");
+ }
+}
+
+TSIGKeyInfoListParser::~TSIGKeyInfoListParser(){
+}
+
+void
+TSIGKeyInfoListParser::
+build(isc::data::ConstElementPtr key_list){
+ int i = 0;
+ isc::data::ConstElementPtr key_config;
+ // For each key element in the key list:
+ // 1. Create a parser for the key element.
+ // 2. Invoke the parser's build method passing in the key's
+ // configuration.
+ // 3. Add the parser to a local collection of parsers.
+ BOOST_FOREACH(key_config, key_list->listValue()) {
+ // Create a name for the parser based on its position in the list.
+ std::string entry_name = boost::lexical_cast<std::string>(i++);
+ isc::dhcp::ParserPtr parser(new TSIGKeyInfoParser(entry_name,
+ keys_));
+ parser->build(key_config);
+ parsers_.push_back(parser);
+ }
+}
+
+void
+TSIGKeyInfoListParser::commit() {
+ // Invoke commit on each server parser. This will cause each one to
+ // create it's server instance and commit it to storage.
+ BOOST_FOREACH(isc::dhcp::ParserPtr parser, parsers_) {
+ parser->commit();
+ }
+}
+
+// *********************** DnsServerInfoParser *************************
+
+DnsServerInfoParser::DnsServerInfoParser(const std::string& entry_name,
+ DnsServerInfoStoragePtr servers)
+ : entry_name_(entry_name), servers_(servers), local_scalars_() {
+ if (!servers_) {
+ isc_throw(D2CfgError, "DnsServerInfoParser ctor:"
+ " server storage cannot be null");
+ }
+}
+
+DnsServerInfoParser::~DnsServerInfoParser() {
+}
+
+void
+DnsServerInfoParser::build(isc::data::ConstElementPtr server_config) {
+ isc::dhcp::ConfigPair config_pair;
+ // For each element in the server configuration:
+ // 1. Create a parser for the element.
+ // 2. Invoke the parser's build method passing in the element's
+ // configuration.
+ // 3. Invoke the parser's commit method to store the element's parsed
+ // data to the parser's local storage.
+ BOOST_FOREACH (config_pair, server_config->mapValue()) {
+ isc::dhcp::ParserPtr parser(createConfigParser(config_pair.first));
+ parser->build(config_pair.second);
+ parser->commit();
+ }
+
+}
+
+isc::dhcp::ParserPtr
+DnsServerInfoParser::createConfigParser(const std::string& config_id) {
+ DhcpConfigParser* parser = NULL;
+ // Based on the configuration id of the element, create the appropriate
+ // parser. Scalars are set to use the parser's local scalar storage.
+ if ((config_id == "hostname") ||
+ (config_id == "ip_address")) {
+ parser = new isc::dhcp::StringParser(config_id,
+ local_scalars_.getStringStorage());
+ } else if (config_id == "port") {
+ parser = new isc::dhcp::Uint32Parser(config_id,
+ local_scalars_.getUint32Storage());
+ } else {
+ isc_throw(NotImplemented,
+ "parser error: DnsServerInfo parameter not supported: "
+ << config_id);
+ }
+
+ // Return the new parser instance.
+ return (isc::dhcp::ParserPtr(parser));
+}
+
+void
+DnsServerInfoParser::commit() {
+ std::string hostname;
+ std::string ip_address;
+ uint32_t port = DnsServerInfo::STANDARD_DNS_PORT;
+
+ // Fetch the server configuration's parsed scalar values from parser's
+ // local storage.
+ local_scalars_.getParam("hostname", hostname, DCfgContextBase::OPTIONAL);
+ local_scalars_.getParam("ip_address", ip_address,
+ DCfgContextBase::OPTIONAL);
+ local_scalars_.getParam("port", port, DCfgContextBase::OPTIONAL);
+
+ // The configuration must specify one or the other.
+ if (hostname.empty() == ip_address.empty()) {
+ isc_throw(D2CfgError, "Dns Server must specify one or the other"
+ " of hostname and IP address");
+ }
+
+ DnsServerInfoPtr serverInfo;
+ if (!hostname.empty()) {
+ // When hostname is specified, create a valid, blank IOAddress and
+ // then create the DnsServerInfo.
+ isc::asiolink::IOAddress io_addr(DnsServerInfo::EMPTY_IP_STR);
+ serverInfo.reset(new DnsServerInfo(hostname, io_addr, port));
+ } else {
+ try {
+ // Create an IOAddress from the IP address string given and then
+ // create the DnsServerInfo.
+ isc::asiolink::IOAddress io_addr(ip_address);
+ serverInfo.reset(new DnsServerInfo(hostname, io_addr, port));
+ } catch (const isc::asiolink::IOError& ex) {
+ isc_throw(D2CfgError, "Invalid IP address:" << ip_address);
+ }
+ }
+
+ // Add the new DnsServerInfo to the server storage.
+ servers_->push_back(serverInfo);
+}
+
+// *********************** DnsServerInfoListParser *************************
+
+DnsServerInfoListParser::DnsServerInfoListParser(const std::string& list_name,
+ DnsServerInfoStoragePtr servers)
+ :list_name_(list_name), servers_(servers), parsers_() {
+ if (!servers_) {
+ isc_throw(D2CfgError, "DdnsServerInfoListParser ctor:"
+ " server storage cannot be null");
+ }
+}
+
+DnsServerInfoListParser::~DnsServerInfoListParser(){
+}
+
+void
+DnsServerInfoListParser::
+build(isc::data::ConstElementPtr server_list){
+ int i = 0;
+ isc::data::ConstElementPtr server_config;
+ // For each server element in the server list:
+ // 1. Create a parser for the server element.
+ // 2. Invoke the parser's build method passing in the server's
+ // configuration.
+ // 3. Add the parser to a local collection of parsers.
+ BOOST_FOREACH(server_config, server_list->listValue()) {
+ // Create a name for the parser based on its position in the list.
+ std::string entry_name = boost::lexical_cast<std::string>(i++);
+ isc::dhcp::ParserPtr parser(new DnsServerInfoParser(entry_name,
+ servers_));
+ parser->build(server_config);
+ parsers_.push_back(parser);
+ }
+}
+
+void
+DnsServerInfoListParser::commit() {
+ // Domains must have at least one server.
+ if (parsers_.size() == 0) {
+ isc_throw (D2CfgError, "Server List must contain at least one server");
+ }
+
+ // Invoke commit on each server parser. This will cause each one to
+ // create it's server instance and commit it to storage.
+ BOOST_FOREACH(isc::dhcp::ParserPtr parser, parsers_) {
+ parser->commit();
+ }
+}
+
+// *********************** DdnsDomainParser *************************
+
+DdnsDomainParser::DdnsDomainParser(const std::string& entry_name,
+ DdnsDomainMapPtr domains,
+ TSIGKeyInfoMapPtr keys)
+ : entry_name_(entry_name), domains_(domains), keys_(keys),
+ local_servers_(new DnsServerInfoStorage()), local_scalars_() {
+ if (!domains_) {
+ isc_throw(D2CfgError,
+ "DdnsDomainParser ctor, domain storage cannot be null");
+ }
+}
+
+
+DdnsDomainParser::~DdnsDomainParser() {
+}
+
+void
+DdnsDomainParser::build(isc::data::ConstElementPtr domain_config) {
+ // For each element in the domain configuration:
+ // 1. Create a parser for the element.
+ // 2. Invoke the parser's build method passing in the element's
+ // configuration.
+ // 3. Invoke the parser's commit method to store the element's parsed
+ // data to the parser's local storage.
+ isc::dhcp::ConfigPair config_pair;
+ BOOST_FOREACH(config_pair, domain_config->mapValue()) {
+ isc::dhcp::ParserPtr parser(createConfigParser(config_pair.first));
+ parser->build(config_pair.second);
+ parser->commit();
+ }
+}
+
+isc::dhcp::ParserPtr
+DdnsDomainParser::createConfigParser(const std::string& config_id) {
+ DhcpConfigParser* parser = NULL;
+ // Based on the configuration id of the element, create the appropriate
+ // parser. Scalars are set to use the parser's local scalar storage.
+ if ((config_id == "name") ||
+ (config_id == "key_name")) {
+ parser = new isc::dhcp::StringParser(config_id,
+ local_scalars_.getStringStorage());
+ } else if (config_id == "dns_servers") {
+ // Server list parser is given in our local server storage. It will pass
+ // this down to its server parsers and is where they will write their
+ // server instances upon commit.
+ parser = new DnsServerInfoListParser(config_id, local_servers_);
+ } else {
+ isc_throw(NotImplemented,
+ "parser error: DdnsDomain parameter not supported: "
+ << config_id);
+ }
+
+ // Return the new domain parser instance.
+ return (isc::dhcp::ParserPtr(parser));
+}
+
+void
+DdnsDomainParser::commit() {
+ std::string name;
+ std::string key_name;
+
+ // Domain name is not optional. The get will throw if its not there.
+ local_scalars_.getParam("name", name);
+
+ // Blank domain names are not allowed.
+ if (name.empty()) {
+ isc_throw(D2CfgError, "Domain name cannot be blank");
+ }
+
+ // Currently, the premise is that domain storage is always empty
+ // prior to parsing so always adding domains never replacing them.
+ // Duplicates are not allowed and should be flagged as a configuration
+ // error.
+ if (domains_->find(name) != domains_->end()) {
+ isc_throw(D2CfgError, "Duplicate domain specified:" << name);
+ }
+
+ // Key name is optional. If it is not blank, then validate it against
+ // the defined list of keys.
+ local_scalars_.getParam("key_name", key_name, DCfgContextBase::OPTIONAL);
+ if (!key_name.empty()) {
+ if ((!keys_) || (keys_->find(key_name) == keys_->end())) {
+ isc_throw(D2CfgError, "DdnsDomain :" << name <<
+ " specifies and undefined key:" << key_name);
+ }
+ }
+
+ // Instantiate the new domain and add it to domain storage.
+ DdnsDomainPtr domain(new DdnsDomain(name, key_name, local_servers_));
+
+ // Add the new domain to the domain storage.
+ (*domains_)[name]=domain;
+}
+
+// *********************** DdnsDomainListParser *************************
+
+DdnsDomainListParser::DdnsDomainListParser(const std::string& list_name,
+ DdnsDomainMapPtr domains,
+ TSIGKeyInfoMapPtr keys)
+ :list_name_(list_name), domains_(domains), keys_(keys), parsers_() {
+ if (!domains_) {
+ isc_throw(D2CfgError, "DdnsDomainListParser ctor:"
+ " domain storage cannot be null");
+ }
+}
+
+DdnsDomainListParser::~DdnsDomainListParser(){
+}
+
+void
+DdnsDomainListParser::
+build(isc::data::ConstElementPtr domain_list){
+ // For each domain element in the domain list:
+ // 1. Create a parser for the domain element.
+ // 2. Invoke the parser's build method passing in the domain's
+ // configuration.
+ // 3. Add the parser to the local collection of parsers.
+ int i = 0;
+ isc::data::ConstElementPtr domain_config;
+ BOOST_FOREACH(domain_config, domain_list->listValue()) {
+ std::string entry_name = boost::lexical_cast<std::string>(i++);
+ isc::dhcp::ParserPtr parser(new DdnsDomainParser(entry_name,
+ domains_, keys_));
+ parser->build(domain_config);
+ parsers_.push_back(parser);
+ }
+}
+
+void
+DdnsDomainListParser::commit() {
+ // Invoke commit on each server parser. This will cause each one to
+ // create it's server instance and commit it to storage.
+ BOOST_FOREACH(isc::dhcp::ParserPtr parser, parsers_) {
+ parser->commit();
+ }
+}
+
+
+// *********************** DdnsDomainListMgrParser *************************
+
+DdnsDomainListMgrParser::DdnsDomainListMgrParser(const std::string& entry_name,
+ DdnsDomainListMgrPtr mgr, TSIGKeyInfoMapPtr keys)
+ : entry_name_(entry_name), mgr_(mgr), keys_(keys),
+ local_domains_(new DdnsDomainMap()), local_scalars_() {
+}
+
+
+DdnsDomainListMgrParser::~DdnsDomainListMgrParser() {
+}
+
+void
+DdnsDomainListMgrParser::build(isc::data::ConstElementPtr domain_config) {
+ // For each element in the domain manager configuration:
+ // 1. Create a parser for the element.
+ // 2. Invoke the parser's build method passing in the element's
+ // configuration.
+ // 3. Invoke the parser's commit method to store the element's parsed
+ // data to the parser's local storage.
+ isc::dhcp::ConfigPair config_pair;
+ BOOST_FOREACH(config_pair, domain_config->mapValue()) {
+ isc::dhcp::ParserPtr parser(createConfigParser(config_pair.first));
+ parser->build(config_pair.second);
+ parser->commit();
+ }
+}
+
+isc::dhcp::ParserPtr
+DdnsDomainListMgrParser::createConfigParser(const std::string& config_id) {
+ DhcpConfigParser* parser = NULL;
+ if (config_id == "ddns_domains") {
+ // Domain list parser is given our local domain storage. It will pass
+ // this down to its domain parsers and is where they will write their
+ // domain instances upon commit.
+ parser = new DdnsDomainListParser(config_id, local_domains_, keys_);
+ } else {
+ isc_throw(NotImplemented, "parser error: "
+ "DdnsDomainListMgr parameter not supported: " << config_id);
+ }
+
+ // Return the new domain parser instance.
+ return (isc::dhcp::ParserPtr(parser));
+}
+
+void
+DdnsDomainListMgrParser::commit() {
+ // Add the new domain to the domain storage.
+ mgr_->setDomains(local_domains_);
+}
+
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
diff --git a/src/bin/d2/d2_config.h b/src/bin/d2/d2_config.h
new file mode 100644
index 0000000..7350291
--- /dev/null
+++ b/src/bin/d2/d2_config.h
@@ -0,0 +1,941 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef D2_CONFIG_H
+#define D2_CONFIG_H
+
+#include <asiolink/io_address.h>
+#include <cc/data.h>
+#include <d2/d_cfg_mgr.h>
+#include <dhcpsrv/dhcp_parsers.h>
+#include <exceptions/exceptions.h>
+
+#include <boost/foreach.hpp>
+
+#include <stdint.h>
+#include <string>
+
+namespace isc {
+namespace d2 {
+
+/// @file d2_config.h
+/// @brief A collection of classes for housing and parsing the application
+/// configuration necessary for the DHCP-DDNS application (aka D2).
+///
+/// This file contains the class declarations for the class hierarchy created
+/// from the D2 configuration and the parser classes used to create it.
+/// The application configuration consists of a set of scalar parameters,
+/// a list of TSIG keys, and two managed lists of domains: one list for
+/// forward domains and one list for reverse domains.
+///
+/// The key list consists of one or more TSIG keys, each entry described by
+/// a name, the algorithm method name, and its secret key component.
+///
+/// @todo NOTE that TSIG configuration parsing is functional, the use of
+/// TSIG Keys during the actual DNS update transactions is not. This will be
+/// implemented in a future release.
+///
+/// Each managed domain list consists of a list one or more domains and is
+/// represented by the class DdnsDomainListMgr.
+///
+/// Each domain consists of a set of scalars parameters and a list of DNS
+/// servers which support that domain. Among its scalars, is key_name, which
+/// is the name of the TSIG Key to use for with this domain. This value should
+/// map to one of the TSIG Keys in the key list. Domains are represented by
+/// the class, DdnsDomain.
+///
+/// Each server consists of a set of scalars used to describe the server such
+/// that the application can carry out DNS update exchanges with it. Servers
+/// are represented by the class, DnsServerInfo.
+///
+/// The configuration specification for use with BIND10 is detailed in the file
+/// dhcp-ddns.spec.
+///
+/// The parsing class hierarchy reflects this same scheme. Working top down:
+///
+/// A DdnsDomainListMgrParser parses a managed domain list entry. It handles
+/// any scalars which belong to the manager as well as creating and invoking a
+/// DdnsDomainListParser to parse its list of domain entries.
+///
+/// A DdnsDomainLiatParser creates and invokes DdnsDomainListParser for each
+/// domain entry in its list.
+///
+/// A DdnsDomainParser handles the scalars which belong to the domain as well as
+/// creating and invoking a DnsSeverInfoListParser to parse its list of server
+/// entries.
+///
+/// A DnsServerInfoListParser creates and invokes a DnsServerInfoParser for
+/// each server entry in its list.
+///
+/// A DdnsServerInfoParser handles the scalars which belong to the server.
+/// The following is sample configuration in JSON form with extra spacing
+/// for clarity:
+///
+/// @code
+/// {
+/// "interface" : "eth1" ,
+/// "ip_address" : "192.168.1.33" ,
+/// "port" : 88 ,
+/// "tsig_keys":
+//// [
+/// {
+/// "name": "d2_key.tmark.org" ,
+/// "algorithm": "md5" ,
+/// "secret": "0123456989"
+/// }
+/// ],
+/// "forward_ddns" :
+/// {
+/// "ddns_domains":
+/// [
+/// {
+/// "name": "tmark.org." ,
+/// "key_name": "d2_key.tmark.org" ,
+/// "dns_servers" :
+/// [
+/// { "hostname": "fserver.tmark.org" },
+/// { "hostname": "f2server.tmark.org" }
+/// ]
+/// },
+/// {
+/// "name": "pub.tmark.org." ,
+/// "key_name": "d2_key.tmark.org" ,
+/// "dns_servers" :
+/// [
+/// { "hostname": "f3server.tmark.org" }
+/// ]
+/// }
+/// ]
+/// },
+/// "reverse_ddns" :
+/// {
+/// "ddns_domains":
+/// [
+/// {
+/// "name": " 0.168.192.in.addr.arpa." ,
+/// "key_name": "d2_key.tmark.org" ,
+/// "dns_servers" :
+/// [
+/// { "ip_address": "127.0.0.101" , "port": 100 }
+/// ]
+/// }
+/// ]
+/// }
+/// }
+/// @endcode
+
+/// @brief Exception thrown when the error during configuration handling
+/// occurs.
+class D2CfgError : public isc::Exception {
+public:
+ D2CfgError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Represents a TSIG Key.
+///
+/// Currently, this is simple storage class containing the basic attributes of
+/// a TSIG Key. It is intended primarily as a reference for working with
+/// actual keys and may eventually be replaced by isc::dns::TSIGKey. TSIG Key
+/// functionality at this stage is strictly limited to configuration parsing.
+/// @todo full functionality for using TSIG during DNS updates will be added
+/// in a future release.
+class TSIGKeyInfo {
+public:
+
+ /// @brief Constructor
+ ///
+ /// @param name the unique label used to identify this key
+ /// @param algorithm the name of the encryption alogirthm this key uses.
+ /// (@todo This will be a fixed list of choices)
+ /// @param secret the secret component of this key
+ TSIGKeyInfo(const std::string& name, const std::string& algorithm,
+ const std::string& secret);
+
+ /// @brief Destructor
+ virtual ~TSIGKeyInfo();
+
+ /// @brief Getter which returns the key's name.
+ ///
+ /// @return returns the name as as std::string.
+ const std::string getName() const {
+ return (name_);
+ }
+
+ /// @brief Getter which returns the key's algorithm.
+ ///
+ /// @return returns the algorithm as as std::string.
+ const std::string getAlgorithm() const {
+ return (algorithm_);
+ }
+
+ /// @brief Getter which returns the key's secret.
+ ///
+ /// @return returns the secret as as std::string.
+ const std::string getSecret() const {
+ return (secret_);
+ }
+
+private:
+ /// @brief The name of the key.
+ ///
+ /// This value is the unique identifeir thay domains use to
+ /// to specify which TSIG key they need.
+ std::string name_;
+
+ /// @brief The algorithm that should be used for this key.
+ std::string algorithm_;
+
+ /// @brief The secret value component of this key.
+ std::string secret_;
+};
+
+/// @brief Defines a pointer for TSIGKeyInfo instances.
+typedef boost::shared_ptr<TSIGKeyInfo> TSIGKeyInfoPtr;
+
+/// @brief Defines a map of TSIGKeyInfos, keyed by the name.
+typedef std::map<std::string, TSIGKeyInfoPtr> TSIGKeyInfoMap;
+
+/// @brief Defines a iterator pairing of name and TSIGKeyInfo
+typedef std::pair<std::string, TSIGKeyInfoPtr> TSIGKeyInfoMapPair;
+
+/// @brief Defines a pointer to map of TSIGkeyInfos
+typedef boost::shared_ptr<TSIGKeyInfoMap> TSIGKeyInfoMapPtr;
+
+
+/// @brief Represents a specific DNS Server.
+/// It provides information about the server's network identity and typically
+/// belongs to a list of servers supporting DNS for a given domain. It will
+/// be used to establish communications with the server to carry out DNS
+/// updates.
+class DnsServerInfo {
+public:
+
+ /// @brief defines DNS standard port value
+ static const uint32_t STANDARD_DNS_PORT = 53;
+
+ /// @brief defines an "empty" string version of an ip address.
+ static const char* EMPTY_IP_STR;
+
+
+ /// @brief Constructor
+ ///
+ /// @param hostname is the resolvable name of the server. If not blank,
+ /// then the server address should be resolved at runtime.
+ /// @param ip_address is the static IP address of the server. If hostname
+ /// is blank, then this address should be used to connect to the server.
+ /// @param port is the port number on which the server listens.
+ /// primarily meant for testing purposes. Normally, DNS traffic is on
+ /// is port 53. (NOTE the constructing code is responsible for setting
+ /// the default.)
+ /// @param enabled is a flag that indicates whether this server is
+ /// enabled for use. It defaults to true.
+ DnsServerInfo(const std::string& hostname,
+ isc::asiolink::IOAddress ip_address,
+ uint32_t port = STANDARD_DNS_PORT,
+ bool enabled=true);
+
+ /// @brief Destructor
+ virtual ~DnsServerInfo();
+
+ /// @brief Getter which returns the server's hostname.
+ ///
+ /// @return returns the hostname as as std::string.
+ const std::string getHostname() const {
+ return (hostname_);
+ }
+
+ /// @brief Getter which returns the server's port number.
+ ///
+ /// @return returns the port number as a unsigned integer.
+ uint32_t getPort() const {
+ return (port_);
+ }
+
+ /// @brief Getter which returns the server's ip_address.
+ ///
+ /// @return returns the address as an IOAddress reference.
+ const isc::asiolink::IOAddress& getIpAddress() const {
+ return (ip_address_);
+ }
+
+ /// @brief Convenience method which returns whether or not the
+ /// server is enabled.
+ ///
+ /// @return returns true if the server is enabled, false otherwise.
+ bool isEnabled() const {
+ return (enabled_);
+ }
+
+ /// @brief Sets the server's enabled flag to true.
+ void enable() {
+ enabled_ = true;
+ }
+
+ /// @brief Sets the server's enabled flag to false.
+ void disable() {
+ enabled_ = false;
+ }
+
+
+private:
+ /// @brief The resolvable name of the server. If not blank, then the
+ /// server's IP address should be dynamically resolved at runtime.
+ std::string hostname_;
+
+ /// @brief The static IP address of the server. When hostname is blank,
+ /// then this address should be used to connect to the server.
+ isc::asiolink::IOAddress ip_address_;
+
+ /// @brief The port number on which the server listens for DNS traffic.
+ uint32_t port_;
+
+ /// @param enabled is a flag that indicates whether this server is
+ /// enabled for use. It defaults to true.
+ bool enabled_;
+};
+
+/// @brief Defines a pointer for DnsServerInfo instances.
+typedef boost::shared_ptr<DnsServerInfo> DnsServerInfoPtr;
+
+/// @brief Defines a storage container for DnsServerInfo pointers.
+typedef std::vector<DnsServerInfoPtr> DnsServerInfoStorage;
+
+/// @brief Defines a pointer to DnsServerInfo storage containers.
+typedef boost::shared_ptr<DnsServerInfoStorage> DnsServerInfoStoragePtr;
+
+
+/// @brief Represents a DNS domain that is may be updated dynamically.
+/// This class specifies a DNS domain and the list of DNS servers that support
+/// it. It's primary use is to map a domain to the DNS server(s) responsible
+/// for it.
+/// @todo Currently the name entry for a domain is just an std::string. It
+/// may be worthwhile to change this to a dns::Name for purposes of better
+/// validation and matching capabilities.
+class DdnsDomain {
+public:
+ /// @brief Constructor
+ ///
+ /// @param name is the domain name of the domain.
+ /// @param key_name is the TSIG key name for use with this domain.
+ /// @param servers is the list of server(s) supporting this domain.
+ DdnsDomain(const std::string& name, const std::string& key_name,
+ DnsServerInfoStoragePtr servers);
+
+ /// @brief Destructor
+ virtual ~DdnsDomain();
+
+ /// @brief Getter which returns the domain's name.
+ ///
+ /// @return returns the name in an std::string.
+ const std::string getName() const {
+ return (name_);
+ }
+
+ /// @brief Getter which returns the domain's TSIG key name.
+ ///
+ /// @return returns the key name in an std::string.
+ const std::string getKeyName() const {
+ return (key_name_);
+ }
+
+ /// @brief Getter which returns the domain's list of servers.
+ ///
+ /// @return returns the pointer to the server storage.
+ const DnsServerInfoStoragePtr& getServers() {
+ return (servers_);
+ }
+
+private:
+ /// @brief The domain name of the domain.
+ std::string name_;
+
+ /// @brief The name of the TSIG key for use with this domain.
+ std::string key_name_;
+
+ /// @brief The list of server(s) supporting this domain.
+ DnsServerInfoStoragePtr servers_;
+};
+
+/// @brief Defines a pointer for DdnsDomain instances.
+typedef boost::shared_ptr<DdnsDomain> DdnsDomainPtr;
+
+/// @brief Defines a map of DdnsDomains, keyed by the domain name.
+typedef std::map<std::string, DdnsDomainPtr> DdnsDomainMap;
+
+/// @brief Defines a iterator pairing domain name and DdnsDomain
+typedef std::pair<std::string, DdnsDomainPtr> DdnsDomainMapPair;
+
+/// @brief Defines a pointer to DdnsDomain storage containers.
+typedef boost::shared_ptr<DdnsDomainMap> DdnsDomainMapPtr;
+
+/// @brief Provides storage for and management of a list of DNS domains.
+/// In addition to housing the domain list storage, it provides domain matching
+/// services. These services are used to match a FQDN to a domain. Currently
+/// it supports a single matching service, which will return the matching
+/// domain or a wild card domain if one is specified. The wild card domain is
+/// specified as a domain whose name is "*".
+/// As matching capabilities evolve this class is expected to expand.
+class DdnsDomainListMgr {
+public:
+ /// @brief defines the domain name for denoting the wildcard domain.
+ static const char* wildcard_domain_name_;
+
+ /// @brief Constructor
+ ///
+ /// @param name is an arbitrary label assigned to this manager.
+ DdnsDomainListMgr(const std::string& name);
+
+ /// @brief Destructor
+ virtual ~DdnsDomainListMgr ();
+
+ /// @brief Matches a given name to a domain based on a longest match
+ /// scheme.
+ ///
+ /// Given a FQDN, search the list of domains, successively removing a
+ /// sub-domain from the FQDN until a match is found. If no match is found
+ /// and the wild card domain is present in the list, then return it as the
+ /// match. If the wild card domain is the only domain in the list, then
+ /// it will be returned immediately for any FQDN.
+ ///
+ /// @param fqdn is the name for which to look.
+ /// @param domain receives the matching domain. Note that it will be reset
+ /// upon entry and only set if a match is subsequently found.
+ ///
+ /// @return returns true if a match is found, false otherwise.
+ /// @todo This is a very basic match method, which expects valid FQDNs
+ /// both as input and for the DdnsDomain::getName(). Currently both are
+ /// simple strings and there is no normalization (i.e. added trailing dots
+ /// if missing).
+ virtual bool matchDomain(const std::string& fqdn, DdnsDomainPtr& domain);
+
+ /// @brief Fetches the manager's name.
+ ///
+ /// @return returns a std::string containing the name of the manager.
+ const std::string getName() const {
+ return (name_);
+ }
+
+ /// @brief Returns the number of domains in the domain list.
+ ///
+ /// @brief returns an unsigned int containing the domain count.
+ uint32_t size() const {
+ return (domains_->size());
+ }
+
+ /// @brief Fetches the wild card domain.
+ ///
+ /// @return returns a pointer reference to the domain. The pointer will
+ /// empty if the wild card domain is not present.
+ const DdnsDomainPtr& getWildcardDomain() {
+ return (wildcard_domain_);
+ }
+
+ /// @brief Fetches the domain list.
+ ///
+ /// @return returns a pointer reference to the list of domains.
+ const DdnsDomainMapPtr &getDomains() {
+ return (domains_);
+ }
+
+ /// @brief Sets the manger's domain list to the given list of domains.
+ /// This method will scan the inbound list for the wild card domain and
+ /// set the internal wild card domain pointer accordingly.
+ void setDomains(DdnsDomainMapPtr domains);
+
+private:
+ /// @brief An arbitrary label assigned to this manager.
+ std::string name_;
+
+ /// @brief Map of the domains, keyed by name.
+ DdnsDomainMapPtr domains_;
+
+ /// @brief Pointer to the wild card domain.
+ DdnsDomainPtr wildcard_domain_;
+};
+
+/// @brief Defines a pointer for DdnsDomain instances.
+typedef boost::shared_ptr<DdnsDomainListMgr> DdnsDomainListMgrPtr;
+
+/// @brief Storage container for scalar configuration parameters.
+///
+/// This class is useful for implementing parsers for more complex configuration
+/// elements (e.g. those of item type "map"). It provides a convenient way to
+/// add storage to the parser for an arbitrary number and variety of scalar
+/// configuration items (e.g. ints, bools, strings...) without explicitly adding
+/// storage for each individual type needed by the parser.
+///
+/// This class implements a concrete version of the base class by supplying a
+/// "clone" method.
+class DScalarContext : public DCfgContextBase {
+public:
+
+ /// @brief Constructor
+ DScalarContext() {
+ };
+
+ /// @brief Destructor
+ virtual ~DScalarContext() {
+ }
+
+ /// @brief Creates a clone of a DStubContext.
+ ///
+ /// @return returns a pointer to the new clone.
+ virtual DCfgContextBasePtr clone() {
+ return (DCfgContextBasePtr(new DScalarContext(*this)));
+ }
+
+protected:
+ /// @brief Copy constructor
+ DScalarContext(const DScalarContext& rhs) : DCfgContextBase(rhs) {
+ }
+
+private:
+ /// @brief Private assignment operator, not implemented.
+ DScalarContext& operator=(const DScalarContext& rhs);
+};
+
+/// @brief Defines a pointer for DScalarContext instances.
+typedef boost::shared_ptr<DScalarContext> DScalarContextPtr;
+
+/// @brief Parser for TSIGKeyInfo
+///
+/// This class parses the configuration element "tsig_key" defined in
+/// src/bin/d2/dhcp-ddns.spec and creates an instance of a TSIGKeyInfo.
+class TSIGKeyInfoParser : public isc::dhcp::DhcpConfigParser {
+public:
+ /// @brief Constructor
+ ///
+ /// @param entry_name is an arbitrary label assigned to this configuration
+ /// definition. Since servers are specified in a list this value is likely
+ /// be something akin to "key:0", set during parsing.
+ /// @param keys is a pointer to the storage area to which the parser
+ /// should commit the newly created TSIGKeyInfo instance.
+ TSIGKeyInfoParser(const std::string& entry_name, TSIGKeyInfoMapPtr keys);
+
+ /// @brief Destructor
+ virtual ~TSIGKeyInfoParser();
+
+ /// @brief Performs the actual parsing of the given "tsig_key" element.
+ ///
+ /// The results of the parsing are retained internally for use during
+ /// commit.
+ ///
+ /// @param key_config is the "tsig_key" configuration to parse
+ virtual void build(isc::data::ConstElementPtr key_config);
+
+ /// @brief Creates a parser for the given "tsig_key" member element id.
+ ///
+ /// The key elements currently supported are(see dhcp-ddns.spec):
+ /// 1. name
+ /// 2. algorithm
+ /// 3. secret
+ ///
+ /// @param config_id is the "item_name" for a specific member element of
+ /// the "tsig_key" specification.
+ ///
+ /// @return returns a pointer to newly created parser.
+ virtual isc::dhcp::ParserPtr createConfigParser(const std::string&
+ config_id);
+
+ /// @brief Instantiates a DnsServerInfo from internal data values
+ /// saves it to the storage area pointed to by servers_.
+ virtual void commit();
+
+private:
+ /// @brief Arbitrary label assigned to this parser instance.
+ /// Since servers are specified in a list this value is likely be something
+ /// akin to "key:0", set during parsing. Primarily here for diagnostics.
+ std::string entry_name_;
+
+ /// @brief Pointer to the storage area to which the parser should commit
+ /// the newly created TSIGKeyInfo instance. This is given to us as a
+ /// constructor argument by an upper level.
+ TSIGKeyInfoMapPtr keys_;
+
+ /// @brief Local storage area for scalar parameter values. Use to hold
+ /// data until time to commit.
+ DScalarContext local_scalars_;
+};
+
+/// @brief Parser for a list of TSIGKeyInfos
+///
+/// This class parses a list of "tsig_key" configuration elements.
+/// (see src/bin/d2/dhcp-ddns.spec). The TSIGKeyInfo instances are added
+/// to the given storage upon commit.
+class TSIGKeyInfoListParser : public isc::dhcp::DhcpConfigParser {
+public:
+
+ /// @brief Constructor
+ ///
+ /// @param list_name is an arbitrary label assigned to this parser instance.
+ /// @param keys is a pointer to the storage area to which the parser
+ /// should commit the newly created TSIGKeyInfo instance.
+ TSIGKeyInfoListParser(const std::string& list_name, TSIGKeyInfoMapPtr keys);
+
+ /// @brief Destructor
+ virtual ~TSIGKeyInfoListParser();
+
+ /// @brief Performs the parsing of the given list "tsig_key" elements.
+ ///
+ /// It iterates over each key entry in the list:
+ /// 1. Instantiate a TSIGKeyInfoParser for the entry
+ /// 2. Pass the element configuration to the parser's build method
+ /// 3. Add the parser instance to local storage
+ ///
+ /// The net effect is to parse all of the key entries in the list
+ /// prepping them for commit.
+ ///
+ /// @param key_list_config is the list of "tsig_key" elements to parse.
+ virtual void build(isc::data::ConstElementPtr key_list_config);
+
+ /// @brief Iterates over the internal list of TSIGKeyInfoParsers,
+ /// invoking commit on each. This causes each parser to instantiate a
+ /// TSIGKeyInfo from its internal data values and add that that key
+ /// instance to the storage area, keys_.
+ virtual void commit();
+
+private:
+ /// @brief Arbitrary label assigned to this parser instance.
+ std::string list_name_;
+
+ /// @brief Pointer to the storage area to which the parser should commit
+ /// the list of newly created TSIGKeyInfo instances. This is given to us
+ /// as a constructor argument by an upper level.
+ TSIGKeyInfoMapPtr keys_;
+
+ /// @brief Local storage of TSIGKeyInfoParser instances
+ isc::dhcp::ParserCollection parsers_;
+};
+
+/// @brief Parser for DnsServerInfo
+///
+/// This class parses the configuration element "dns_server" defined in
+/// src/bin/d2/dhcp-ddns.spec and creates an instance of a DnsServerInfo.
+class DnsServerInfoParser : public isc::dhcp::DhcpConfigParser {
+public:
+ /// @brief Constructor
+ ///
+ /// @param entry_name is an arbitrary label assigned to this configuration
+ /// definition. Since servers are specified in a list this value is likely
+ /// be something akin to "server:0", set during parsing.
+ /// @param servers is a pointer to the storage area to which the parser
+ /// should commit the newly created DnsServerInfo instance.
+ DnsServerInfoParser(const std::string& entry_name,
+ DnsServerInfoStoragePtr servers);
+
+ /// @brief Destructor
+ virtual ~DnsServerInfoParser();
+
+ /// @brief Performs the actual parsing of the given "dns_server" element.
+ /// The results of the parsing are retained internally for use during
+ /// commit.
+ ///
+ /// @param server_config is the "dns_server" configuration to parse
+ virtual void build(isc::data::ConstElementPtr server_config);
+
+ /// @brief Creates a parser for the given "dns_server" member element id.
+ ///
+ /// The server elements currently supported are(see dhcp-ddns.spec):
+ /// 1. hostname
+ /// 2. ip_address
+ /// 3. port
+ ///
+ /// @param config_id is the "item_name" for a specific member element of
+ /// the "dns_server" specification.
+ ///
+ /// @return returns a pointer to newly created parser.
+ virtual isc::dhcp::ParserPtr createConfigParser(const std::string&
+ config_id);
+
+ /// @brief Instantiates a DnsServerInfo from internal data values
+ /// saves it to the storage area pointed to by servers_.
+ virtual void commit();
+
+private:
+ /// @brief Arbitrary label assigned to this parser instance.
+ /// Since servers are specified in a list this value is likely be something
+ /// akin to "server:0", set during parsing. Primarily here for diagnostics.
+ std::string entry_name_;
+
+ /// @brief Pointer to the storage area to which the parser should commit
+ /// the newly created DnsServerInfo instance. This is given to us as a
+ /// constructor argument by an upper level.
+ DnsServerInfoStoragePtr servers_;
+
+ /// @brief Local storage area for scalar parameter values. Use to hold
+ /// data until time to commit.
+ DScalarContext local_scalars_;
+};
+
+/// @brief Parser for a list of DnsServerInfos
+///
+/// This class parses a list of "dns_server" configuration elements.
+/// (see src/bin/d2/dhcp-ddns.spec). The DnsServerInfo instances are added
+/// to the given storage upon commit.
+class DnsServerInfoListParser : public isc::dhcp::DhcpConfigParser {
+public:
+
+ /// @brief Constructor
+ ///
+ /// @param list_name is an arbitrary label assigned to this parser instance.
+ /// @param servers is a pointer to the storage area to which the parser
+ /// should commit the newly created DnsServerInfo instance.
+ DnsServerInfoListParser(const std::string& list_name,
+ DnsServerInfoStoragePtr servers);
+
+ /// @brief Destructor
+ virtual ~DnsServerInfoListParser();
+
+ /// @brief Performs the actual parsing of the given list "dns_server"
+ /// elements.
+ /// It iterates over each server entry in the list:
+ /// 1. Instantiate a DnsServerInfoParser for the entry
+ /// 2. Pass the element configuration to the parser's build method
+ /// 3. Add the parser instance to local storage
+ ///
+ /// The net effect is to parse all of the server entries in the list
+ /// prepping them for commit.
+ ///
+ /// @param server_list_config is the list of "dns_server" elements to parse.
+ virtual void build(isc::data::ConstElementPtr server_list_config);
+
+ /// @brief Iterates over the internal list of DnsServerInfoParsers,
+ /// invoking commit on each. This causes each parser to instantiate a
+ /// DnsServerInfo from its internal data values and add that that server
+ /// instance to the storage area, servers_.
+ virtual void commit();
+
+private:
+ /// @brief Arbitrary label assigned to this parser instance.
+ std::string list_name_;
+
+ /// @brief Pointer to the storage area to which the parser should commit
+ /// the list of newly created DnsServerInfo instances. This is given to us
+ /// as a constructor argument by an upper level.
+ DnsServerInfoStoragePtr servers_;
+
+ /// @brief Local storage of DnsServerInfoParser instances
+ isc::dhcp::ParserCollection parsers_;
+};
+
+/// @brief Parser for DdnsDomain
+///
+/// This class parses the configuration element "ddns_domain" defined in
+/// src/bin/d2/dhcp-ddns.spec and creates an instance of a DdnsDomain.
+class DdnsDomainParser : public isc::dhcp::DhcpConfigParser {
+public:
+ /// @brief Constructor
+ ///
+ /// @param entry_name is an arbitrary label assigned to this configuration
+ /// definition. Since domains are specified in a list this value is likely
+ /// be something akin to "forward_ddns:0", set during parsing.
+ /// @param domains is a pointer to the storage area to which the parser
+ /// @param keys is a pointer to a map of the defined TSIG keys.
+ /// should commit the newly created DdnsDomain instance.
+ DdnsDomainParser(const std::string& entry_name, DdnsDomainMapPtr domains,
+ TSIGKeyInfoMapPtr keys);
+
+ /// @brief Destructor
+ virtual ~DdnsDomainParser();
+
+ /// @brief Performs the actual parsing of the given "ddns_domain" element.
+ /// The results of the parsing are retained internally for use during
+ /// commit.
+ ///
+ /// @param domain_config is the "ddns_domain" configuration to parse
+ virtual void build(isc::data::ConstElementPtr domain_config);
+
+ /// @brief Creates a parser for the given "ddns_domain" member element id.
+ ///
+ /// The domain elements currently supported are(see dhcp-ddns.spec):
+ /// 1. name
+ /// 2. key_name
+ /// 3. dns_servers
+ ///
+ /// @param config_id is the "item_name" for a specific member element of
+ /// the "ddns_domain" specification.
+ ///
+ /// @return returns a pointer to newly created parser.
+ virtual isc::dhcp::ParserPtr createConfigParser(const std::string&
+ config_id);
+
+ /// @brief Instantiates a DdnsDomain from internal data values
+ /// saves it to the storage area pointed to by domains_.
+ virtual void commit();
+
+private:
+
+ /// @brief Arbitrary label assigned to this parser instance.
+ std::string entry_name_;
+
+ /// @brief Pointer to the storage area to which the parser should commit
+ /// the newly created DdnsDomain instance. This is given to us as a
+ /// constructor argument by an upper level.
+ DdnsDomainMapPtr domains_;
+
+ /// @brief Pointer to the map of defined TSIG keys.
+ /// This map is passed into us and contains all of the TSIG keys defined
+ /// for this configuration. It is used to validate the key name entry of
+ /// DdnsDomains that specify one.
+ TSIGKeyInfoMapPtr keys_;
+
+ /// @brief Local storage for DnsServerInfo instances. This is passed into
+ /// DnsServerInfoListParser(s), which in turn passes it into each
+ /// DnsServerInfoParser. When the DnsServerInfoParsers "commit" they add
+ /// their server instance to this storage.
+ DnsServerInfoStoragePtr local_servers_;
+
+ /// @brief Local storage area for scalar parameter values. Use to hold
+ /// data until time to commit.
+ DScalarContext local_scalars_;
+};
+
+/// @brief Parser for a list of DdnsDomains
+///
+/// This class parses a list of "ddns_domain" configuration elements.
+/// (see src/bin/d2/dhcp-ddns.spec). The DdnsDomain instances are added
+/// to the given storage upon commit.
+class DdnsDomainListParser : public isc::dhcp::DhcpConfigParser {
+public:
+
+ /// @brief Constructor
+ ///
+ /// @param list_name is an arbitrary label assigned to this parser instance.
+ /// @param domains is a pointer to the storage area to which the parser
+ /// @param keys is a pointer to a map of the defined TSIG keys.
+ /// should commit the newly created DdnsDomain instance.
+ DdnsDomainListParser(const std::string& list_name,
+ DdnsDomainMapPtr domains, TSIGKeyInfoMapPtr keys);
+
+ /// @brief Destructor
+ virtual ~DdnsDomainListParser();
+
+ /// @brief Performs the actual parsing of the given list "ddns_domain"
+ /// elements.
+ /// It iterates over each server entry in the list:
+ /// 1. Instantiate a DdnsDomainParser for the entry
+ /// 2. Pass the element configuration to the parser's build method
+ /// 3. Add the parser instance to local storage
+ ///
+ /// The net effect is to parse all of the domain entries in the list
+ /// prepping them for commit.
+ ///
+ /// @param domain_list_config is the list of "ddns_domain" elements to
+ /// parse.
+ virtual void build(isc::data::ConstElementPtr domain_list_config);
+
+ /// @brief Iterates over the internal list of DdnsDomainParsers, invoking
+ /// commit on each. This causes each parser to instantiate a DdnsDomain
+ /// from its internal data values and add that domain instance to the
+ /// storage area, domains_.
+ virtual void commit();
+
+private:
+ /// @brief Arbitrary label assigned to this parser instance.
+ std::string list_name_;
+
+ /// @brief Pointer to the storage area to which the parser should commit
+ /// the list of newly created DdnsDomain instances. This is given to us
+ /// as a constructor argument by an upper level.
+ DdnsDomainMapPtr domains_;
+
+ /// @brief Pointer to the map of defined TSIG keys.
+ /// This map is passed into us and contains all of the TSIG keys defined
+ /// for this configuration. It is used to validate the key name entry of
+ /// DdnsDomains that specify one.
+ TSIGKeyInfoMapPtr keys_;
+
+ /// @brief Local storage of DdnsDomainParser instances
+ isc::dhcp::ParserCollection parsers_;
+};
+
+/// @brief Parser for DdnsDomainListMgr
+///
+/// This class parses the configuration elements "forward_ddns" and
+/// "reverse_ddns" as defined in src/bin/d2/dhcp-ddns.spec. It populates the
+/// given DdnsDomainListMgr with parsed information upon commit. Note that
+/// unlike other parsers, this parser does NOT instantiate the final object
+/// during the commit phase, it populates it. It must pre-exist.
+class DdnsDomainListMgrParser : public isc::dhcp::DhcpConfigParser {
+public:
+ /// @brief Constructor
+ ///
+ /// @param entry_name is an arbitrary label assigned to this configuration
+ /// definition.
+ /// @param mgr is a pointer to the DdnsDomainListMgr to populate.
+ /// @param keys is a pointer to a map of the defined TSIG keys.
+ /// @throw throws D2CfgError if mgr pointer is empty.
+ DdnsDomainListMgrParser(const std::string& entry_name,
+ DdnsDomainListMgrPtr mgr, TSIGKeyInfoMapPtr keys);
+
+ /// @brief Destructor
+ virtual ~DdnsDomainListMgrParser();
+
+ /// @brief Performs the actual parsing of the given manager element.
+ /// The results of the parsing are retained internally for use during
+ /// commit.
+ ///
+ /// @param mgr_config is the manager configuration to parse
+ virtual void build(isc::data::ConstElementPtr mgr_config);
+
+ /// @brief Creates a parser for the given manager member element id.
+ ///
+ /// The manager elements currently supported are (see dhcp-ddns.spec):
+ /// 1. ddns_domains
+ ///
+ /// @param config_id is the "item_name" for a specific member element of
+ /// the manager specification.
+ ///
+ /// @return returns a pointer to newly created parser.
+ virtual isc::dhcp::ParserPtr createConfigParser(const std::string&
+ config_id);
+
+ /// @brief Populates the DdnsDomainListMgr from internal data values
+ /// set during parsing.
+ virtual void commit();
+
+private:
+ /// @brief Arbitrary label assigned to this parser instance.
+ std::string entry_name_;
+
+ /// @brief Pointer to manager instance to which the parser should commit
+ /// the parsed data. This is given to us as a constructor argument by an
+ /// upper level.
+ DdnsDomainListMgrPtr mgr_;
+
+ /// @brief Pointer to the map of defined TSIG keys.
+ /// This map is passed into us and contains all of the TSIG keys defined
+ /// for this configuration. It is used to validate the key name entry of
+ /// DdnsDomains that specify one.
+ TSIGKeyInfoMapPtr keys_;
+
+ /// @brief Local storage for DdnsDomain instances. This is passed into a
+ /// DdnsDomainListParser(s), which in turn passes it into each
+ /// DdnsDomainParser. When the DdnsDomainParsers "commit" they add their
+ /// domain instance to this storage.
+ DdnsDomainMapPtr local_domains_;
+
+ /// @brief Local storage area for scalar parameter values. Use to hold
+ /// data until time to commit.
+ /// @todo Currently, the manager has no scalars but this is likely to
+ /// change as matching capabilities expand.
+ DScalarContext local_scalars_;
+};
+
+
+}; // end of isc::d2 namespace
+}; // end of isc namespace
+
+#endif // D2_CONFIG_H
diff --git a/src/bin/d2/d2_controller.h b/src/bin/d2/d2_controller.h
index 5ee15b1..0290f87 100644
--- a/src/bin/d2/d2_controller.h
+++ b/src/bin/d2/d2_controller.h
@@ -24,7 +24,7 @@ namespace d2 {
/// This class is the DHCP-DDNS specific derivation of DControllerBase. It
/// creates and manages an instance of the DHCP-DDNS application process,
/// D2Process.
-/// @TODO Currently, this class provides only the minimum required specialized
+/// @todo Currently, this class provides only the minimum required specialized
/// behavior to run the DHCP-DDNS service. It may very well expand as the
/// service implementation evolves. Some thought was given to making
/// DControllerBase a templated class but the labor savings versus the
diff --git a/src/bin/d2/d2_messages.mes b/src/bin/d2/d2_messages.mes
index 27778dc..d2358ce 100644
--- a/src/bin/d2/d2_messages.mes
+++ b/src/bin/d2/d2_messages.mes
@@ -26,11 +26,22 @@ to establish a session with the BIND10 control channel.
A debug message listing the command (and possible arguments) received
from the BIND10 control system by the controller.
+% DCTL_CONFIG_COMPLETE server has completed configuration: %1
+This is an informational message announcing the successful processing of a
+new configuration. It is output during server startup, and when an updated
+configuration is committed by the administrator. Additional information
+may be provided.
+
% DCTL_CONFIG_LOAD_FAIL %1 configuration failed to load: %2
This critical error message indicates that the initial application
configuration has failed. The service will start, but will not
process requests until the configuration has been corrected.
+% DCTL_CONFIG_START parsing new configuration: %1
+A debug message indicating that the application process has received an
+updated configuration and has passed it to its configuration manager
+for parsing.
+
% DCTL_CONFIG_STUB %1 configuration stub handler called
This debug message is issued when the dummy handler for configuration
events is called. This only happens during initial startup.
@@ -55,7 +66,24 @@ application and will exit.
% DCTL_NOT_RUNNING %1 application instance is not running
A warning message is issued when an attempt is made to shut down the
-the application when it is not running.
+application when it is not running.
+
+% DCTL_ORDER_ERROR configuration contains more elements than the parsing order
+An error message which indicates that configuration being parsed includes
+element ids not specified the configuration manager's parse order list. This
+is a programmatic error.
+
+% DCTL_ORDER_NO_ELEMENT element: %1 is in the parsing order but is missing from the configuration
+An error message output during a configuration update. The program is
+expecting an item but has not found it in the new configuration. This may
+mean that the BIND 10 configuration database is corrupt.
+
+% DCTL_PARSER_FAIL configuration parsing failed for configuration element: %1, reason: %2
+On receipt of message containing details to a change of its configuration,
+the server failed to create a parser to decode the contents of the named
+configuration element, or the creation succeeded but the parsing actions
+and committal of changes failed. The reason for the failure is given in
+the message.
% DCTL_PROCESS_FAILED %1 application execution failed: %2
The controller has encountered a fatal error while running the
@@ -96,6 +124,16 @@ has been invoked.
This is a debug message issued when the Dhcp-Ddns application encounters an
unrecoverable error from within the event loop.
+% DHCP_DDNS_NO_MATCH No DNS servers match FQDN: %1
+This is warning message issued when there are no domains in the configuration
+which match the cited fully qualified domain name (FQDN). The DNS Update
+request for the FQDN cannot be processed.
+
+% DHCP_DDNS_INVALID_RESPONSE received response to DNS Update message is malformed: %1
+This is a debug message issued when the DHCP-DDNS application encountered an error
+while decoding a response to DNS Update message. Typically, this error will be
+encountered when a response message is malformed.
+
% DHCP_DDNS_PROCESS_INIT application init invoked
This is a debug message issued when the Dhcp-Ddns application enters
its init method.
diff --git a/src/bin/d2/d2_process.cc b/src/bin/d2/d2_process.cc
index 130bcc1..74cb93c 100644
--- a/src/bin/d2/d2_process.cc
+++ b/src/bin/d2/d2_process.cc
@@ -14,6 +14,7 @@
#include <config/ccsession.h>
#include <d2/d2_log.h>
+#include <d2/d2_cfg_mgr.h>
#include <d2/d2_process.h>
using namespace asio;
@@ -22,7 +23,7 @@ namespace isc {
namespace d2 {
D2Process::D2Process(const char* name, IOServicePtr io_service)
- : DProcessBase(name, io_service) {
+ : DProcessBase(name, io_service, DCfgMgrBasePtr(new D2CfgMgr())) {
};
void
@@ -60,23 +61,23 @@ D2Process::shutdown() {
isc::data::ConstElementPtr
D2Process::configure(isc::data::ConstElementPtr config_set) {
- // @TODO This is the initial implementation which simply accepts
- // any content in config_set as valid. This is sufficient to
- // allow participation as a BIND10 module, while D2 configuration support
- // is being developed.
+ // @todo This is the initial implementation passes the configuration onto
+ // the D2CfgMgr. There may be additional steps taken added to handle
+ // configuration changes but for now, assume that D2CfgMgr is handling it
+ // all.
LOG_DEBUG(dctl_logger, DBGLVL_TRACE_BASIC,
DHCP_DDNS_CONFIGURE).arg(config_set->str());
- return (isc::config::createAnswer(0, "Configuration accepted."));
+ return (getCfgMgr()->parseConfig(config_set));
}
isc::data::ConstElementPtr
D2Process::command(const std::string& command, isc::data::ConstElementPtr args){
- // @TODO This is the initial implementation. If and when D2 is extended
+ // @todo This is the initial implementation. If and when D2 is extended
// to support its own commands, this implementation must change. Otherwise
// it should reject all commands as it does now.
- LOG_DEBUG(dctl_logger, DBGLVL_TRACE_BASIC,
- DHCP_DDNS_COMMAND).arg(command).arg(args->str());
+ LOG_DEBUG(dctl_logger, DBGLVL_TRACE_BASIC, DHCP_DDNS_COMMAND)
+ .arg(command).arg(args ? args->str() : "(no args)");
return (isc::config::createAnswer(COMMAND_INVALID, "Unrecognized command: "
+ command));
diff --git a/src/bin/d2/d2_process.h b/src/bin/d2/d2_process.h
index 4ddf8be..6f50f0c 100644
--- a/src/bin/d2/d2_process.h
+++ b/src/bin/d2/d2_process.h
@@ -41,7 +41,7 @@ public:
D2Process(const char* name, IOServicePtr io_service);
/// @brief Will be used after instantiation to perform initialization
- /// unique to D2. @TODO This will likely include interactions with
+ /// unique to D2. @todo This will likely include interactions with
/// QueueMgr and UpdateMgr, to prepare for request receipt and processing.
/// Current implementation successfully does nothing.
/// @throw throws a DProcessBaseError if the initialization fails.
@@ -58,7 +58,7 @@ public:
/// @brief Implements the process's shutdown processing. When invoked, it
/// should ensure that the process gracefully exits the run method.
/// Current implementation simply sets the shutdown flag monitored by the
- /// run method. @TODO this may need to expand as the implementation evolves.
+ /// run method. @todo this may need to expand as the implementation evolves.
/// @throw throws a DProcessBaseError if an error is encountered.
virtual void shutdown();
@@ -85,7 +85,7 @@ public:
///
/// @param command is a string label representing the command to execute.
/// @param args is a set of arguments (if any) required for the given
- /// command.
+ /// command. It can be a NULL pointer if no arguments exist for a command.
/// @return an Element that contains the results of command composed
/// of an integer status value (0 means successful, non-zero means failure),
/// and a string explanation of the outcome.
diff --git a/src/bin/d2/d2_update_message.h b/src/bin/d2/d2_update_message.h
index 98e98a8..955e5c0 100644
--- a/src/bin/d2/d2_update_message.h
+++ b/src/bin/d2/d2_update_message.h
@@ -63,6 +63,10 @@ public:
isc::Exception(file, line, what) {}
};
+class D2UpdateMessage;
+
+/// @brief Pointer to the DNS Update Message.
+typedef boost::shared_ptr<D2UpdateMessage> D2UpdateMessagePtr;
/// @brief The @c D2UpdateMessage encapsulates a DNS Update message.
///
diff --git a/src/bin/d2/d_cfg_mgr.cc b/src/bin/d2/d_cfg_mgr.cc
new file mode 100644
index 0000000..1e6bb57
--- /dev/null
+++ b/src/bin/d2/d_cfg_mgr.cc
@@ -0,0 +1,240 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config/ccsession.h>
+#include <d2/d2_log.h>
+#include <dhcp/libdhcp++.h>
+#include <d2/d_cfg_mgr.h>
+#include <dhcpsrv/dhcp_parsers.h>
+#include <util/encode/hex.h>
+#include <util/strutil.h>
+
+#include <boost/foreach.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/algorithm/string.hpp>
+
+#include <limits>
+#include <iostream>
+#include <vector>
+#include <map>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::data;
+using namespace isc::asiolink;
+
+namespace isc {
+namespace d2 {
+
+// *********************** DCfgContextBase *************************
+
+DCfgContextBase::DCfgContextBase():
+ boolean_values_(new BooleanStorage()),
+ uint32_values_(new Uint32Storage()),
+ string_values_(new StringStorage()) {
+ }
+
+DCfgContextBase::DCfgContextBase(const DCfgContextBase& rhs):
+ boolean_values_(new BooleanStorage(*(rhs.boolean_values_))),
+ uint32_values_(new Uint32Storage(*(rhs.uint32_values_))),
+ string_values_(new StringStorage(*(rhs.string_values_))) {
+}
+
+void
+DCfgContextBase::getParam(const std::string& name, bool& value, bool optional) {
+ try {
+ value = boolean_values_->getParam(name);
+ } catch (DhcpConfigError& ex) {
+ // If the parameter is not optional, re-throw the exception.
+ if (!optional) {
+ throw;
+ }
+ }
+}
+
+
+void
+DCfgContextBase::getParam(const std::string& name, uint32_t& value,
+ bool optional) {
+ try {
+ value = uint32_values_->getParam(name);
+ } catch (DhcpConfigError& ex) {
+ // If the parameter is not optional, re-throw the exception.
+ if (!optional) {
+ throw;
+ }
+ }
+}
+
+void
+DCfgContextBase::getParam(const std::string& name, std::string& value,
+ bool optional) {
+ try {
+ value = string_values_->getParam(name);
+ } catch (DhcpConfigError& ex) {
+ // If the parameter is not optional, re-throw the exception.
+ if (!optional) {
+ throw;
+ }
+ }
+}
+
+DCfgContextBase::~DCfgContextBase() {
+}
+
+// *********************** DCfgMgrBase *************************
+
+DCfgMgrBase::DCfgMgrBase(DCfgContextBasePtr context)
+ : parse_order_(), context_(context) {
+ if (!context_) {
+ isc_throw(DCfgMgrBaseError, "DCfgMgrBase ctor: context cannot be NULL");
+ }
+}
+
+DCfgMgrBase::~DCfgMgrBase() {
+}
+
+isc::data::ConstElementPtr
+DCfgMgrBase::parseConfig(isc::data::ConstElementPtr config_set) {
+ LOG_DEBUG(dctl_logger, DBGLVL_COMMAND,
+ DCTL_CONFIG_START).arg(config_set->str());
+
+ if (!config_set) {
+ return (isc::config::createAnswer(1,
+ std::string("Can't parse NULL config")));
+ }
+
+ // The parsers implement data inheritance by directly accessing
+ // configuration context. For this reason the data parsers must store
+ // the parsed data into context immediately. This may cause data
+ // inconsistency if the parsing operation fails after the context has been
+ // modified. We need to preserve the original context here
+ // so as we can rollback changes when an error occurs.
+ DCfgContextBasePtr original_context = context_->clone();
+
+ // Answer will hold the result returned to the caller.
+ ConstElementPtr answer;
+
+ // Holds the name of the element being parsed.
+ std::string element_id;
+
+ try {
+ // Grab a map of element_ids and their data values from the new
+ // configuration set.
+ const std::map<std::string, ConstElementPtr>& values_map =
+ config_set->mapValue();
+
+ // Use a pre-ordered list of element ids to parse the elements in a
+ // specific order if the list (parser_order_) is not empty; otherwise
+ // elements are parsed in the order the value_map presents them.
+
+ if (!parse_order_.empty()) {
+ // For each element_id in the parse order list, look for it in the
+ // value map. If the element exists in the map, pass it and it's
+ // associated data in for parsing.
+ // If there is no matching entry in the value map an error is
+ // thrown. Note, that elements tagged as "optional" from the user
+ // perspective must still have default or empty entries in the
+ // configuration set to be parsed.
+ int parsed_count = 0;
+ std::map<std::string, ConstElementPtr>::const_iterator it;
+ BOOST_FOREACH(element_id, parse_order_) {
+ it = values_map.find(element_id);
+ if (it != values_map.end()) {
+ ++parsed_count;
+ buildAndCommit(element_id, it->second);
+ }
+ else {
+ LOG_ERROR(dctl_logger, DCTL_ORDER_NO_ELEMENT)
+ .arg(element_id);
+ isc_throw(DCfgMgrBaseError, "Element:" << element_id <<
+ " is listed in the parse order but is not "
+ " present in the configuration");
+ }
+ }
+
+ // NOTE: When using ordered parsing, the parse order list MUST
+ // include every possible element id that the value_map may contain.
+ // Entries in the map that are not in the parse order, would not be
+ // parsed. For now we will flag this as a programmatic error. One
+ // could attempt to adjust for this, by identifying such entries
+ // and parsing them either first or last but which would be correct?
+ // Better to hold the engineer accountable. So, if we parsed none
+ // or we parsed fewer than are in the map; then either the parse i
+ // order is incomplete OR the map has unsupported values.
+ if (!parsed_count ||
+ (parsed_count && ((parsed_count + 1) < values_map.size()))) {
+ LOG_ERROR(dctl_logger, DCTL_ORDER_ERROR);
+ isc_throw(DCfgMgrBaseError,
+ "Configuration contains elements not in parse order");
+ }
+ } else {
+ // Order doesn't matter so iterate over the value map directly.
+ // Pass each element and it's associated data in to be parsed.
+ ConfigPair config_pair;
+ BOOST_FOREACH(config_pair, values_map) {
+ element_id = config_pair.first;
+ buildAndCommit(element_id, config_pair.second);
+ }
+ }
+
+ // Everything was fine. Configuration set processed successfully.
+ LOG_INFO(dctl_logger, DCTL_CONFIG_COMPLETE).arg("");
+ answer = isc::config::createAnswer(0, "Configuration committed.");
+
+ } catch (const isc::Exception& ex) {
+ LOG_ERROR(dctl_logger, DCTL_PARSER_FAIL).arg(element_id).arg(ex.what());
+ answer = isc::config::createAnswer(1,
+ string("Configuration parsing failed: ") + ex.what());
+
+ // An error occurred, so make sure that we restore original context.
+ context_ = original_context;
+ return (answer);
+ }
+
+ return (answer);
+}
+
+void DCfgMgrBase::buildAndCommit(std::string& element_id,
+ isc::data::ConstElementPtr value) {
+ // Call derivation's implementation to create the appropriate parser
+ // based on the element id.
+ ParserPtr parser = createConfigParser(element_id);
+ if (!parser) {
+ isc_throw(DCfgMgrBaseError, "Could not create parser");
+ }
+
+ try {
+ // Invoke the parser's build method passing in the value. This will
+ // "convert" the Element form of value into the actual data item(s)
+ // and store them in parser's local storage.
+ parser->build(value);
+
+ // Invoke the parser's commit method. This "writes" the the data
+ // item(s) stored locally by the parser into the context. (Note that
+ // parsers are free to do more than update the context, but that is an
+ // nothing something we are concerned with here.)
+ parser->commit();
+ } catch (const isc::Exception& ex) {
+ isc_throw(DCfgMgrBaseError,
+ "Could not build and commit: " << ex.what());
+ } catch (...) {
+ isc_throw(DCfgMgrBaseError, "Non-ISC exception occurred");
+ }
+}
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
+
diff --git a/src/bin/d2/d_cfg_mgr.h b/src/bin/d2/d_cfg_mgr.h
new file mode 100644
index 0000000..a0bd9bb
--- /dev/null
+++ b/src/bin/d2/d_cfg_mgr.h
@@ -0,0 +1,331 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef D_CFG_MGR_H
+#define D_CFG_MGR_H
+
+#include <cc/data.h>
+#include <exceptions/exceptions.h>
+#include <dhcpsrv/dhcp_parsers.h>
+
+#include <stdint.h>
+#include <string>
+
+namespace isc {
+namespace d2 {
+
+/// @brief Exception thrown if the configuration manager encounters an error.
+class DCfgMgrBaseError : public isc::Exception {
+public:
+ DCfgMgrBaseError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+class DCfgContextBase;
+/// @brief Pointer to a configuration context.
+typedef boost::shared_ptr<DCfgContextBase> DCfgContextBasePtr;
+
+/// @brief Abstract class that implements a container for configuration context.
+/// It provides a single enclosure for the storage of configuration parameters
+/// and any other context specific information that needs to be accessible
+/// during configuration parsing as well as to the application as a whole.
+/// The base class supports storage for a small set of simple data types.
+/// Derivations simply add additional storage as needed. Note that this class
+/// declares the pure virtual clone() method, its copy constructor is protected,
+/// and its copy operator is inaccessible. Derivations must supply an
+/// implementation of clone that calls the base class copy constructor.
+/// This allows the management class to perform context backup and restoration
+/// without derivation specific knowledge using logic like
+/// the following:
+///
+/// // Make a backup copy
+/// DCfgContextBasePtr backup_copy(context_->clone());
+/// :
+/// // Restore from backup
+/// context_ = backup_copy;
+///
+class DCfgContextBase {
+public:
+ /// @brief Indicator that a configuration parameter is optional.
+ static const bool OPTIONAL = true;
+ static const bool REQUIRED = false;
+
+ /// @brief Constructor
+ DCfgContextBase();
+
+ /// @brief Destructor
+ virtual ~DCfgContextBase();
+
+ /// @brief Fetches the value for a given boolean configuration parameter
+ /// from the context.
+ ///
+ /// @param name is the name of the parameter to retrieve.
+ /// @param value is an output parameter in which to return the retrieved
+ /// value.
+ /// @param optional if true, the parameter is optional and the method
+ /// will not throw if the parameter is not found in the context. The
+ /// contents of the output parameter, value, will not be altered.
+ /// It defaults to false if not specified.
+ /// @throw throws DhcpConfigError if the context does not contain the
+ /// parameter and optional is false.
+ void getParam(const std::string& name, bool& value, bool optional=false);
+
+ /// @brief Fetches the value for a given uint32_t configuration parameter
+ /// from the context.
+ ///
+ /// @param name is the name of the parameter to retrieve.
+ /// @param value is an output parameter in which to return the retrieved
+ /// value.
+ /// @param optional if true, the parameter is optional and the method
+ /// will not throw if the parameter is not found in the context. The
+ /// contents of the output parameter, value, will not be altered.
+ /// @throw throws DhcpConfigError if the context does not contain the
+ /// parameter and optional is false.
+ void getParam(const std::string& name, uint32_t& value,
+ bool optional=false);
+
+ /// @brief Fetches the value for a given string configuration parameter
+ /// from the context.
+ ///
+ /// @param name is the name of the parameter to retrieve.
+ /// @param value is an output parameter in which to return the retrieved
+ /// value.
+ /// @param optional if true, the parameter is optional and the method
+ /// will not throw if the parameter is not found in the context. The
+ /// contents of the output parameter, value, will not be altered.
+ /// @throw throws DhcpConfigError if the context does not contain the
+ /// parameter and optional is false.
+ void getParam(const std::string& name, std::string& value,
+ bool optional=false);
+
+ /// @brief Fetches the Boolean Storage. Typically used for passing
+ /// into parsers.
+ ///
+ /// @return returns a pointer to the Boolean Storage.
+ isc::dhcp::BooleanStoragePtr getBooleanStorage() {
+ return (boolean_values_);
+ }
+
+ /// @brief Fetches the uint32 Storage. Typically used for passing
+ /// into parsers.
+ ///
+ /// @return returns a pointer to the uint32 Storage.
+ isc::dhcp::Uint32StoragePtr getUint32Storage() {
+ return (uint32_values_);
+ }
+
+ /// @brief Fetches the string Storage. Typically used for passing
+ /// into parsers.
+ ///
+ /// @return returns a pointer to the string Storage.
+ isc::dhcp::StringStoragePtr getStringStorage() {
+ return (string_values_);
+ }
+
+ /// @brief Creates a clone of this context object.
+ ///
+ /// As mentioned in the the class brief, derivation must supply an
+ /// implementation that initializes the base class storage as well as its
+ /// own. Typically the derivation's clone method would return the result
+ /// of passing "*this" into its own copy constructor:
+ ///
+ /// @code
+ /// class DStubContext : public DCfgContextBase {
+ /// public:
+ /// :
+ /// // Clone calls its own copy constructor
+ /// virtual DCfgContextBasePtr clone() {
+ /// return (DCfgContextBasePtr(new DStubContext(*this)));
+ /// }
+ ///
+ /// // Note that the copy constructor calls the base class copy ctor
+ /// // then initializes its additional storage.
+ /// DStubContext(const DStubContext& rhs) : DCfgContextBase(rhs),
+ /// extra_values_(new Uint32Storage(*(rhs.extra_values_))) {
+ /// }
+ /// :
+ /// // Here's the derivation's additional storage.
+ /// isc::dhcp::Uint32StoragePtr extra_values_;
+ /// :
+ /// @endcode
+ ///
+ /// @return returns a pointer to the new clone.
+ virtual DCfgContextBasePtr clone() = 0;
+
+protected:
+ /// @brief Copy constructor for use by derivations in clone().
+ DCfgContextBase(const DCfgContextBase& rhs);
+
+private:
+ /// @brief Private assignment operator to avoid potential for slicing.
+ DCfgContextBase& operator=(const DCfgContextBase& rhs);
+
+ /// @brief Storage for boolean parameters.
+ isc::dhcp::BooleanStoragePtr boolean_values_;
+
+ /// @brief Storage for uint32 parameters.
+ isc::dhcp::Uint32StoragePtr uint32_values_;
+
+ /// @brief Storage for string parameters.
+ isc::dhcp::StringStoragePtr string_values_;
+};
+
+/// @brief Defines an unsorted, list of string Element IDs.
+typedef std::vector<std::string> ElementIdList;
+
+/// @brief Configuration Manager
+///
+/// DCfgMgrBase is an abstract class that provides the mechanisms for managing
+/// an application's configuration. This includes services for parsing sets of
+/// configuration values, storing the parsed information in its converted form,
+/// and retrieving the information on demand. It is intended to be the worker
+/// class which is handed a set of configuration values to process by upper
+/// application management layers.
+///
+/// The class presents a public method for receiving new configurations,
+/// parseConfig. This method coordinates the parsing effort as follows:
+///
+/// @code
+/// make backup copy of configuration context
+/// for each top level element in new configuration
+/// get derivation-specific parser for element
+/// run parser
+/// update context with parsed results
+/// break on error
+///
+/// if an error occurred
+/// restore configuration context from backup
+/// @endcode
+///
+/// After making a backup of the current context, it iterates over the top-level
+/// elements in the new configuration. The order in which the elements are
+/// processed is either:
+///
+/// 1. Natural order presented by the configuration set
+/// 2. Specific order determined by a list of element ids
+///
+/// This allows a derivation to specify the order in which its elements are
+/// parsed if there are dependencies between elements.
+///
+/// To parse a given element, its id is passed into createConfigParser,
+/// which returns an instance of the appropriate parser. This method is
+/// abstract so the derivation's implementation determines the type of parser
+/// created. This isolates the knowledge of specific element ids and which
+/// application specific parsers to derivation.
+///
+/// Once the parser has been created, it is used to parse the data value
+/// associated with the element id and update the context with the parsed
+/// results.
+///
+/// In the event that an error occurs, parsing is halted and the
+/// configuration context is restored from backup.
+class DCfgMgrBase {
+public:
+ /// @brief Constructor
+ ///
+ /// @param context is a pointer to the configuration context the manager
+ /// will use for storing parsed results.
+ ///
+ /// @throw throws DCfgMgrBaseError if context is null
+ DCfgMgrBase(DCfgContextBasePtr context);
+
+ /// @brief Destructor
+ virtual ~DCfgMgrBase();
+
+ /// @brief Acts as the receiver of new configurations and coordinates
+ /// the parsing as described in the class brief.
+ ///
+ /// @param config_set is a set of configuration elements to parsed.
+ ///
+ /// @return an Element that contains the results of configuration composed
+ /// of an integer status value (0 means successful, non-zero means failure),
+ /// and a string explanation of the outcome.
+ isc::data::ConstElementPtr parseConfig(isc::data::ConstElementPtr
+ config_set);
+
+ /// @brief Adds a given element id to the end of the parse order list.
+ ///
+ /// The order in which elements are retrieved from this is the order in
+ /// which they are added to the list. Derivations should use this method
+ /// to populate the parse order as part of their constructor.
+ ///
+ /// @param element_id is the string name of the element as it will appear
+ /// in the configuration set.
+ void addToParseOrder(const std::string& element_id){
+ parse_order_.push_back(element_id);
+ }
+
+ /// @brief Fetches the parse order list.
+ ///
+ /// @return returns a const reference to the list.
+ const ElementIdList& getParseOrder() const {
+ return (parse_order_);
+ }
+
+ /// @brief Fetches the configuration context.
+ ///
+ /// @return returns a pointer reference to the configuration context.
+ DCfgContextBasePtr& getContext() {
+ return (context_);
+ }
+
+protected:
+ /// @brief Create a parser instance based on an element id.
+ ///
+ /// Given an element_id returns an instance of the appropriate parser.
+ /// This method is abstract, isolating any direct knowledge of element_ids
+ /// and parsers to within the application-specific derivation.
+ ///
+ /// @param element_id is the string name of the element as it will appear
+ /// in the configuration set.
+ ///
+ /// @return returns a ParserPtr to the parser instance.
+ /// @throw throws DCfgMgrBaseError if an error occurs.
+ virtual isc::dhcp::ParserPtr
+ createConfigParser(const std::string& element_id) = 0;
+
+private:
+
+ /// @brief Parse a configuration element.
+ ///
+ /// Given an element_id and data value, instantiate the appropriate
+ /// parser, parse the data value, and commit the results.
+ ///
+ /// @param element_id is the string name of the element as it will appear
+ /// in the configuration set.
+ /// @param value is the data value to be parsed and associated with
+ /// element_id.
+ ///
+ /// @throw throws DCfgMgrBaseError if an error occurs.
+ void buildAndCommit(std::string& element_id,
+ isc::data::ConstElementPtr value);
+
+ /// @brief A list of element ids which specifies the element parsing order.
+ ///
+ /// If the list is empty, the natural order in the configuration set
+ /// it used.
+ ElementIdList parse_order_;
+
+ /// @brief Pointer to the configuration context instance.
+ DCfgContextBasePtr context_;
+};
+
+/// @brief Defines a shared pointer to DCfgMgrBase.
+typedef boost::shared_ptr<DCfgMgrBase> DCfgMgrBasePtr;
+
+
+}; // end of isc::d2 namespace
+}; // end of isc namespace
+
+#endif // D_CFG_MGR_H
diff --git a/src/bin/d2/d_controller.cc b/src/bin/d2/d_controller.cc
index b32fc45..ed38cd8 100644
--- a/src/bin/d2/d_controller.cc
+++ b/src/bin/d2/d_controller.cc
@@ -46,7 +46,7 @@ DControllerBase::setController(const DControllerBasePtr& controller) {
}
void
-DControllerBase::launch(int argc, char* argv[]) {
+DControllerBase::launch(int argc, char* argv[], const bool test_mode) {
// Step 1 is to parse the command line arguments.
try {
parseArgs(argc, argv);
@@ -55,12 +55,16 @@ DControllerBase::launch(int argc, char* argv[]) {
throw; // rethrow it
}
- // Now that we know what the mode flags are, we can init logging.
- // If standalone is enabled, do not buffer initial log messages
- isc::log::initLogger(bin_name_,
- ((verbose_ && stand_alone_)
- ? isc::log::DEBUG : isc::log::INFO),
- isc::log::MAX_DEBUG_LEVEL, NULL, !stand_alone_);
+ // Do not initialize logger here if we are running unit tests. It would
+ // replace an instance of unit test specific logger.
+ if (!test_mode) {
+ // Now that we know what the mode flags are, we can init logging.
+ // If standalone is enabled, do not buffer initial log messages
+ isc::log::initLogger(bin_name_,
+ ((verbose_ && stand_alone_)
+ ? isc::log::DEBUG : isc::log::INFO),
+ isc::log::MAX_DEBUG_LEVEL, NULL, !stand_alone_);
+ }
LOG_DEBUG(dctl_logger, DBGLVL_START_SHUT, DCTL_STARTING)
.arg(app_name_).arg(getpid());
@@ -295,7 +299,8 @@ DControllerBase::commandHandler(const std::string& command,
isc::data::ConstElementPtr args) {
LOG_DEBUG(dctl_logger, DBGLVL_COMMAND, DCTL_COMMAND_RECEIVED)
- .arg(controller_->getAppName()).arg(command).arg(args->str());
+ .arg(controller_->getAppName()).arg(command)
+ .arg(args ? args->str() : "(no args)");
// Invoke the instance method on the controller singleton.
return (controller_->executeCommand(command, args));
@@ -305,7 +310,7 @@ isc::data::ConstElementPtr
DControllerBase::updateConfig(isc::data::ConstElementPtr new_config) {
isc::data::ConstElementPtr full_config;
if (stand_alone_) {
- // @TODO Until there is a configuration manager to provide retrieval
+ // @todo Until there is a configuration manager to provide retrieval
// we'll just assume the incoming config is the full configuration set.
// It may also make more sense to isolate the controller from the
// configuration manager entirely. We could do something like
@@ -368,7 +373,7 @@ DControllerBase::executeCommand(const std::string& command,
if (rcode == COMMAND_INVALID)
{
// It wasn't controller command, so may be an application command.
- answer = process_->command(command,args);
+ answer = process_->command(command, args);
}
}
diff --git a/src/bin/d2/d_controller.h b/src/bin/d2/d_controller.h
index bf7a607..4dab2e8 100644
--- a/src/bin/d2/d_controller.h
+++ b/src/bin/d2/d_controller.h
@@ -144,8 +144,19 @@ public:
/// arguments. Note this method is deliberately not virtual to ensure the
/// proper sequence of events occur.
///
+ /// This function can be run in the test mode. It prevents initialization
+ /// of D2 module logger. This is used in unit tests which initialize logger
+ /// in their main function. Such logger uses environmental variables to
+ /// control severity, verbosity etc. Reinitialization of logger by this
+ /// function would replace unit tests specific logger configuration with
+ /// this suitable for D2 running as a bind10 module.
+ ///
/// @param argc is the number of command line arguments supplied
/// @param argv is the array of string (char *) command line arguments
+ /// @param test_mode is a bool value which indicates if
+ /// @c DControllerBase::launch should be run in the test mode (if true).
+ /// This parameter doesn't have default value to force test implementers to
+ /// enable test mode explicitly.
///
/// @throw throws one of the following exceptions:
/// InvalidUsage - Indicates invalid command line.
@@ -156,7 +167,7 @@ public:
/// process event loop.
/// SessionEndError - Could not disconnect from BIND10 (integrated mode
/// only).
- void launch(int argc, char* argv[]);
+ void launch(int argc, char* argv[], const bool test_mode);
/// @brief A dummy configuration handler that always returns success.
///
@@ -171,7 +182,7 @@ public:
/// various configuration values. Installing the dummy handler
/// that guarantees to return success causes initial configuration
/// to be stored for the session being created and that it can
- /// be later accessed with \ref isc::ConfigData::getFullConfig.
+ /// be later accessed with \ref isc::config::ConfigData::getFullConfig.
///
/// @param new_config new configuration.
///
@@ -198,7 +209,8 @@ public:
/// the virtual instance method, executeCommand.
///
/// @param command textual representation of the command
- /// @param args parameters of the command
+ /// @param args parameters of the command. It can be NULL pointer if no
+ /// arguments exist for a particular command.
///
/// @return status of the processed command
static isc::data::ConstElementPtr
@@ -210,7 +222,7 @@ public:
/// implementation will merge the configuration update into the existing
/// configuration and then invoke the application process' configure method.
///
- /// @TODO This implementation is will evolve as the D2 configuration
+ /// @todo This implementation is will evolve as the D2 configuration
/// management task is implemented (trac #2957).
///
/// @param new_config is the new configuration
@@ -261,7 +273,7 @@ protected:
///
/// @param option is the option "character" from the command line, without
/// any prefixing hyphen(s)
- /// @optarg optarg is the argument value (if one) associated with the option
+ /// @param optarg is the argument value (if one) associated with the option
///
/// @return must return true if the option was valid, false is it is
/// invalid. (Note the default implementation always returns false.)
@@ -395,7 +407,7 @@ protected:
/// @brief Setter for setting the name of the controller's BIND10 spec file.
///
- /// @param value is the file name string.
+ /// @param spec_file_name the file name string.
void setSpecFileName(const std::string& spec_file_name) {
spec_file_name_ = spec_file_name;
}
diff --git a/src/bin/d2/d_process.h b/src/bin/d2/d_process.h
index 11b0a09..d1a7a55 100644
--- a/src/bin/d2/d_process.h
+++ b/src/bin/d2/d_process.h
@@ -17,6 +17,8 @@
#include <asiolink/asiolink.h>
#include <cc/data.h>
+#include <d2/d_cfg_mgr.h>
+
#include <boost/shared_ptr.hpp>
#include <exceptions/exceptions.h>
@@ -58,13 +60,21 @@ public:
/// in log statements, but otherwise arbitrary.
/// @param io_service is the io_service used by the caller for
/// asynchronous event handling.
+ /// @param cfg_mgr the configuration manager instance that handles
+ /// configuration parsing.
///
/// @throw DProcessBaseError is io_service is NULL.
- DProcessBase(const char* app_name, IOServicePtr io_service)
- : app_name_(app_name), io_service_(io_service), shut_down_flag_(false) {
+ DProcessBase(const char* app_name, IOServicePtr io_service,
+ DCfgMgrBasePtr cfg_mgr)
+ : app_name_(app_name), io_service_(io_service), shut_down_flag_(false),
+ cfg_mgr_(cfg_mgr) {
if (!io_service_) {
isc_throw (DProcessBaseError, "IO Service cannot be null");
}
+
+ if (!cfg_mgr_) {
+ isc_throw (DProcessBaseError, "CfgMgr cannot be null");
+ }
};
/// @brief May be used after instantiation to perform initialization unique
@@ -159,6 +169,13 @@ public:
io_service_->stop();
}
+ /// @brief Fetches the process's configuration manager.
+ ///
+ /// @return returns a reference to the configuration manager.
+ DCfgMgrBasePtr& getCfgMgr() {
+ return (cfg_mgr_);
+ }
+
private:
/// @brief Text label for the process. Generally used in log statements,
/// but otherwise can be arbitrary.
@@ -169,6 +186,9 @@ private:
/// @brief Boolean flag set when shutdown has been requested.
bool shut_down_flag_;
+
+ /// @brief Pointer to the configuration manager.
+ DCfgMgrBasePtr cfg_mgr_;
};
/// @brief Defines a shared pointer to DProcessBase.
diff --git a/src/bin/d2/dhcp-ddns.spec b/src/bin/d2/dhcp-ddns.spec
index 1098ada..e29bc88 100644
--- a/src/bin/d2/dhcp-ddns.spec
+++ b/src/bin/d2/dhcp-ddns.spec
@@ -1,21 +1,205 @@
+{
+"module_spec":
{
- "module_spec": {
"module_name": "DhcpDdns",
"module_description": "DHPC-DDNS Service",
"config_data": [
- ],
+ {
+ "item_name": "interface",
+ "item_type": "string",
+ "item_optional": true,
+ "item_default": "eth0"
+ },
+
+ {
+ "item_name": "ip_address",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "127.0.0.1"
+ },
+
+ {
+ "item_name": "port",
+ "item_type": "integer",
+ "item_optional": true,
+ "item_default": 51771
+ },
+ {
+ "item_name": "tsig_keys",
+ "item_type": "list",
+ "item_optional": true,
+ "item_default": [],
+ "list_item_spec":
+ {
+ "item_name": "tsig_key",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {"algorithm" : "hmac_md5"},
+ "map_item_spec": [
+ {
+ "item_name": "name",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ },
+ {
+ "item_name": "algorithm",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ },
+ {
+ "item_name": "secret",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ }]
+ }
+ },
+ {
+ "item_name": "forward_ddns",
+ "item_type": "map",
+ "item_optional": true,
+ "item_default": {},
+ "map_item_spec": [
+ {
+ "item_name": "ddns_domains",
+ "item_type": "list",
+ "item_optional": false,
+ "item_default": [],
+ "list_item_spec":
+ {
+ "item_name": "ddns_domain",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "map_item_spec": [
+ {
+ "item_name": "name",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ },
+
+ {
+ "item_name": "key_name",
+ "item_type": "string",
+ "item_optional": true,
+ "item_default": ""
+ },
+
+ {
+ "item_name": "dns_servers",
+ "item_type": "list",
+ "item_optional": false,
+ "item_default": [],
+ "list_item_spec":
+ {
+ "item_name": "dns_server",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "map_item_spec": [
+ {
+ "item_name": "hostname",
+ "item_type": "string",
+ "item_optional": true,
+ "item_default": ""
+ },
+ {
+ "item_name": "ip_address",
+ "item_type": "string",
+ "item_optional": true,
+ "item_default": ""
+ },
+ {
+ "item_name": "port",
+ "item_type": "integer",
+ "item_optional": true,
+ "item_default": 53
+ }]
+ }
+ }]
+ }
+ }]
+ },
+
+ {
+ "item_name": "reverse_ddns",
+ "item_type": "map",
+ "item_optional": true,
+ "item_default": {},
+ "map_item_spec": [
+ {
+ "item_name": "ddns_domains",
+ "item_type": "list",
+ "item_optional": false,
+ "item_default": [],
+ "list_item_spec":
+ {
+ "item_name": "ddns_domain",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "map_item_spec": [
+ {
+ "item_name": "name",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ },
+
+ {
+ "item_name": "key_name",
+ "item_type": "string",
+ "item_optional": true,
+ "item_default": ""
+ },
+
+ {
+ "item_name": "dns_servers",
+ "item_type": "list",
+ "item_optional": false,
+ "item_default": [],
+ "list_item_spec":
+ {
+ "item_name": "dns_server",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "map_item_spec": [
+ {
+ "item_name": "hostname",
+ "item_type": "string",
+ "item_optional": true,
+ "item_default": ""
+ },
+ {
+ "item_name": "ip_address",
+ "item_type": "string",
+ "item_optional": true,
+ "item_default": ""
+ },
+ {
+ "item_name": "port",
+ "item_type": "integer",
+ "item_optional": true,
+ "item_default": 53
+ }]
+ }
+ }]
+ }
+ }]
+ }],
+
"commands": [
- {
- "command_name": "shutdown",
- "command_description": "Shut down the DHCP-DDNS service",
- "command_args": [
- {
- "item_name": "pid",
- "item_type": "integer",
- "item_optional": true
- }
- ]
- }
+ {
+ "command_name": "shutdown",
+ "command_description": "Shuts down DHCPv6 server.",
+ "command_args": [
+ ]
+ }
]
}
}
+
diff --git a/src/bin/d2/dns_client.cc b/src/bin/d2/dns_client.cc
new file mode 100644
index 0000000..a3bff81
--- /dev/null
+++ b/src/bin/d2/dns_client.cc
@@ -0,0 +1,247 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <d2/dns_client.h>
+#include <d2/d2_log.h>
+#include <dns/messagerenderer.h>
+#include <limits>
+
+namespace isc {
+namespace d2 {
+
+namespace {
+
+// OutputBuffer objects are pre-allocated before data is written to them.
+// This is a default number of bytes for the buffers we create within
+// DNSClient class.
+const size_t DEFAULT_BUFFER_SIZE = 128;
+
+}
+
+using namespace isc::util;
+using namespace isc::asiolink;
+using namespace isc::asiodns;
+using namespace isc::dns;
+
+// This class provides the implementation for the DNSClient. This allows for
+// the separation of the DNSClient interface from the implementation details.
+// Currently, implementation uses IOFetch object to handle asynchronous
+// communication with the DNS. This design may be revisited in the future. If
+// implementation is changed, the DNSClient API will remain unchanged thanks
+// to this separation.
+class DNSClientImpl : public asiodns::IOFetch::Callback {
+public:
+ // A buffer holding response from a DNS.
+ util::OutputBufferPtr in_buf_;
+ // A caller-supplied object holding a parsed response from DNS.
+ D2UpdateMessagePtr response_;
+ // A caller-supplied external callback which is invoked when DNS message
+ // exchange is complete or interrupted.
+ DNSClient::Callback* callback_;
+ // A Transport Layer protocol used to communicate with a DNS.
+ DNSClient::Protocol proto_;
+
+ // Constructor and Destructor
+ DNSClientImpl(D2UpdateMessagePtr& response_placeholder,
+ DNSClient::Callback* callback,
+ const DNSClient::Protocol proto);
+ virtual ~DNSClientImpl();
+
+ // This internal callback is called when the DNS update message exchange is
+ // complete. It further invokes the external callback provided by a caller.
+ // Before external callback is invoked, an object of the D2UpdateMessage
+ // type, representing a response from the server is set.
+ virtual void operator()(asiodns::IOFetch::Result result);
+
+ // Starts asynchronous DNS Update.
+ void doUpdate(asiolink::IOService& io_service,
+ const asiolink::IOAddress& ns_addr,
+ const uint16_t ns_port,
+ D2UpdateMessage& update,
+ const unsigned int wait);
+
+ // This function maps the IO error to the DNSClient error.
+ DNSClient::Status getStatus(const asiodns::IOFetch::Result);
+};
+
+DNSClientImpl::DNSClientImpl(D2UpdateMessagePtr& response_placeholder,
+ DNSClient::Callback* callback,
+ const DNSClient::Protocol proto)
+ : in_buf_(new OutputBuffer(DEFAULT_BUFFER_SIZE)),
+ response_(response_placeholder), callback_(callback), proto_(proto) {
+
+ // @todo Currently we only support UDP. The support for TCP is planned for
+ // the future release.
+ if (proto_ == DNSClient::TCP) {
+ isc_throw(isc::NotImplemented, "TCP is currently not supported as a"
+ << " Transport protocol for DNS Updates; please use UDP");
+ }
+
+ // Given that we already eliminated the possibility that TCP is used, it
+ // would be sufficient to check that (proto != DNSClient::UDP). But, once
+ // support TCP is added the check above will disappear and the extra check
+ // will be needed here anyway.
+ // Note that cascaded check is used here instead of:
+ // if (proto_ != DNSClient::TCP && proto_ != DNSClient::UDP)..
+ // because some versions of GCC compiler complain that check above would
+ // be always 'false' due to limited range of enumeration. In fact, it is
+ // possible to pass out of range integral value through enum and it should
+ // be caught here.
+ if (proto_ != DNSClient::TCP) {
+ if (proto_ != DNSClient::UDP) {
+ isc_throw(isc::NotImplemented, "invalid transport protocol type '"
+ << proto_ << "' specified for DNS Updates");
+ }
+ }
+
+ if (!response_) {
+ isc_throw(BadValue, "a pointer to an object to encapsulate the DNS"
+ " server must be provided; found NULL value");
+
+ }
+}
+
+DNSClientImpl::~DNSClientImpl() {
+}
+
+void
+DNSClientImpl::operator()(asiodns::IOFetch::Result result) {
+ // Get the status from IO. If no success, we just call user's callback
+ // and pass the status code.
+ DNSClient::Status status = getStatus(result);
+ if (status == DNSClient::SUCCESS) {
+ InputBuffer response_buf(in_buf_->getData(), in_buf_->getLength());
+ // Server's response may be corrupted. In such case, fromWire will
+ // throw an exception. We want to catch this exception to return
+ // appropriate status code to the caller and log this event.
+ try {
+ response_->fromWire(response_buf);
+
+ } catch (const Exception& ex) {
+ status = DNSClient::INVALID_RESPONSE;
+ LOG_DEBUG(dctl_logger, DBGLVL_TRACE_DETAIL,
+ DHCP_DDNS_INVALID_RESPONSE).arg(ex.what());
+
+ }
+ }
+
+ // Once we are done with internal business, let's call a callback supplied
+ // by a caller.
+ if (callback_ != NULL) {
+ (*callback_)(status);
+ }
+}
+
+DNSClient::Status
+DNSClientImpl::getStatus(const asiodns::IOFetch::Result result) {
+ switch (result) {
+ case IOFetch::SUCCESS:
+ return (DNSClient::SUCCESS);
+
+ case IOFetch::TIME_OUT:
+ return (DNSClient::TIMEOUT);
+
+ case IOFetch::STOPPED:
+ return (DNSClient::IO_STOPPED);
+
+ default:
+ ;
+ }
+ return (DNSClient::OTHER);
+}
+
+void
+DNSClientImpl::doUpdate(IOService& io_service,
+ const IOAddress& ns_addr,
+ const uint16_t ns_port,
+ D2UpdateMessage& update,
+ const unsigned int wait) {
+ // A renderer is used by the toWire function which creates the on-wire data
+ // from the DNS Update message. A renderer has its internal buffer where it
+ // renders data by default. However, this buffer can't be directly accessed.
+ // Fortunately, the renderer's API accepts user-supplied buffers. So, let's
+ // create our own buffer and pass it to the renderer so as the message is
+ // rendered to this buffer. Finally, we pass this buffer to IOFetch.
+ dns::MessageRenderer renderer;
+ OutputBufferPtr msg_buf(new OutputBuffer(DEFAULT_BUFFER_SIZE));
+ renderer.setBuffer(msg_buf.get());
+
+ // Render DNS Update message. This may throw a bunch of exceptions if
+ // invalid message object is given.
+ update.toWire(renderer);
+
+ // IOFetch has all the mechanisms that we need to perform asynchronous
+ // communication with the DNS server. The last but one argument points to
+ // this object as a completion callback for the message exchange. As a
+ // result operator()(Status) will be called.
+
+ // Timeout value is explicitly cast to the int type to avoid warnings about
+ // overflows when doing implicit cast. It should have been checked by the
+ // caller that the unsigned timeout value will fit into int.
+ IOFetch io_fetch(IOFetch::UDP, io_service, msg_buf, ns_addr, ns_port,
+ in_buf_, this, static_cast<int>(wait));
+ // Post the task to the task queue in the IO service. Caller will actually
+ // run these tasks by executing IOService::run.
+ io_service.post(io_fetch);
+}
+
+
+DNSClient::DNSClient(D2UpdateMessagePtr& response_placeholder,
+ Callback* callback, const DNSClient::Protocol proto)
+ : impl_(new DNSClientImpl(response_placeholder, callback, proto)) {
+}
+
+DNSClient::~DNSClient() {
+ delete (impl_);
+}
+
+unsigned int
+DNSClient::getMaxTimeout() {
+ static const unsigned int max_timeout = std::numeric_limits<int>::max();
+ return (max_timeout);
+}
+
+void
+DNSClient::doUpdate(IOService&,
+ const IOAddress&,
+ const uint16_t,
+ D2UpdateMessage&,
+ const unsigned int,
+ const dns::TSIGKey&) {
+ isc_throw(isc::NotImplemented, "TSIG is currently not supported for"
+ "DNS Update message");
+}
+
+void
+DNSClient::doUpdate(IOService& io_service,
+ const IOAddress& ns_addr,
+ const uint16_t ns_port,
+ D2UpdateMessage& update,
+ const unsigned int wait) {
+ // The underlying implementation which we use to send DNS Updates uses
+ // signed integers for timeout. If we want to avoid overflows we need to
+ // respect this limitation here.
+ if (wait > getMaxTimeout()) {
+ isc_throw(isc::BadValue, "A timeout value for DNS Update request must"
+ " not exceed " << getMaxTimeout()
+ << ". Provided timeout value is '" << wait << "'");
+ }
+ impl_->doUpdate(io_service, ns_addr, ns_port, update, wait);
+}
+
+
+
+} // namespace d2
+} // namespace isc
+
diff --git a/src/bin/d2/dns_client.h b/src/bin/d2/dns_client.h
new file mode 100644
index 0000000..c1c54f6
--- /dev/null
+++ b/src/bin/d2/dns_client.h
@@ -0,0 +1,192 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef DNS_CLIENT_H
+#define DNS_CLIENT_H
+
+#include <d2/d2_update_message.h>
+
+#include <asiolink/io_service.h>
+#include <util/buffer.h>
+
+#include <asiodns/io_fetch.h>
+#include <dns/tsig.h>
+
+namespace isc {
+namespace d2 {
+
+class DNSClient;
+typedef boost::shared_ptr<DNSClient> DNSClientPtr;
+
+/// DNSClient class implementation.
+class DNSClientImpl;
+
+/// @brief The @c DNSClient class handles communication with the DNS server.
+///
+/// Communication with the DNS server is asynchronous. Caller must provide a
+/// callback, which will be invoked when the response from the DNS server is
+/// received, a timeout has occurred or IO service has been stopped for any
+/// reason. The caller-supplied callback is called by the internal callback
+/// operator implemented by @c DNSClient. This callback is responsible for
+/// initializing the @c D2UpdateMessage instance which encapsulates the response
+/// from the DNS. This initialization does not take place if the response from
+/// DNS is not received.
+///
+/// Caller must supply a pointer to the @c D2UpdateMessage object, which will
+/// encapsulate DNS response, through class constructor. An exception will be
+/// thrown if the pointer is not initialized by the caller.
+///
+/// @todo Ultimately, this class will support both TCP and UDP Transport.
+/// Currently only UDP is supported and can be specified as a preferred
+/// protocol. @c DNSClient constructor will throw an exception if TCP is
+/// specified. Once both protocols are supported, the @c DNSClient logic will
+/// try to obey caller's preference. However, it may use the other protocol if
+/// on its own discretion, when there is a legitimate reason to do so. For
+/// example, if communication with the server using preferred protocol fails.
+class DNSClient {
+public:
+
+ /// @brief Transport layer protocol used by a DNS Client to communicate
+ /// with a server.
+ enum Protocol {
+ UDP,
+ TCP
+ };
+
+ /// @brief A status code of the DNSClient.
+ enum Status {
+ SUCCESS, ///< Response received and is ok.
+ TIMEOUT, ///< No response, timeout.
+ IO_STOPPED, ///< IO was stopped.
+ INVALID_RESPONSE, ///< Response received but invalid.
+ OTHER ///< Other, unclassified error.
+ };
+
+ /// @brief Callback for the @c DNSClient class.
+ ///
+ /// This is is abstract class which represents the external callback for the
+ /// @c DNSClient. Caller must implement this class and supply its instance
+ /// in the @c DNSClient constructor to get callbacks when the DNS Update
+ /// exchange is complete (@see @c DNSClient).
+ class Callback {
+ public:
+ /// @brief Virtual destructor.
+ virtual ~Callback() { }
+
+ /// @brief Function operator implementing a callback.
+ ///
+ /// @param status a @c DNSClient::Status enum representing status code
+ /// of DNSClient operation.
+ virtual void operator()(DNSClient::Status status) = 0;
+ };
+
+ /// @brief Constructor.
+ ///
+ /// @param response_placeholder Pointer to an object which will hold a
+ /// DNS server's response. Caller is responsible for allocating this object.
+ /// @param callback Pointer to an object implementing @c DNSClient::Callback
+ /// class. This object will be called when DNS message exchange completes or
+ /// if an error occurs. NULL value disables callback invocation.
+ /// @param proto caller's preference regarding Transport layer protocol to
+ /// be used by DNS Client to communicate with a server.
+ DNSClient(D2UpdateMessagePtr& response_placeholder, Callback* callback,
+ const Protocol proto = UDP);
+
+ /// @brief Virtual destructor, does nothing.
+ ~DNSClient();
+
+ ///
+ /// @name Copy constructor and assignment operator
+ ///
+ /// Copy constructor and assignment operator are private because there are
+ /// no use cases when @DNSClient instance will need to be copied. Also, it
+ /// is desired to avoid copying @DNSClient::impl_ pointer and external
+ /// callbacks.
+ ///
+ //@{
+private:
+ DNSClient(const DNSClient& source);
+ DNSClient& operator=(const DNSClient& source);
+ //@}
+
+public:
+
+ /// @brief Returns maximal allowed timeout value accepted by
+ /// @c DNSClient::doUpdate.
+ ///
+ /// @return maximal allowed timeout value accepted by @c DNSClient::doUpdate
+ static unsigned int getMaxTimeout();
+
+ /// @brief Start asynchronous DNS Update with TSIG.
+ ///
+ /// This function starts asynchronous DNS Update and returns. The DNS Update
+ /// will be executed by the specified IO service. Once the message exchange
+ /// with a DNS server is complete, timeout occurs or IO operation is
+ /// interrupted, the caller-supplied callback function will be invoked.
+ ///
+ /// An address and port of the DNS server is specified through the function
+ /// arguments so as the same instance of the @c DNSClient can be used to
+ /// initiate multiple message exchanges.
+ ///
+ /// @param io_service IO service to be used to run the message exchange.
+ /// @param ns_addr DNS server address.
+ /// @param ns_port DNS server port.
+ /// @param update A DNS Update message to be sent to the server.
+ /// @param wait A timeout (in seconds) for the response. If a response is
+ /// not received within the timeout, exchange is interrupted. This value
+ /// must not exceed maximal value for 'int' data type.
+ /// @param tsig_key An @c isc::dns::TSIGKey object representing TSIG
+ /// context which will be used to render the DNS Update message.
+ ///
+ /// @todo Implement TSIG Support. Currently any attempt to call this
+ /// function will result in exception.
+ void doUpdate(asiolink::IOService& io_service,
+ const asiolink::IOAddress& ns_addr,
+ const uint16_t ns_port,
+ D2UpdateMessage& update,
+ const unsigned int wait,
+ const dns::TSIGKey& tsig_key);
+
+ /// @brief Start asynchronous DNS Update without TSIG.
+ ///
+ /// This function starts asynchronous DNS Update and returns. The DNS Update
+ /// will be executed by the specified IO service. Once the message exchange
+ /// with a DNS server is complete, timeout occurs or IO operation is
+ /// interrupted, the caller-supplied callback function will be invoked.
+ ///
+ /// An address and port of the DNS server is specified through the function
+ /// arguments so as the same instance of the @c DNSClient can be used to
+ /// initiate multiple message exchanges.
+ ///
+ /// @param io_service IO service to be used to run the message exchange.
+ /// @param ns_addr DNS server address.
+ /// @param ns_port DNS server port.
+ /// @param update A DNS Update message to be sent to the server.
+ /// @param wait A timeout (in seconds) for the response. If a response is
+ /// not received within the timeout, exchange is interrupted. This value
+ /// must not exceed maximal value for 'int' data type.
+ void doUpdate(asiolink::IOService& io_service,
+ const asiolink::IOAddress& ns_addr,
+ const uint16_t ns_port,
+ D2UpdateMessage& update,
+ const unsigned int wait);
+
+private:
+ DNSClientImpl* impl_; ///< Pointer to DNSClient implementation.
+};
+
+} // namespace d2
+} // namespace isc
+
+#endif // DNS_CLIENT_H
diff --git a/src/bin/d2/main.cc b/src/bin/d2/main.cc
index fda31038..4b5b8bc 100644
--- a/src/bin/d2/main.cc
+++ b/src/bin/d2/main.cc
@@ -38,7 +38,8 @@ int main(int argc, char* argv[]) {
// Launch the controller passing in command line arguments.
// Exit program with the controller's return code.
try {
- controller->launch(argc, argv);
+ // 'false' value disables test mode.
+ controller->launch(argc, argv, false);
} catch (const isc::Exception& ex) {
std::cerr << "Service failed:" << ex.what() << std::endl;
ret = EXIT_FAILURE;
diff --git a/src/bin/d2/ncr_msg.cc b/src/bin/d2/ncr_msg.cc
new file mode 100644
index 0000000..cf47176
--- /dev/null
+++ b/src/bin/d2/ncr_msg.cc
@@ -0,0 +1,459 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <d2/ncr_msg.h>
+#include <asiolink/io_address.h>
+#include <asiolink/io_error.h>
+
+#include <sstream>
+#include <limits>
+
+namespace isc {
+namespace d2 {
+
+/********************************* D2Dhcid ************************************/
+
+D2Dhcid::D2Dhcid() {
+}
+
+D2Dhcid::D2Dhcid(const std::string& data) {
+ fromStr(data);
+}
+
+void
+D2Dhcid::fromStr(const std::string& data) {
+ bytes_.clear();
+ try {
+ isc::util::encode::decodeHex(data, bytes_);
+ } catch (const isc::Exception& ex) {
+ isc_throw(NcrMessageError, "Invalid data in Dhcid:" << ex.what());
+ }
+}
+
+std::string
+D2Dhcid::toStr() const {
+ return (isc::util::encode::encodeHex(bytes_));
+
+
+}
+
+
+/**************************** NameChangeRequest ******************************/
+
+NameChangeRequest::NameChangeRequest()
+ : change_type_(CHG_ADD), forward_change_(false),
+ reverse_change_(false), fqdn_(""), ip_address_(""),
+ dhcid_(), lease_expires_on_(), lease_length_(0), status_(ST_NEW) {
+}
+
+NameChangeRequest::NameChangeRequest(const NameChangeType change_type,
+ const bool forward_change, const bool reverse_change,
+ const std::string& fqdn, const std::string& ip_address,
+ const D2Dhcid& dhcid,
+ const uint64_t lease_expires_on,
+ const uint32_t lease_length)
+ : change_type_(change_type), forward_change_(forward_change),
+ reverse_change_(reverse_change), fqdn_(fqdn), ip_address_(ip_address),
+ dhcid_(dhcid), lease_expires_on_(lease_expires_on),
+ lease_length_(lease_length), status_(ST_NEW) {
+
+ // Validate the contents. This will throw a NcrMessageError if anything
+ // is invalid.
+ validateContent();
+}
+
+NameChangeRequestPtr
+NameChangeRequest::fromFormat(const NameChangeFormat format,
+ isc::util::InputBuffer& buffer) {
+ // Based on the format requested, pull the marshalled request from
+ // InputBuffer and pass it into the appropriate format-specific factory.
+ NameChangeRequestPtr ncr;
+ switch (format) {
+ case FMT_JSON: {
+ try {
+ // Get the length of the JSON text.
+ size_t len = buffer.readUint16();
+
+ // Read the text from the buffer into a vector.
+ std::vector<uint8_t> vec;
+ buffer.readVector(vec, len);
+
+ // Turn the vector into a string.
+ std::string string_data(vec.begin(), vec.end());
+
+ // Pass the string of JSON text into JSON factory to create the
+ // NameChangeRequest instance. Note the factory may throw
+ // NcrMessageError.
+ ncr = NameChangeRequest::fromJSON(string_data);
+ } catch (isc::util::InvalidBufferPosition& ex) {
+ // Read error accessing data in InputBuffer.
+ isc_throw(NcrMessageError, "fromFormat: buffer read error: "
+ << ex.what());
+ }
+
+ break;
+ }
+ default:
+ // Programmatic error, shouldn't happen.
+ isc_throw(NcrMessageError, "fromFormat - invalid format");
+ break;
+ }
+
+ return (ncr);
+}
+
+void
+NameChangeRequest::toFormat(const NameChangeFormat format,
+ isc::util::OutputBuffer& buffer) const {
+ // Based on the format requested, invoke the appropriate format handler
+ // which will marshal this request's contents into the OutputBuffer.
+ switch (format) {
+ case FMT_JSON: {
+ // Invoke toJSON to create a JSON text of this request's contents.
+ std::string json = toJSON();
+ uint16_t length = json.size();
+
+ // Write the length of the JSON text to the OutputBuffer first, then
+ // write the JSON text itself.
+ buffer.writeUint16(length);
+ buffer.writeData(json.c_str(), length);
+ break;
+ }
+ default:
+ // Programmatic error, shouldn't happen.
+ isc_throw(NcrMessageError, "toFormat - invalid format");
+ break;
+ }
+}
+
+NameChangeRequestPtr
+NameChangeRequest::fromJSON(const std::string& json) {
+ // This method leverages the existing JSON parsing provided by isc::data
+ // library. Should this prove to be a performance issue, it may be that
+ // lighter weight solution would be appropriate.
+
+ // Turn the string of JSON text into an Element set.
+ isc::data::ElementPtr elements;
+ try {
+ elements = isc::data::Element::fromJSON(json);
+ } catch (isc::data::JSONError& ex) {
+ isc_throw(NcrMessageError,
+ "Malformed NameChangeRequest JSON: " << ex.what());
+ }
+
+ // Get a map of the Elements, keyed by element name.
+ ElementMap element_map = elements->mapValue();
+ isc::data::ConstElementPtr element;
+
+ // Use default constructor to create a "blank" NameChangeRequest.
+ NameChangeRequestPtr ncr(new NameChangeRequest());
+
+ // For each member of NameChangeRequest, find its element in the map and
+ // call the appropriate Element-based setter. These setters may throw
+ // NcrMessageError if the given Element is the wrong type or its data
+ // content is lexically invalid. If the element is NOT found in the
+ // map, getElement will throw NcrMessageError indicating the missing
+ // member. Currently there are no optional values.
+ element = ncr->getElement("change_type", element_map);
+ ncr->setChangeType(element);
+
+ element = ncr->getElement("forward_change", element_map);
+ ncr->setForwardChange(element);
+
+ element = ncr->getElement("reverse_change", element_map);
+ ncr->setReverseChange(element);
+
+ element = ncr->getElement("fqdn", element_map);
+ ncr->setFqdn(element);
+
+ element = ncr->getElement("ip_address", element_map);
+ ncr->setIpAddress(element);
+
+ element = ncr->getElement("dhcid", element_map);
+ ncr->setDhcid(element);
+
+ element = ncr->getElement("lease_expires_on", element_map);
+ ncr->setLeaseExpiresOn(element);
+
+ element = ncr->getElement("lease_length", element_map);
+ ncr->setLeaseLength(element);
+
+ // All members were in the Element set and were correct lexically. Now
+ // validate the overall content semantically. This will throw an
+ // NcrMessageError if anything is amiss.
+ ncr->validateContent();
+
+ // Everything is valid, return the new instance.
+ return (ncr);
+}
+
+std::string
+NameChangeRequest::toJSON() const {
+ // Create a JSON string of this request's contents. Note that this method
+ // does NOT use the isc::data library as generating the output is straight
+ // forward.
+ std::ostringstream stream;
+
+ stream << "{\"change_type\":" << getChangeType() << ","
+ << "\"forward_change\":"
+ << (isForwardChange() ? "true" : "false") << ","
+ << "\"reverse_change\":"
+ << (isReverseChange() ? "true" : "false") << ","
+ << "\"fqdn\":\"" << getFqdn() << "\","
+ << "\"ip_address\":\"" << getIpAddress() << "\","
+ << "\"dhcid\":\"" << getDhcid().toStr() << "\","
+ << "\"lease_expires_on\":\"" << getLeaseExpiresOnStr() << "\","
+ << "\"lease_length\":" << getLeaseLength() << "}";
+
+ return (stream.str());
+}
+
+
+void
+NameChangeRequest::validateContent() {
+ //@todo This is an initial implementation which provides a minimal amount
+ // of validation. FQDN, DHCID, and IP Address members are all currently
+ // strings, these may be replaced with richer classes.
+ if (fqdn_ == "") {
+ isc_throw(NcrMessageError, "FQDN cannot be blank");
+ }
+
+ // Validate IP Address.
+ try {
+ isc::asiolink::IOAddress io_addr(ip_address_);
+ } catch (const isc::asiolink::IOError& ex) {
+ isc_throw(NcrMessageError,
+ "Invalid ip address string for ip_address: " << ip_address_);
+ }
+
+ // Validate the DHCID.
+ if (dhcid_.getBytes().size() == 0) {
+ isc_throw(NcrMessageError, "DHCID cannot be blank");
+ }
+
+ // Ensure the request specifies at least one direction to update.
+ if (!forward_change_ && !reverse_change_) {
+ isc_throw(NcrMessageError,
+ "Invalid Request, forward and reverse flags are both false");
+ }
+}
+
+isc::data::ConstElementPtr
+NameChangeRequest::getElement(const std::string& name,
+ const ElementMap& element_map) const {
+ // Look for "name" in the element map.
+ ElementMap::const_iterator it = element_map.find(name);
+ if (it == element_map.end()) {
+ // Didn't find the element, so throw.
+ isc_throw(NcrMessageError,
+ "NameChangeRequest value missing for: " << name );
+ }
+
+ // Found the element, return it.
+ return (it->second);
+}
+
+void
+NameChangeRequest::setChangeType(const NameChangeType value) {
+ change_type_ = value;
+}
+
+
+void
+NameChangeRequest::setChangeType(isc::data::ConstElementPtr element) {
+ long raw_value = -1;
+ try {
+ // Get the element's integer value.
+ raw_value = element->intValue();
+ } catch (isc::data::TypeError& ex) {
+ // We expect a integer Element type, don't have one.
+ isc_throw(NcrMessageError,
+ "Wrong data type for change_type: " << ex.what());
+ }
+
+ if ((raw_value != CHG_ADD) && (raw_value != CHG_REMOVE)) {
+ // Value is not a valid change type.
+ isc_throw(NcrMessageError,
+ "Invalid data value for change_type: " << raw_value);
+ }
+
+ // Good to go, make the assignment.
+ setChangeType(static_cast<NameChangeType>(raw_value));
+}
+
+void
+NameChangeRequest::setForwardChange(const bool value) {
+ forward_change_ = value;
+}
+
+void
+NameChangeRequest::setForwardChange(isc::data::ConstElementPtr element) {
+ bool value;
+ try {
+ // Get the element's boolean value.
+ value = element->boolValue();
+ } catch (isc::data::TypeError& ex) {
+ // We expect a boolean Element type, don't have one.
+ isc_throw(NcrMessageError,
+ "Wrong data type for forward_change :" << ex.what());
+ }
+
+ // Good to go, make the assignment.
+ setForwardChange(value);
+}
+
+void
+NameChangeRequest::setReverseChange(const bool value) {
+ reverse_change_ = value;
+}
+
+void
+NameChangeRequest::setReverseChange(isc::data::ConstElementPtr element) {
+ bool value;
+ try {
+ // Get the element's boolean value.
+ value = element->boolValue();
+ } catch (isc::data::TypeError& ex) {
+ // We expect a boolean Element type, don't have one.
+ isc_throw(NcrMessageError,
+ "Wrong data type for reverse_change :" << ex.what());
+ }
+
+ // Good to go, make the assignment.
+ setReverseChange(value);
+}
+
+
+void
+NameChangeRequest::setFqdn(isc::data::ConstElementPtr element) {
+ setFqdn(element->stringValue());
+}
+
+void
+NameChangeRequest::setFqdn(const std::string& value) {
+ fqdn_ = value;
+}
+
+void
+NameChangeRequest::setIpAddress(const std::string& value) {
+ ip_address_ = value;
+}
+
+
+void
+NameChangeRequest::setIpAddress(isc::data::ConstElementPtr element) {
+ setIpAddress(element->stringValue());
+}
+
+
+void
+NameChangeRequest::setDhcid(const std::string& value) {
+ dhcid_.fromStr(value);
+}
+
+void
+NameChangeRequest::setDhcid(isc::data::ConstElementPtr element) {
+ setDhcid(element->stringValue());
+}
+
+std::string
+NameChangeRequest::getLeaseExpiresOnStr() const {
+ return (isc::util::timeToText64(lease_expires_on_));
+}
+
+void
+NameChangeRequest::setLeaseExpiresOn(const std::string& value) {
+ try {
+ lease_expires_on_ = isc::util::timeFromText64(value);
+ } catch(...) {
+ // We were given an invalid string, so throw.
+ isc_throw(NcrMessageError,
+ "Invalid date-time string: [" << value << "]");
+ }
+
+}
+
+void NameChangeRequest::setLeaseExpiresOn(isc::data::ConstElementPtr element) {
+ // Pull out the string value and pass it into the string setter.
+ setLeaseExpiresOn(element->stringValue());
+}
+
+void
+NameChangeRequest::setLeaseLength(const uint32_t value) {
+ lease_length_ = value;
+}
+
+void
+NameChangeRequest::setLeaseLength(isc::data::ConstElementPtr element) {
+ long value = -1;
+ try {
+ // Get the element's integer value.
+ value = element->intValue();
+ } catch (isc::data::TypeError& ex) {
+ // We expect a integer Element type, don't have one.
+ isc_throw(NcrMessageError,
+ "Wrong data type for lease_length: " << ex.what());
+ }
+
+ // Make sure we the range is correct and value is positive.
+ if (value > std::numeric_limits<uint32_t>::max()) {
+ isc_throw(NcrMessageError, "lease_length value " << value <<
+ "is too large for unsigned 32-bit integer.");
+ }
+ if (value < 0) {
+ isc_throw(NcrMessageError, "lease_length value " << value <<
+ "is negative. It must greater than or equal to zero ");
+ }
+
+ // Good to go, make the assignment.
+ setLeaseLength(static_cast<uint32_t>(value));
+}
+
+void
+NameChangeRequest::setStatus(const NameChangeStatus value) {
+ status_ = value;
+}
+
+std::string
+NameChangeRequest::toText() const {
+ std::ostringstream stream;
+
+ stream << "Type: " << static_cast<int>(change_type_) << " (";
+ switch (change_type_) {
+ case CHG_ADD:
+ stream << "CHG_ADD)\n";
+ break;
+ case CHG_REMOVE:
+ stream << "CHG_REMOVE)\n";
+ break;
+ default:
+ // Shouldn't be possible.
+ stream << "Invalid Value\n";
+ }
+
+ stream << "Forward Change: " << (forward_change_ ? "yes" : "no")
+ << std::endl
+ << "Reverse Change: " << (reverse_change_ ? "yes" : "no")
+ << std::endl
+ << "FQDN: [" << fqdn_ << "]" << std::endl
+ << "IP Address: [" << ip_address_ << "]" << std::endl
+ << "DHCID: [" << dhcid_.toStr() << "]" << std::endl
+ << "Lease Expires On: " << getLeaseExpiresOnStr() << std::endl
+ << "Lease Length: " << lease_length_ << std::endl;
+
+ return (stream.str());
+}
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
diff --git a/src/bin/d2/ncr_msg.h b/src/bin/d2/ncr_msg.h
new file mode 100644
index 0000000..b3a4153
--- /dev/null
+++ b/src/bin/d2/ncr_msg.h
@@ -0,0 +1,487 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef NCR_MSG_H
+#define NCR_MSG_H
+
+/// @file ncr_msg.h
+/// @brief This file provides the classes needed to embody, compose, and
+/// decompose DNS update requests that are sent by DHCP-DDNS clients to
+/// DHCP-DDNS. These requests are referred to as NameChangeRequests.
+
+#include <cc/data.h>
+#include <exceptions/exceptions.h>
+#include <util/buffer.h>
+#include <util/encode/hex.h>
+#include <util/time_utilities.h>
+
+#include <time.h>
+#include <string>
+
+namespace isc {
+namespace d2 {
+
+/// @brief Exception thrown when NameChangeRequest marshalling error occurs.
+class NcrMessageError : public isc::Exception {
+public:
+ NcrMessageError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Defines the types of DNS updates that can be requested.
+enum NameChangeType {
+ CHG_ADD,
+ CHG_REMOVE
+};
+
+/// @brief Defines the runtime processing status values for requests.
+enum NameChangeStatus {
+ ST_NEW,
+ ST_PENDING,
+ ST_COMPLETED,
+ ST_FAILED,
+};
+
+/// @brief Defines the list of data wire formats supported.
+enum NameChangeFormat {
+ FMT_JSON
+};
+
+/// @brief Container class for handling the DHCID value within a
+/// NameChangeRequest. It provides conversion to and from string for JSON
+/// formatting, but stores the data internally as unsigned bytes.
+class D2Dhcid {
+public:
+ /// @brief Default constructor
+ D2Dhcid();
+
+ /// @brief Constructor - Creates a new instance, populated by converting
+ /// a given string of digits into an array of unsigned bytes.
+ ///
+ /// @param data is a string of hexadecimal digits. The format is simply
+ /// a contiguous stream of digits, with no delimiters. For example a string
+ /// containing "14A3" converts to a byte array containing: 0x14, 0xA3.
+ ///
+ /// @throw throws a NcrMessageError if the input data contains non-digits
+ /// or there is an odd number of digits.
+ D2Dhcid(const std::string& data);
+
+ /// @brief Returns the DHCID value as a string of hexadecimal digits.
+ ///
+ /// @return returns a string containing a contiguous stream of digits.
+ std::string toStr() const;
+
+ /// @brief Sets the DHCID value based on the given string.
+ ///
+ /// @param data is a string of hexadecimal digits. The format is simply
+ /// a contiguous stream of digits, with no delimiters. For example a string
+ /// containing "14A3" converts to a byte array containing: 0x14, 0xA3.
+ ///
+ /// @throw throws a NcrMessageError if the input data contains non-digits
+ /// or there is an odd number of digits.
+ void fromStr(const std::string& data);
+
+ /// @brief Returns a reference to the DHCID byte vector.
+ ///
+ /// @return returns a reference to the vector.
+ const std::vector<uint8_t>& getBytes() {
+ return (bytes_);
+ }
+
+private:
+ /// @brief Storage for the DHCID value in unsigned bytes.
+ std::vector<uint8_t> bytes_;
+};
+
+class NameChangeRequest;
+/// @brief Defines a pointer to a NameChangeRequest.
+typedef boost::shared_ptr<NameChangeRequest> NameChangeRequestPtr;
+
+/// @brief Defines a map of Elements, keyed by their string name.
+typedef std::map<std::string, isc::data::ConstElementPtr> ElementMap;
+
+/// @brief Represents a DHCP-DDNS client request.
+/// This class is used by DHCP-DDNS clients (e.g. DHCP4, DHCP6) to
+/// request DNS updates. Each message contains a single DNS change (either an
+/// add/update or a remove) for a single FQDN. It provides marshalling services
+/// for moving instances to and from the wire. Currently, the only format
+/// supported is JSON, however the class provides an interface such that other
+/// formats can be readily supported.
+class NameChangeRequest {
+public:
+ /// @brief Default Constructor.
+ NameChangeRequest();
+
+ /// @brief Constructor. Full constructor, which provides parameters for
+ /// all of the class members, except status.
+ ///
+ /// @param change_type the type of change (Add or Update)
+ /// @param forward_change indicates if this change should be sent to forward
+ /// DNS servers.
+ /// @param reverse_change indicates if this change should be sent to reverse
+ /// DNS servers.
+ /// @param fqdn the domain name whose pointer record(s) should be
+ /// updated.
+ /// @param ip_address the ip address leased to the given FQDN.
+ /// @param dhcid the lease client's unique DHCID.
+ /// @param lease_expires_on a timestamp containing the date/time the lease
+ /// expires.
+ /// @param lease_length the amount of time in seconds for which the
+ /// lease is valid (TTL).
+ NameChangeRequest(const NameChangeType change_type,
+ const bool forward_change, const bool reverse_change,
+ const std::string& fqdn, const std::string& ip_address,
+ const D2Dhcid& dhcid,
+ const uint64_t lease_expires_on,
+ const uint32_t lease_length);
+
+ /// @brief Static method for creating a NameChangeRequest from a
+ /// buffer containing a marshalled request in a given format.
+ ///
+ /// When the format is:
+ ///
+ /// JSON: The buffer is expected to contain a two byte unsigned integer
+ /// which specified the length of the JSON text; followed by the JSON
+ /// text itself. This method attempts to extract "length" characters
+ /// from the buffer. This data is used to create a character string that
+ /// is than treated as JSON which is then parsed into the data needed
+ /// to create a request instance.
+ ///
+ /// (NOTE currently only JSON is supported.)
+ ///
+ /// @param format indicates the data format to use
+ /// @param buffer is the input buffer containing the marshalled request
+ ///
+ /// @return returns a pointer to the new NameChangeRequest
+ ///
+ /// @throw throws NcrMessageError if an error occurs creating new
+ /// request.
+ static NameChangeRequestPtr fromFormat(const NameChangeFormat format,
+ isc::util::InputBuffer& buffer);
+
+ /// @brief Instance method for marshalling the contents of the request
+ /// into the given buffer in the given format.
+ ///
+ /// When the format is:
+ ///
+ /// JSON: Upon completion, the buffer will contain a two byte unsigned
+ /// integer which specifies the length of the JSON text; followed by the
+ /// JSON text itself. The JSON text contains the names and values for all
+ /// the request data needed to reassemble the request on the receiving
+ /// end. The JSON text in the buffer is NOT null-terminated.
+ ///
+ /// (NOTE currently only JSON is supported.)
+ ///
+ /// @param format indicates the data format to use
+ /// @param buffer is the output buffer to which the request should be
+ /// marshalled.
+ void toFormat(const NameChangeFormat format,
+ isc::util::OutputBuffer& buffer) const;
+
+ /// @brief Static method for creating a NameChangeRequest from a
+ /// string containing a JSON rendition of a request.
+ ///
+ /// @param json is a string containing the JSON text
+ ///
+ /// @return returns a pointer to the new NameChangeRequest
+ ///
+ /// @throw throws NcrMessageError if an error occurs creating new request.
+ static NameChangeRequestPtr fromJSON(const std::string& json);
+
+ /// @brief Instance method for marshalling the contents of the request
+ /// into a string of JSON text.
+ ///
+ /// @return returns a string containing the JSON rendition of the request
+ std::string toJSON() const;
+
+ /// @brief Validates the content of a populated request. This method is
+ /// used by both the full constructor and from-wire marshalling to ensure
+ /// that the request is content valid. Currently it enforces the
+ /// following rules:
+ ///
+ /// - FQDN must not be blank.
+ /// - The IP address must be a valid address.
+ /// - The DHCID must not be blank.
+ /// - The lease expiration date must be a valid date/time.
+ /// - That at least one of the two direction flags, forward change and
+ /// reverse change is true.
+ ///
+ /// @todo This is an initial implementation which provides a minimal amount
+ /// of validation. FQDN, DHCID, and IP Address members are all currently
+ /// strings, these may be replaced with richer classes.
+ ///
+ /// @throw throws a NcrMessageError if the request content violates any
+ /// of the validation rules.
+ void validateContent();
+
+ /// @brief Fetches the request change type.
+ ///
+ /// @return returns the change type
+ NameChangeType getChangeType() const {
+ return (change_type_);
+ }
+
+ /// @brief Sets the change type to the given value.
+ ///
+ /// @param value is the NameChangeType value to assign to the request.
+ void setChangeType(const NameChangeType value);
+
+ /// @brief Sets the change type to the value of the given Element.
+ ///
+ /// @param element is an integer Element containing the change type value.
+ ///
+ /// @throw throws a NcrMessageError if the element is not an integer
+ /// Element or contains an invalid value.
+ void setChangeType(isc::data::ConstElementPtr element);
+
+ /// @brief Checks forward change flag.
+ ///
+ /// @return returns a true if the forward change flag is true.
+ bool isForwardChange() const {
+ return (forward_change_);
+ }
+
+ /// @brief Sets the forward change flag to the given value.
+ ///
+ /// @param value contains the new value to assign to the forward change
+ /// flag
+ void setForwardChange(const bool value);
+
+ /// @brief Sets the forward change flag to the value of the given Element.
+ ///
+ /// @param element is a boolean Element containing the forward change flag
+ /// value.
+ ///
+ /// @throw throws a NcrMessageError if the element is not a boolean
+ /// Element
+ void setForwardChange(isc::data::ConstElementPtr element);
+
+ /// @brief Checks reverse change flag.
+ ///
+ /// @return returns a true if the reverse change flag is true.
+ bool isReverseChange() const {
+ return (reverse_change_);
+ }
+
+ /// @brief Sets the reverse change flag to the given value.
+ ///
+ /// @param value contains the new value to assign to the reverse change
+ /// flag
+ void setReverseChange(const bool value);
+
+ /// @brief Sets the reverse change flag to the value of the given Element.
+ ///
+ /// @param element is a boolean Element containing the reverse change flag
+ /// value.
+ ///
+ /// @throw throws a NcrMessageError if the element is not a boolean
+ /// Element
+ void setReverseChange(isc::data::ConstElementPtr element);
+
+ /// @brief Fetches the request FQDN
+ ///
+ /// @return returns a string containing the FQDN
+ const std::string getFqdn() const {
+ return (fqdn_);
+ }
+
+ /// @brief Sets the FQDN to the given value.
+ ///
+ /// @param value contains the new value to assign to the FQDN
+ void setFqdn(const std::string& value);
+
+ /// @brief Sets the FQDN to the value of the given Element.
+ ///
+ /// @param element is a string Element containing the FQDN
+ ///
+ /// @throw throws a NcrMessageError if the element is not a string
+ /// Element
+ void setFqdn(isc::data::ConstElementPtr element);
+
+ /// @brief Fetches the request IP address.
+ ///
+ /// @return returns a string containing the IP address
+ const std::string& getIpAddress() const {
+ return (ip_address_);
+ }
+
+ /// @brief Sets the IP address to the given value.
+ ///
+ /// @param value contains the new value to assign to the IP address
+ void setIpAddress(const std::string& value);
+
+ /// @brief Sets the IP address to the value of the given Element.
+ ///
+ /// @param element is a string Element containing the IP address
+ ///
+ /// @throw throws a NcrMessageError if the element is not a string
+ /// Element
+ void setIpAddress(isc::data::ConstElementPtr element);
+
+ /// @brief Fetches the request DHCID
+ ///
+ /// @return returns a reference to the request's D2Dhcid
+ const D2Dhcid& getDhcid() const {
+ return (dhcid_);
+ }
+
+ /// @brief Sets the DHCID based on the given string value.
+ ///
+ /// @param value is a string of hexadecimal digits. The format is simply
+ /// a contiguous stream of digits, with no delimiters. For example a string
+ /// containing "14A3" converts to a byte array containing: 0x14, 0xA3.
+ ///
+ /// @throw throws a NcrMessageError if the input data contains non-digits
+ /// or there is an odd number of digits.
+ void setDhcid(const std::string& value);
+
+ /// @brief Sets the DHCID based on the value of the given Element.
+ ///
+ /// @param element is a string Element containing the string of hexadecimal
+ /// digits. (See setDhcid(std::string&) above.)
+ ///
+ /// @throw throws a NcrMessageError if the input data contains non-digits
+ /// or there is an odd number of digits.
+ void setDhcid(isc::data::ConstElementPtr element);
+
+ /// @brief Fetches the request lease expiration
+ ///
+ /// @return returns the lease expiration as the number of seconds since
+ /// the (00:00:00 January 1, 1970)
+ uint64_t getLeaseExpiresOn() const {
+ return (lease_expires_on_);
+ }
+
+ /// @brief Fetches the request lease expiration as string.
+ ///
+ /// The format of the string returned is:
+ ///
+ /// YYYYMMDDHHmmSS
+ ///
+ /// Example: 18:54:54 June 26, 2013 would be: 20130626185455
+ /// NOTE This is always UTC time.
+ ///
+ /// @return returns a ISO date-time string of the lease expiration.
+ std::string getLeaseExpiresOnStr() const;
+
+ /// @brief Sets the lease expiration based on the given string.
+ ///
+ /// @param value is an date-time string from which to set the
+ /// lease expiration. The format of the input is:
+ ///
+ /// YYYYMMDDHHmmSS
+ ///
+ /// Example: 18:54:54 June 26, 2013 would be: 20130626185455
+ /// NOTE This is always UTC time.
+ ///
+ /// @throw throws a NcrMessageError if the ISO string is invalid.
+ void setLeaseExpiresOn(const std::string& value);
+
+ /// @brief Sets the lease expiration based on the given Element.
+ ///
+ /// @param element is string Element containing a date-time string.
+ ///
+ /// @throw throws a NcrMessageError if the element is not a string
+ /// Element, or if the element value is an invalid date-time string.
+ void setLeaseExpiresOn(isc::data::ConstElementPtr element);
+
+ /// @brief Fetches the request lease length.
+ ///
+ /// @return returns an integer containing the lease length
+ uint32_t getLeaseLength() const {
+ return (lease_length_);
+ }
+
+ /// @brief Sets the lease length to the given value.
+ ///
+ /// @param value contains the new value to assign to the lease length
+ void setLeaseLength(const uint32_t value);
+
+ /// @brief Sets the lease length to the value of the given Element.
+ ///
+ /// @param element is a integer Element containing the lease length
+ ///
+ /// @throw throws a NcrMessageError if the element is not a string
+ /// Element
+ void setLeaseLength(isc::data::ConstElementPtr element);
+
+ /// @brief Fetches the request status.
+ ///
+ /// @return returns the request status as a NameChangeStatus
+ NameChangeStatus getStatus() const {
+ return (status_);
+ }
+
+ /// @brief Sets the request status to the given value.
+ ///
+ /// @param value contains the new value to assign to request status
+ void setStatus(const NameChangeStatus value);
+
+ /// @brief Given a name, finds and returns an element from a map of
+ /// elements.
+ ///
+ /// @param name is the name of the desired element
+ /// @param element_map is the map of elements to search
+ ///
+ /// @return returns a pointer to the element if located
+ /// @throw throws a NcrMessageError if the element cannot be found within
+ /// the map
+ isc::data::ConstElementPtr getElement(const std::string& name,
+ const ElementMap& element_map) const;
+
+ /// @brief Returns a text rendition of the contents of the request.
+ /// This method is primarily for logging purposes.
+ ///
+ /// @return returns a string containing the text.
+ std::string toText() const;
+
+private:
+ /// @brief Denotes the type of this change as either an Add or a Remove.
+ NameChangeType change_type_;
+
+ /// @brief Indicates if this change should sent to forward DNS servers.
+ bool forward_change_;
+
+ /// @brief Indicates if this change should sent to reverse DNS servers.
+ bool reverse_change_;
+
+ /// @brief The domain name whose DNS entry(ies) are to be updated.
+ /// @todo Currently, this is a std::string but may be replaced with
+ /// dns::Name which provides additional validation and domain name
+ /// manipulation.
+ std::string fqdn_;
+
+ /// @brief The ip address leased to the FQDN.
+ std::string ip_address_;
+
+ /// @brief The lease client's unique DHCID.
+ /// @todo Currently, this is uses D2Dhcid it but may be replaced with
+ /// dns::DHCID which provides additional validation.
+ D2Dhcid dhcid_;
+
+ /// @brief The date-time the lease expires.
+ uint64_t lease_expires_on_;
+
+ /// @brief The amount of time in seconds for which the lease is valid (TTL).
+ uint32_t lease_length_;
+
+ /// @brief The processing status of the request. Used internally.
+ NameChangeStatus status_;
+};
+
+
+}; // end of isc::d2 namespace
+}; // end of isc namespace
+
+#endif
diff --git a/src/bin/d2/tests/.gitignore b/src/bin/d2/tests/.gitignore
index 9a1b143..3e1a1b7 100644
--- a/src/bin/d2/tests/.gitignore
+++ b/src/bin/d2/tests/.gitignore
@@ -1 +1,2 @@
/d2_unittests
+/test_data_files_config.h
diff --git a/src/bin/d2/tests/Makefile.am b/src/bin/d2/tests/Makefile.am
index a7b37ce..4e888a9 100644
--- a/src/bin/d2/tests/Makefile.am
+++ b/src/bin/d2/tests/Makefile.am
@@ -56,15 +56,24 @@ d2_unittests_SOURCES += ../d_process.h
d2_unittests_SOURCES += ../d_controller.cc ../d2_controller.h
d2_unittests_SOURCES += ../d2_process.cc ../d2_process.h
d2_unittests_SOURCES += ../d2_controller.cc ../d2_controller.h
+d2_unittests_SOURCES += ../d_cfg_mgr.cc ../d_cfg_mgr.h
+d2_unittests_SOURCES += ../d2_config.cc ../d2_config.h
+d2_unittests_SOURCES += ../d2_cfg_mgr.cc ../d2_cfg_mgr.h
d2_unittests_SOURCES += ../d2_update_message.cc ../d2_update_message.h
d2_unittests_SOURCES += ../d2_zone.cc ../d2_zone.h
+d2_unittests_SOURCES += ../dns_client.cc ../dns_client.h
+d2_unittests_SOURCES += ../ncr_msg.cc ../ncr_msg.h
d2_unittests_SOURCES += d_test_stubs.cc d_test_stubs.h
d2_unittests_SOURCES += d2_unittests.cc
d2_unittests_SOURCES += d2_process_unittests.cc
d2_unittests_SOURCES += d_controller_unittests.cc
d2_unittests_SOURCES += d2_controller_unittests.cc
+d2_unittests_SOURCES += d_cfg_mgr_unittests.cc
+d2_unittests_SOURCES += d2_cfg_mgr_unittests.cc
d2_unittests_SOURCES += d2_update_message_unittests.cc
d2_unittests_SOURCES += d2_zone_unittests.cc
+d2_unittests_SOURCES += dns_client_unittests.cc
+d2_unittests_SOURCES += ncr_unittests.cc
nodist_d2_unittests_SOURCES = ../d2_messages.h ../d2_messages.cc
d2_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
@@ -72,10 +81,14 @@ d2_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
d2_unittests_LDADD = $(GTEST_LDADD)
d2_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
d2_unittests_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/asiodns/libb10-asiodns.la
d2_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
d2_unittests_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
d2_unittests_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/dhcpsrv/libb10-dhcpsrv.la
d2_unittests_LDADD += $(top_builddir)/src/lib/dns/libb10-dns++.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
+
endif
noinst_PROGRAMS = $(TESTS)
diff --git a/src/bin/d2/tests/d2_cfg_mgr_unittests.cc b/src/bin/d2/tests/d2_cfg_mgr_unittests.cc
new file mode 100644
index 0000000..9f5a660
--- /dev/null
+++ b/src/bin/d2/tests/d2_cfg_mgr_unittests.cc
@@ -0,0 +1,1238 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config/module_spec.h>
+#include <d2/d2_config.h>
+#include <d2/d2_cfg_mgr.h>
+#include <d_test_stubs.h>
+#include <test_data_files_config.h>
+
+#include <boost/foreach.hpp>
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::d2;
+
+namespace {
+
+std::string specfile(const std::string& name) {
+ return (std::string(D2_SRC_DIR) + "/" + name);
+}
+
+/// @brief Test fixture class for testing D2CfgMgr class.
+/// It maintains an member instance of D2CfgMgr and provides methods for
+/// converting JSON strings to configuration element sets, checking parse
+/// results, and accessing the configuration context.
+class D2CfgMgrTest : public ConfigParseTest {
+public:
+
+ /// @brief Constructor
+ D2CfgMgrTest():cfg_mgr_(new D2CfgMgr) {
+ }
+
+ /// @brief Destructor
+ ~D2CfgMgrTest() {
+ }
+
+ /// @brief Configuration manager instance.
+ D2CfgMgrPtr cfg_mgr_;
+};
+
+/// @brief Tests that the spec file is valid.
+/// Verifies that the BIND10 DHCP-DDNS configuration specification file
+// is valid.
+TEST(D2SpecTest, basicSpecTest) {
+ ASSERT_NO_THROW(isc::config::
+ moduleSpecFromFile(specfile("dhcp-ddns.spec")));
+}
+
+/// @brief Convenience function which compares the contents of the given
+/// DnsServerInfo against the given set of values.
+///
+/// It is structured in such a way that each value is checked, and output
+/// is generate for all that do not match.
+///
+/// @param server is a pointer to the server to check against.
+/// @param hostname is the value to compare against server's hostname_.
+/// @param ip_address is the string value to compare against server's
+/// ip_address_.
+/// @param port is the value to compare against server's port.
+///
+/// @return returns true if there is a match across the board, otherwise it
+/// returns false.
+bool checkServer(DnsServerInfoPtr server, const char* hostname,
+ const char *ip_address, uint32_t port)
+{
+ // Return value, assume its a match.
+ bool result = true;
+
+ if (!server) {
+ EXPECT_TRUE(server);
+ return false;
+ }
+
+ // Check hostname.
+ if (server->getHostname() != hostname) {
+ EXPECT_EQ(hostname, server->getHostname());
+ result = false;
+ }
+
+ // Check IP address.
+ if (server->getIpAddress().toText() != ip_address) {
+ EXPECT_EQ(ip_address, server->getIpAddress().toText());
+ result = false;
+ }
+
+ // Check port.
+ if (server->getPort() != port) {
+ EXPECT_EQ (port, server->getPort());
+ result = false;
+ }
+
+ return (result);
+}
+
+/// @brief Convenience function which compares the contents of the given
+/// TSIGKeyInfo against the given set of values.
+///
+/// It is structured in such a way that each value is checked, and output
+/// is generate for all that do not match.
+///
+/// @param key is a pointer to the key to check against.
+/// @param name is the value to compare against key's name_.
+/// @param algorithm is the string value to compare against key's algorithm.
+/// @param secret is the value to compare against key's secret.
+///
+/// @return returns true if there is a match across the board, otherwise it
+/// returns false.
+bool checkKey(TSIGKeyInfoPtr key, const char* name,
+ const char *algorithm, const char* secret)
+{
+ // Return value, assume its a match.
+ bool result = true;
+ if (!key) {
+ EXPECT_TRUE(key);
+ return false;
+ }
+
+ // Check name.
+ if (key->getName() != name) {
+ EXPECT_EQ(name, key->getName());
+ result = false;
+ }
+
+ // Check algorithm.
+ if (key->getAlgorithm() != algorithm) {
+ EXPECT_EQ(algorithm, key->getAlgorithm());
+ result = false;
+ }
+
+ // Check secret.
+ if (key->getSecret() != secret) {
+ EXPECT_EQ (secret, key->getSecret());
+ result = false;
+ }
+
+ return (result);
+}
+
+/// @brief Test fixture class for testing DnsServerInfo parsing.
+class TSIGKeyInfoTest : public ConfigParseTest {
+public:
+
+ /// @brief Constructor
+ TSIGKeyInfoTest() {
+ reset();
+ }
+
+ /// @brief Destructor
+ ~TSIGKeyInfoTest() {
+ }
+
+ /// @brief Wipe out the current storage and parser and replace
+ /// them with new ones.
+ void reset() {
+ keys_.reset(new TSIGKeyInfoMap());
+ parser_.reset(new TSIGKeyInfoParser("test", keys_));
+ }
+
+ /// @brief Storage for "committing" keys.
+ TSIGKeyInfoMapPtr keys_;
+
+ /// @brief Pointer to the current parser instance.
+ isc::dhcp::ParserPtr parser_;
+};
+
+/// @brief Test fixture class for testing DnsServerInfo parsing.
+class DnsServerInfoTest : public ConfigParseTest {
+public:
+
+ /// @brief Constructor
+ DnsServerInfoTest() {
+ reset();
+ }
+
+ /// @brief Destructor
+ ~DnsServerInfoTest() {
+ }
+
+ /// @brief Wipe out the current storage and parser and replace
+ /// them with new ones.
+ void reset() {
+ servers_.reset(new DnsServerInfoStorage());
+ parser_.reset(new DnsServerInfoParser("test", servers_));
+ }
+
+ /// @brief Storage for "committing" servers.
+ DnsServerInfoStoragePtr servers_;
+
+ /// @brief Pointer to the current parser instance.
+ isc::dhcp::ParserPtr parser_;
+};
+
+
+/// @brief Test fixture class for testing DDnsDomain parsing.
+class DdnsDomainTest : public ConfigParseTest {
+public:
+
+ /// @brief Constructor
+ DdnsDomainTest() {
+ reset();
+ }
+
+ /// @brief Destructor
+ ~DdnsDomainTest() {
+ }
+
+ /// @brief Wipe out the current storage and parser and replace
+ /// them with new ones.
+ void reset() {
+ keys_.reset(new TSIGKeyInfoMap());
+ domains_.reset(new DdnsDomainMap());
+ parser_.reset(new DdnsDomainParser("test", domains_, keys_));
+ }
+
+ /// @brief Add TSIGKeyInfos to the key map
+ ///
+ /// @param name the name of the key
+ /// @param algorithm the algorithm of the key
+ /// @param secret the secret value of the key
+ void addKey(const std::string& name, const std::string& algorithm,
+ const std::string& secret) {
+ TSIGKeyInfoPtr key_info(new TSIGKeyInfo(name, algorithm, secret));
+ (*keys_)[name]=key_info;
+ }
+
+ /// @brief Storage for "committing" domains.
+ DdnsDomainMapPtr domains_;
+
+ /// @brief Storage for TSIGKeys
+ TSIGKeyInfoMapPtr keys_;
+
+ /// @brief Pointer to the current parser instance.
+ isc::dhcp::ParserPtr parser_;
+};
+
+/// @brief Tests the enforcement of data validation when parsing TSIGKeyInfos.
+/// It verifies that:
+/// 1. Name cannot be blank.
+/// 2. Algorithm cannot be blank.
+/// 3. Secret cannot be blank.
+/// @TODO TSIG keys are not fully functional. Only basic validation is
+/// currently supported. This test will need to expand as they evolve.
+TEST_F(TSIGKeyInfoTest, invalidEntryTests) {
+ // Config with a blank name entry.
+ std::string config = "{"
+ " \"name\": \"\" , "
+ " \"algorithm\": \"md5\" , "
+ " \"secret\": \"0123456789\" "
+ "}";
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that build succeeds but commit fails on blank name.
+ EXPECT_NO_THROW(parser_->build(config_set_));
+ EXPECT_THROW(parser_->commit(), D2CfgError);
+
+ // Config with a blank algorithm entry.
+ config = "{"
+ " \"name\": \"d2_key_one\" , "
+ " \"algorithm\": \"\" , "
+ " \"secret\": \"0123456789\" "
+ "}";
+
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that build succeeds but commit fails on blank algorithm.
+ EXPECT_NO_THROW(parser_->build(config_set_));
+ EXPECT_THROW(parser_->commit(), D2CfgError);
+
+ // Config with a blank secret entry.
+ config = "{"
+ " \"name\": \"d2_key_one\" , "
+ " \"algorithm\": \"md5\" , "
+ " \"secret\": \"\" "
+ "}";
+
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that build succeeds but commit fails on blank secret.
+ EXPECT_NO_THROW(parser_->build(config_set_));
+ EXPECT_THROW(parser_->commit(), D2CfgError);
+}
+
+/// @brief Verifies that TSIGKeyInfo parsing creates a proper TSIGKeyInfo
+/// when given a valid combination of entries.
+TEST_F(TSIGKeyInfoTest, validEntryTests) {
+ // Valid entries for TSIG key, all items are required.
+ std::string config = "{"
+ " \"name\": \"d2_key_one\" , "
+ " \"algorithm\": \"md5\" , "
+ " \"secret\": \"0123456789\" "
+ "}";
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that it builds and commits without throwing.
+ ASSERT_NO_THROW(parser_->build(config_set_));
+ ASSERT_NO_THROW(parser_->commit());
+
+ // Verify the correct number of keys are present
+ int count = keys_->size();
+ EXPECT_EQ(1, count);
+
+ // Find the key and retrieve it.
+ TSIGKeyInfoMap::iterator gotit = keys_->find("d2_key_one");
+ ASSERT_TRUE(gotit != keys_->end());
+ TSIGKeyInfoPtr& key = gotit->second;
+
+ // Verify the key contents.
+ EXPECT_TRUE(checkKey(key, "d2_key_one", "md5", "0123456789"));
+}
+
+/// @brief Verifies that attempting to parse an invalid list of TSIGKeyInfo
+/// entries is detected.
+TEST_F(TSIGKeyInfoTest, invalidTSIGKeyList) {
+ // Construct a list of keys with an invalid key entry.
+ std::string config = "["
+
+ " { \"name\": \"key1\" , "
+ " \"algorithm\": \"algo1\" ,"
+ " \"secret\": \"secret11\" "
+ " },"
+ " { \"name\": \"key2\" , "
+ " \"algorithm\": \"\" ,"
+ " \"secret\": \"secret12\" "
+ " },"
+ " { \"name\": \"key3\" , "
+ " \"algorithm\": \"algo3\" ,"
+ " \"secret\": \"secret13\" "
+ " }"
+ " ]";
+
+ ASSERT_TRUE(fromJSON(config));
+
+ // Create the list parser.
+ isc::dhcp::ParserPtr parser;
+ ASSERT_NO_THROW(parser.reset(new TSIGKeyInfoListParser("test", keys_)));
+
+ // Verify that the list builds without errors.
+ ASSERT_NO_THROW(parser->build(config_set_));
+
+ // Verify that the list commit fails.
+ EXPECT_THROW(parser->commit(), D2CfgError);
+}
+
+/// @brief Verifies that attempting to parse an invalid list of TSIGKeyInfo
+/// entries is detected.
+TEST_F(TSIGKeyInfoTest, duplicateTSIGKey) {
+ // Construct a list of keys with an invalid key entry.
+ std::string config = "["
+
+ " { \"name\": \"key1\" , "
+ " \"algorithm\": \"algo1\" ,"
+ " \"secret\": \"secret11\" "
+ " },"
+ " { \"name\": \"key2\" , "
+ " \"algorithm\": \"algo2\" ,"
+ " \"secret\": \"secret12\" "
+ " },"
+ " { \"name\": \"key1\" , "
+ " \"algorithm\": \"algo3\" ,"
+ " \"secret\": \"secret13\" "
+ " }"
+ " ]";
+
+ ASSERT_TRUE(fromJSON(config));
+
+ // Create the list parser.
+ isc::dhcp::ParserPtr parser;
+ ASSERT_NO_THROW(parser.reset(new TSIGKeyInfoListParser("test", keys_)));
+
+ // Verify that the list builds without errors.
+ ASSERT_NO_THROW(parser->build(config_set_));
+
+ // Verify that the list commit fails.
+ EXPECT_THROW(parser->commit(), D2CfgError);
+}
+
+/// @brief Verifies a valid list of TSIG Keys parses correctly.
+TEST_F(TSIGKeyInfoTest, validTSIGKeyList) {
+ // Construct a valid list of keys.
+ std::string config = "["
+
+ " { \"name\": \"key1\" , "
+ " \"algorithm\": \"algo1\" ,"
+ " \"secret\": \"secret1\" "
+ " },"
+ " { \"name\": \"key2\" , "
+ " \"algorithm\": \"algo2\" ,"
+ " \"secret\": \"secret2\" "
+ " },"
+ " { \"name\": \"key3\" , "
+ " \"algorithm\": \"algo3\" ,"
+ " \"secret\": \"secret3\" "
+ " }"
+ " ]";
+
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that the list builds and commits without errors.
+ // Create the list parser.
+ isc::dhcp::ParserPtr parser;
+ ASSERT_NO_THROW(parser.reset(new TSIGKeyInfoListParser("test", keys_)));
+ ASSERT_NO_THROW(parser->build(config_set_));
+ ASSERT_NO_THROW(parser->commit());
+
+ // Verify the correct number of keys are present
+ int count = keys_->size();
+ ASSERT_EQ(count, 3);
+
+ // Find the 1st key and retrieve it.
+ TSIGKeyInfoMap::iterator gotit = keys_->find("key1");
+ ASSERT_TRUE(gotit != keys_->end());
+ TSIGKeyInfoPtr& key = gotit->second;
+
+ // Verify the key contents.
+ EXPECT_TRUE(checkKey(key, "key1", "algo1", "secret1"));
+
+ // Find the 2nd key and retrieve it.
+ gotit = keys_->find("key2");
+ ASSERT_TRUE(gotit != keys_->end());
+ key = gotit->second;
+
+ // Verify the key contents.
+ EXPECT_TRUE(checkKey(key, "key2", "algo2", "secret2"));
+
+ // Find the 3rd key and retrieve it.
+ gotit = keys_->find("key3");
+ ASSERT_TRUE(gotit != keys_->end());
+ key = gotit->second;
+
+ // Verify the key contents.
+ EXPECT_TRUE(checkKey(key, "key3", "algo3", "secret3"));
+}
+
+/// @brief Tests the enforcement of data validation when parsing DnsServerInfos.
+/// It verifies that:
+/// 1. Specifying both a hostname and an ip address is not allowed.
+/// 2. Specifying both blank a hostname and blank ip address is not allowed.
+/// 3. Specifying a negative port number is not allowed.
+TEST_F(DnsServerInfoTest, invalidEntryTests) {
+ // Create a config in which both host and ip address are supplied.
+ // Verify that it builds without throwing but commit fails.
+ std::string config = "{ \"hostname\": \"pegasus.tmark\", "
+ " \"ip_address\": \"127.0.0.1\" } ";
+ ASSERT_TRUE(fromJSON(config));
+ EXPECT_NO_THROW(parser_->build(config_set_));
+ EXPECT_THROW(parser_->commit(), D2CfgError);
+
+ // Neither host nor ip address supplied
+ // Verify that it builds without throwing but commit fails.
+ config = "{ \"hostname\": \"\", "
+ " \"ip_address\": \"\" } ";
+ ASSERT_TRUE(fromJSON(config));
+ EXPECT_NO_THROW(parser_->build(config_set_));
+ EXPECT_THROW(parser_->commit(), D2CfgError);
+
+ // Create a config with a negative port number.
+ // Verify that build fails.
+ config = "{ \"ip_address\": \"192.168.5.6\" ,"
+ " \"port\": -100 }";
+ ASSERT_TRUE(fromJSON(config));
+ EXPECT_THROW (parser_->build(config_set_), isc::BadValue);
+}
+
+
+/// @brief Verifies that DnsServerInfo parsing creates a proper DnsServerInfo
+/// when given a valid combination of entries.
+/// It verifies that:
+/// 1. A DnsServerInfo entry is correctly made, when given only a hostname.
+/// 2. A DnsServerInfo entry is correctly made, when given ip address and port.
+/// 3. A DnsServerInfo entry is correctly made, when given only an ip address.
+TEST_F(DnsServerInfoTest, validEntryTests) {
+ // Valid entries for dynamic host
+ std::string config = "{ \"hostname\": \"pegasus.tmark\" }";
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that it builds and commits without throwing.
+ ASSERT_NO_THROW(parser_->build(config_set_));
+ ASSERT_NO_THROW(parser_->commit());
+
+ // Verify the correct number of servers are present
+ int count = servers_->size();
+ EXPECT_EQ(1, count);
+
+ // Verify the server exists and has the correct values.
+ DnsServerInfoPtr server = (*servers_)[0];
+ EXPECT_TRUE(checkServer(server, "pegasus.tmark",
+ DnsServerInfo::EMPTY_IP_STR,
+ DnsServerInfo::STANDARD_DNS_PORT));
+
+ // Start over for a new test.
+ reset();
+
+ // Valid entries for static ip
+ config = " { \"ip_address\": \"127.0.0.1\" , "
+ " \"port\": 100 }";
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that it builds and commits without throwing.
+ ASSERT_NO_THROW(parser_->build(config_set_));
+ ASSERT_NO_THROW(parser_->commit());
+
+ // Verify the correct number of servers are present
+ count = servers_->size();
+ EXPECT_EQ(1, count);
+
+ // Verify the server exists and has the correct values.
+ server = (*servers_)[0];
+ EXPECT_TRUE(checkServer(server, "", "127.0.0.1", 100));
+
+ // Start over for a new test.
+ reset();
+
+ // Valid entries for static ip, no port
+ config = " { \"ip_address\": \"192.168.2.5\" }";
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that it builds and commits without throwing.
+ ASSERT_NO_THROW(parser_->build(config_set_));
+ ASSERT_NO_THROW(parser_->commit());
+
+ // Verify the correct number of servers are present
+ count = servers_->size();
+ EXPECT_EQ(1, count);
+
+ // Verify the server exists and has the correct values.
+ server = (*servers_)[0];
+ EXPECT_TRUE(checkServer(server, "", "192.168.2.5",
+ DnsServerInfo::STANDARD_DNS_PORT));
+}
+
+/// @brief Verifies that attempting to parse an invalid list of DnsServerInfo
+/// entries is detected.
+TEST_F(ConfigParseTest, invalidServerList) {
+ // Construct a list of servers with an invalid server entry.
+ std::string config = "[ { \"hostname\": \"one.tmark\" }, "
+ "{ \"hostname\": \"\" }, "
+ "{ \"hostname\": \"three.tmark\" } ]";
+ ASSERT_TRUE(fromJSON(config));
+
+ // Create the server storage and list parser.
+ DnsServerInfoStoragePtr servers(new DnsServerInfoStorage());
+ isc::dhcp::ParserPtr parser;
+ ASSERT_NO_THROW(parser.reset(new DnsServerInfoListParser("test", servers)));
+
+ // Verify that the list builds without errors.
+ ASSERT_NO_THROW(parser->build(config_set_));
+
+ // Verify that the list commit fails.
+ EXPECT_THROW(parser->commit(), D2CfgError);
+}
+
+/// @brief Verifies that a list of DnsServerInfo entries parses correctly given
+/// a valid configuration.
+TEST_F(ConfigParseTest, validServerList) {
+ // Create a valid list of servers.
+ std::string config = "[ { \"hostname\": \"one.tmark\" }, "
+ "{ \"hostname\": \"two.tmark\" }, "
+ "{ \"hostname\": \"three.tmark\" } ]";
+ ASSERT_TRUE(fromJSON(config));
+
+ // Create the server storage and list parser.
+ DnsServerInfoStoragePtr servers(new DnsServerInfoStorage());
+ isc::dhcp::ParserPtr parser;
+ ASSERT_NO_THROW(parser.reset(new DnsServerInfoListParser("test", servers)));
+
+ // Verfiy that the list builds and commits without error.
+ ASSERT_NO_THROW(parser->build(config_set_));
+ ASSERT_NO_THROW(parser->commit());
+
+ // Verify that the server storage contains the correct number of servers.
+ int count = servers->size();
+ EXPECT_EQ(3, count);
+
+ // Verify the first server exists and has the correct values.
+ DnsServerInfoPtr server = (*servers)[0];
+ EXPECT_TRUE(checkServer(server, "one.tmark", DnsServerInfo::EMPTY_IP_STR,
+ DnsServerInfo::STANDARD_DNS_PORT));
+
+ // Verify the second server exists and has the correct values.
+ server = (*servers)[1];
+ EXPECT_TRUE(checkServer(server, "two.tmark", DnsServerInfo::EMPTY_IP_STR,
+ DnsServerInfo::STANDARD_DNS_PORT));
+
+ // Verify the third server exists and has the correct values.
+ server = (*servers)[2];
+ EXPECT_TRUE(checkServer(server, "three.tmark", DnsServerInfo::EMPTY_IP_STR,
+ DnsServerInfo::STANDARD_DNS_PORT));
+}
+
+/// @brief Tests the enforcement of data validation when parsing DdnsDomains.
+/// It verifies that:
+/// 1. Domain storage cannot be null when constructing a DdnsDomainParser.
+/// 2. The name entry is not optional.
+/// 3. The server list man not be empty.
+/// 4. That a mal-formed server entry is detected.
+/// 5. That an undefined key name is detected.
+TEST_F(DdnsDomainTest, invalidDdnsDomainEntry) {
+ // Verify that attempting to construct the parser with null storage fails.
+ DdnsDomainMapPtr domains;
+ ASSERT_THROW(isc::dhcp::ParserPtr(
+ new DdnsDomainParser("test", domains, keys_)), D2CfgError);
+
+ // Create a domain configuration without a name
+ std::string config = "{ \"key_name\": \"d2_key.tmark.org\" , "
+ " \"dns_servers\" : [ "
+ " { \"ip_address\": \"127.0.0.1\" , "
+ " \"port\": 100 },"
+ " { \"ip_address\": \"127.0.0.2\" , "
+ " \"port\": 200 },"
+ " { \"ip_address\": \"127.0.0.3\" , "
+ " \"port\": 300 } ] } ";
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that the domain configuration builds but commit fails.
+ ASSERT_NO_THROW(parser_->build(config_set_));
+ ASSERT_THROW(parser_->commit(), isc::dhcp::DhcpConfigError);
+
+ // Create a domain configuration with an empty server list.
+ config = "{ \"name\": \"tmark.org\" , "
+ " \"key_name\": \"d2_key.tmark.org\" , "
+ " \"dns_servers\" : [ "
+ " ] } ";
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that the domain configuration build fails.
+ ASSERT_THROW(parser_->build(config_set_), D2CfgError);
+
+ // Create a domain configuration with a mal-formed server entry.
+ config = "{ \"name\": \"tmark.org\" , "
+ " \"key_name\": \"d2_key.tmark.org\" , "
+ " \"dns_servers\" : [ "
+ " { \"ip_address\": \"127.0.0.3\" , "
+ " \"port\": -1 } ] } ";
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that the domain configuration build fails.
+ ASSERT_THROW(parser_->build(config_set_), isc::BadValue);
+
+ // Create a domain configuration without an defined key name
+ config = "{ \"name\": \"tmark.org\" , "
+ " \"key_name\": \"d2_key.tmark.org\" , "
+ " \"dns_servers\" : [ "
+ " { \"ip_address\": \"127.0.0.3\" , "
+ " \"port\": 300 } ] } ";
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that the domain configuration build succeeds but commit fails.
+ ASSERT_NO_THROW(parser_->build(config_set_));
+ ASSERT_THROW(parser_->commit(), D2CfgError);
+}
+
+/// @brief Verifies the basics of parsing DdnsDomains.
+/// It verifies that:
+/// 1. Valid construction of DdnsDomainParser functions.
+/// 2. Given a valid, configuration entry, DdnsDomainParser parses
+/// correctly.
+/// (It indirectly verifies the operation of DdnsDomainMap).
+TEST_F(DdnsDomainTest, ddnsDomainParsing) {
+ // Create a valid domain configuration entry containing three valid
+ // servers.
+ std::string config =
+ "{ \"name\": \"tmark.org\" , "
+ " \"key_name\": \"d2_key.tmark.org\" , "
+ " \"dns_servers\" : [ "
+ " { \"ip_address\": \"127.0.0.1\" , "
+ " \"port\": 100 },"
+ " { \"ip_address\": \"127.0.0.2\" , "
+ " \"port\": 200 },"
+ " { \"ip_address\": \"127.0.0.3\" , "
+ " \"port\": 300 } ] } ";
+ ASSERT_TRUE(fromJSON(config));
+
+ // Add a TSIG key to the test key map, so key validation will pass.
+ addKey("d2_key.tmark.org", "md5", "0123456789");
+
+ // Verify that the domain configuration builds and commits without error.
+ ASSERT_NO_THROW(parser_->build(config_set_));
+ ASSERT_NO_THROW(parser_->commit());
+
+ // Verify that the domain storage contains the correct number of domains.
+ int count = domains_->size();
+ EXPECT_EQ(1, count);
+
+ // Verify that the expected domain exists and can be retrieved from
+ // the storage.
+ DdnsDomainMap::iterator gotit = domains_->find("tmark.org");
+ ASSERT_TRUE(gotit != domains_->end());
+ DdnsDomainPtr& domain = gotit->second;
+
+ // Verify the name and key_name values.
+ EXPECT_EQ("tmark.org", domain->getName());
+ EXPECT_EQ("d2_key.tmark.org", domain->getKeyName());
+
+ // Verify that the server list exists and contains the correct number of
+ // servers.
+ const DnsServerInfoStoragePtr& servers = domain->getServers();
+ EXPECT_TRUE(servers);
+ count = servers->size();
+ EXPECT_EQ(3, count);
+
+ // Fetch each server and verify its contents.
+ DnsServerInfoPtr server = (*servers)[0];
+ EXPECT_TRUE(server);
+
+ EXPECT_TRUE(checkServer(server, "", "127.0.0.1", 100));
+
+ server = (*servers)[1];
+ EXPECT_TRUE(server);
+
+ EXPECT_TRUE(checkServer(server, "", "127.0.0.2", 200));
+
+ server = (*servers)[2];
+ EXPECT_TRUE(server);
+
+ EXPECT_TRUE(checkServer(server, "", "127.0.0.3", 300));
+}
+
+/// @brief Tests the fundamentals of parsing DdnsDomain lists.
+/// This test verifies that given a valid domain list configuration
+/// it will accurately parse and populate each domain in the list.
+TEST_F(DdnsDomainTest, DdnsDomainListParsing) {
+ // Create a valid domain list configuration, with two domains
+ // that have three servers each.
+ std::string config =
+ "[ "
+ "{ \"name\": \"tmark.org\" , "
+ " \"key_name\": \"d2_key.tmark.org\" , "
+ " \"dns_servers\" : [ "
+ " { \"ip_address\": \"127.0.0.1\" , "
+ " \"port\": 100 },"
+ " { \"ip_address\": \"127.0.0.2\" , "
+ " \"port\": 200 },"
+ " { \"ip_address\": \"127.0.0.3\" , "
+ " \"port\": 300 } ] } "
+ ", "
+ "{ \"name\": \"billcat.net\" , "
+ " \"key_name\": \"d2_key.billcat.net\" , "
+ " \"dns_servers\" : [ "
+ " { \"ip_address\": \"127.0.0.4\" , "
+ " \"port\": 400 },"
+ " { \"ip_address\": \"127.0.0.5\" , "
+ " \"port\": 500 },"
+ " { \"ip_address\": \"127.0.0.6\" , "
+ " \"port\": 600 } ] } "
+ "] ";
+
+ ASSERT_TRUE(fromJSON(config));
+
+ // Add keys to key map so key validation passes.
+ addKey("d2_key.tmark.org", "algo1", "secret1");
+ addKey("d2_key.billcat.net", "algo2", "secret2");
+
+ // Create the list parser
+ isc::dhcp::ParserPtr list_parser;
+ ASSERT_NO_THROW(list_parser.reset(
+ new DdnsDomainListParser("test", domains_, keys_)));
+
+ // Verify that the domain configuration builds and commits without error.
+ ASSERT_NO_THROW(list_parser->build(config_set_));
+ ASSERT_NO_THROW(list_parser->commit());
+
+ // Verify that the domain storage contains the correct number of domains.
+ int count = domains_->size();
+ EXPECT_EQ(2, count);
+
+ // Verify that the first domain exists and can be retrieved.
+ DdnsDomainMap::iterator gotit = domains_->find("tmark.org");
+ ASSERT_TRUE(gotit != domains_->end());
+ DdnsDomainPtr& domain = gotit->second;
+
+ // Verify the name and key_name values of the first domain.
+ EXPECT_EQ("tmark.org", domain->getName());
+ EXPECT_EQ("d2_key.tmark.org", domain->getKeyName());
+
+ // Verify the each of the first domain's servers
+ DnsServerInfoStoragePtr servers = domain->getServers();
+ EXPECT_TRUE(servers);
+ count = servers->size();
+ EXPECT_EQ(3, count);
+
+ DnsServerInfoPtr server = (*servers)[0];
+ EXPECT_TRUE(server);
+ EXPECT_TRUE(checkServer(server, "", "127.0.0.1", 100));
+
+ server = (*servers)[1];
+ EXPECT_TRUE(server);
+ EXPECT_TRUE(checkServer(server, "", "127.0.0.2", 200));
+
+ server = (*servers)[2];
+ EXPECT_TRUE(server);
+ EXPECT_TRUE(checkServer(server, "", "127.0.0.3", 300));
+
+ // Verify second domain
+ gotit = domains_->find("billcat.net");
+ ASSERT_TRUE(gotit != domains_->end());
+ domain = gotit->second;
+
+ // Verify the name and key_name values of the second domain.
+ EXPECT_EQ("billcat.net", domain->getName());
+ EXPECT_EQ("d2_key.billcat.net", domain->getKeyName());
+
+ // Verify the each of second domain's servers
+ servers = domain->getServers();
+ EXPECT_TRUE(servers);
+ count = servers->size();
+ EXPECT_EQ(3, count);
+
+ server = (*servers)[0];
+ EXPECT_TRUE(server);
+ EXPECT_TRUE(checkServer(server, "", "127.0.0.4", 400));
+
+ server = (*servers)[1];
+ EXPECT_TRUE(server);
+ EXPECT_TRUE(checkServer(server, "", "127.0.0.5", 500));
+
+ server = (*servers)[2];
+ EXPECT_TRUE(server);
+ EXPECT_TRUE(checkServer(server, "", "127.0.0.6", 600));
+}
+
+/// @brief Tests that a domain list configuration cannot contain duplicates.
+TEST_F(DdnsDomainTest, duplicateDomainTest) {
+ // Create a domain list configuration that contains two domains with
+ // the same name.
+ std::string config =
+ "[ "
+ "{ \"name\": \"tmark.org\" , "
+ " \"dns_servers\" : [ "
+ " { \"ip_address\": \"127.0.0.3\" , "
+ " \"port\": 300 } ] } "
+ ", "
+ "{ \"name\": \"tmark.org\" , "
+ " \"dns_servers\" : [ "
+ " { \"ip_address\": \"127.0.0.3\" , "
+ " \"port\": 300 } ] } "
+ "] ";
+ ASSERT_TRUE(fromJSON(config));
+
+ // Create the list parser
+ isc::dhcp::ParserPtr list_parser;
+ ASSERT_NO_THROW(list_parser.reset(
+ new DdnsDomainListParser("test", domains_, keys_)));
+
+ // Verify that the parse build succeeds but the commit fails.
+ ASSERT_NO_THROW(list_parser->build(config_set_));
+ ASSERT_THROW(list_parser->commit(), D2CfgError);
+}
+
+/// @brief Tests construction of D2CfgMgr
+/// This test verifies that a D2CfgMgr constructs properly.
+TEST(D2CfgMgr, construction) {
+ D2CfgMgr *cfg_mgr = NULL;
+
+ // Verify that configuration manager constructions without error.
+ ASSERT_NO_THROW(cfg_mgr = new D2CfgMgr());
+
+ // Verify that the context can be retrieved and is not null.
+ D2CfgContextPtr context;
+ ASSERT_NO_THROW(context = cfg_mgr->getD2CfgContext());
+ EXPECT_TRUE(context);
+
+ // Verify that the forward manager can be retrieved and is not null.
+ EXPECT_TRUE(context->getForwardMgr());
+
+ // Verify that the reverse manager can be retrieved and is not null.
+ EXPECT_TRUE(context->getReverseMgr());
+
+ // Verify that the manager can be destructed without error.
+ EXPECT_NO_THROW(delete cfg_mgr);
+}
+
+/// @brief Tests the parsing of a complete, valid DHCP-DDNS configuration.
+/// This tests passes the configuration into an instance of D2CfgMgr just
+/// as it would be done by d2_process in response to a configuration update
+/// event.
+TEST_F(D2CfgMgrTest, fullConfigTest) {
+ // Create a configuration with all of application level parameters, plus
+ // both the forward and reverse ddns managers. Both managers have two
+ // domains with three servers per domain.
+ std::string config = "{ "
+ "\"interface\" : \"eth1\" , "
+ "\"ip_address\" : \"192.168.1.33\" , "
+ "\"port\" : 88 , "
+ "\"tsig_keys\": ["
+ "{"
+ " \"name\": \"d2_key.tmark.org\" , "
+ " \"algorithm\": \"md5\" , "
+ " \"secret\": \"ssh-dont-tell\" "
+ "},"
+ "{"
+ " \"name\": \"d2_key.billcat.net\" , "
+ " \"algorithm\": \"md5\" , "
+ " \"secret\": \"ollie-ollie-in-free\" "
+ "}"
+ "],"
+ "\"forward_ddns\" : {"
+ "\"ddns_domains\": [ "
+ "{ \"name\": \"tmark.org\" , "
+ " \"key_name\": \"d2_key.tmark.org\" , "
+ " \"dns_servers\" : [ "
+ " { \"hostname\": \"one.tmark\" } , "
+ " { \"hostname\": \"two.tmark\" } , "
+ " { \"hostname\": \"three.tmark\"} "
+ " ] } "
+ ", "
+ "{ \"name\": \"billcat.net\" , "
+ " \"key_name\": \"d2_key.billcat.net\" , "
+ " \"dns_servers\" : [ "
+ " { \"hostname\": \"four.billcat\" } , "
+ " { \"hostname\": \"five.billcat\" } , "
+ " { \"hostname\": \"six.billcat\" } "
+ " ] } "
+ "] },"
+ "\"reverse_ddns\" : {"
+ "\"ddns_domains\": [ "
+ "{ \"name\": \" 0.168.192.in.addr.arpa.\" , "
+ " \"key_name\": \"d2_key.tmark.org\" , "
+ " \"dns_servers\" : [ "
+ " { \"hostname\": \"one.rev\" } , "
+ " { \"hostname\": \"two.rev\" } , "
+ " { \"hostname\": \"three.rev\" } "
+ " ] } "
+ ", "
+ "{ \"name\": \" 0.247.106.in.addr.arpa.\" , "
+ " \"key_name\": \"d2_key.billcat.net\" , "
+ " \"dns_servers\" : [ "
+ " { \"hostname\": \"four.rev\" }, "
+ " { \"hostname\": \"five.rev\" } , "
+ " { \"hostname\": \"six.rev\" } "
+ " ] } "
+ "] } }";
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that we can parse the configuration.
+ answer_ = cfg_mgr_->parseConfig(config_set_);
+ ASSERT_TRUE(checkAnswer(0));
+
+ // Verify that the D2 context can be retrieved and is not null.
+ D2CfgContextPtr context;
+ ASSERT_NO_THROW(context = cfg_mgr_->getD2CfgContext());
+
+ // Verify that the application level scalars have the proper values.
+ std::string interface;
+ EXPECT_NO_THROW (context->getParam("interface", interface));
+ EXPECT_EQ("eth1", interface);
+
+ std::string ip_address;
+ EXPECT_NO_THROW (context->getParam("ip_address", ip_address));
+ EXPECT_EQ("192.168.1.33", ip_address);
+
+ uint32_t port = 0;
+ EXPECT_NO_THROW (context->getParam("port", port));
+ EXPECT_EQ(88, port);
+
+ // Verify that the forward manager can be retrieved.
+ DdnsDomainListMgrPtr mgr = context->getForwardMgr();
+ ASSERT_TRUE(mgr);
+
+ // Verify that the forward manager has the correct number of domains.
+ DdnsDomainMapPtr domains = mgr->getDomains();
+ ASSERT_TRUE(domains);
+ int count = domains->size();
+ EXPECT_EQ(2, count);
+
+ // Verify that the server count in each of the forward manager domains.
+ // NOTE that since prior tests have validated server parsing, we are are
+ // assuming that the servers did in fact parse correctly if the correct
+ // number of them are there.
+ DdnsDomainMapPair domain_pair;
+ BOOST_FOREACH(domain_pair, (*domains)) {
+ DdnsDomainPtr domain = domain_pair.second;
+ DnsServerInfoStoragePtr servers = domain->getServers();
+ count = servers->size();
+ EXPECT_TRUE(servers);
+ EXPECT_EQ(3, count);
+ }
+
+ // Verify that the reverse manager can be retrieved.
+ mgr = context->getReverseMgr();
+ ASSERT_TRUE(mgr);
+
+ // Verify that the reverse manager has the correct number of domains.
+ domains = mgr->getDomains();
+ count = domains->size();
+ EXPECT_EQ(2, count);
+
+ // Verify that the server count in each of the reverse manager domains.
+ // NOTE that since prior tests have validated server parsing, we are are
+ // assuming that the servers did in fact parse correctly if the correct
+ // number of them are there.
+ BOOST_FOREACH(domain_pair, (*domains)) {
+ DdnsDomainPtr domain = domain_pair.second;
+ DnsServerInfoStoragePtr servers = domain->getServers();
+ count = servers->size();
+ EXPECT_TRUE(servers);
+ EXPECT_EQ(3, count);
+ }
+}
+
+/// @brief Tests the basics of the D2CfgMgr FQDN-domain matching
+/// This test uses a valid configuration to exercise the D2CfgMgr
+/// forward FQDN-to-domain matching.
+/// It verifies that:
+/// 1. Given an FQDN which exactly matches a domain's name, that domain is
+/// returned as match.
+/// 2. Given a FQDN for sub-domain in the list, returns the proper match.
+/// 3. Given a FQDN that matches no domain name, returns the wild card domain
+/// as a match.
+TEST_F(D2CfgMgrTest, forwardMatchTest) {
+ // Create configuration with one domain, one sub domain, and the wild
+ // card.
+ std::string config = "{ "
+ "\"interface\" : \"eth1\" , "
+ "\"ip_address\" : \"192.168.1.33\" , "
+ "\"port\" : 88 , "
+ "\"tsig_keys\": [] ,"
+ "\"forward_ddns\" : {"
+ "\"ddns_domains\": [ "
+ "{ \"name\": \"tmark.org\" , "
+ " \"dns_servers\" : [ "
+ " { \"ip_address\": \"127.0.0.1\" } "
+ " ] } "
+ ", "
+ "{ \"name\": \"one.tmark.org\" , "
+ " \"dns_servers\" : [ "
+ " { \"ip_address\": \"127.0.0.2\" } "
+ " ] } "
+ ", "
+ "{ \"name\": \"*\" , "
+ " \"dns_servers\" : [ "
+ " { \"hostname\": \"global.net\" } "
+ " ] } "
+ "] }, "
+ "\"reverse_ddns\" : {} "
+ "}";
+
+
+ ASSERT_TRUE(fromJSON(config));
+ // Verify that we can parse the configuration.
+ answer_ = cfg_mgr_->parseConfig(config_set_);
+ ASSERT_TRUE(checkAnswer(0));
+
+ // Verify that the D2 context can be retrieved and is not null.
+ D2CfgContextPtr context;
+ ASSERT_NO_THROW(context = cfg_mgr_->getD2CfgContext());
+
+ DdnsDomainPtr match;
+ // Verify that an exact match works.
+ EXPECT_TRUE(cfg_mgr_->matchForward("tmark.org", match));
+ EXPECT_EQ("tmark.org", match->getName());
+
+ // Verify that an exact match works.
+ EXPECT_TRUE(cfg_mgr_->matchForward("one.tmark.org", match));
+ EXPECT_EQ("one.tmark.org", match->getName());
+
+ // Verify that a FQDN for sub-domain matches.
+ EXPECT_TRUE(cfg_mgr_->matchForward("blue.tmark.org", match));
+ EXPECT_EQ("tmark.org", match->getName());
+
+ // Verify that a FQDN for sub-domain matches.
+ EXPECT_TRUE(cfg_mgr_->matchForward("red.one.tmark.org", match));
+ EXPECT_EQ("one.tmark.org", match->getName());
+
+ // Verify that an FQDN with no match, returns the wild card domain.
+ EXPECT_TRUE(cfg_mgr_->matchForward("shouldbe.wildcard", match));
+ EXPECT_EQ("*", match->getName());
+
+ // Verify that an attempt to match an empty FQDN throws.
+ ASSERT_THROW(cfg_mgr_->matchForward("", match), D2CfgError);
+}
+
+/// @brief Tests domain matching when there is no wild card domain.
+/// This test verifies that matches are found only for FQDNs that match
+/// some or all of a domain name. FQDNs without matches should not return
+/// a match.
+TEST_F(D2CfgMgrTest, matchNoWildcard) {
+ // Create a configuration with one domain, one sub-domain, and NO wild card.
+ std::string config = "{ "
+ "\"interface\" : \"eth1\" , "
+ "\"ip_address\" : \"192.168.1.33\" , "
+ "\"port\" : 88 , "
+ "\"tsig_keys\": [] ,"
+ "\"forward_ddns\" : {"
+ "\"ddns_domains\": [ "
+ "{ \"name\": \"tmark.org\" , "
+ " \"dns_servers\" : [ "
+ " { \"ip_address\": \"127.0.0.1\" } "
+ " ] } "
+ ", "
+ "{ \"name\": \"one.tmark.org\" , "
+ " \"dns_servers\" : [ "
+ " { \"ip_address\": \"127.0.0.2\" } "
+ " ] } "
+ "] }, "
+ "\"reverse_ddns\" : {} "
+ " }";
+
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that we can parse the configuration.
+ answer_ = cfg_mgr_->parseConfig(config_set_);
+ ASSERT_TRUE(checkAnswer(0));
+
+ // Verify that the D2 context can be retrieved and is not null.
+ D2CfgContextPtr context;
+ ASSERT_NO_THROW(context = cfg_mgr_->getD2CfgContext());
+
+ DdnsDomainPtr match;
+ // Verify that full or partial matches, still match.
+ EXPECT_TRUE(cfg_mgr_->matchForward("tmark.org", match));
+ EXPECT_EQ("tmark.org", match->getName());
+
+ EXPECT_TRUE(cfg_mgr_->matchForward("blue.tmark.org", match));
+ EXPECT_EQ("tmark.org", match->getName());
+
+ EXPECT_TRUE(cfg_mgr_->matchForward("red.one.tmark.org", match));
+ EXPECT_EQ("one.tmark.org", match->getName());
+
+ // Verify that a FQDN with no match, fails to match.
+ EXPECT_FALSE(cfg_mgr_->matchForward("shouldbe.wildcard", match));
+}
+
+/// @brief Tests domain matching when there is ONLY a wild card domain.
+/// This test verifies that any FQDN matches the wild card.
+TEST_F(D2CfgMgrTest, matchAll) {
+ std::string config = "{ "
+ "\"interface\" : \"eth1\" , "
+ "\"ip_address\" : \"192.168.1.33\" , "
+ "\"port\" : 88 , "
+ "\"tsig_keys\": [] ,"
+ "\"forward_ddns\" : {"
+ "\"ddns_domains\": [ "
+ "{ \"name\": \"*\" , "
+ " \"dns_servers\" : [ "
+ " { \"ip_address\": \"127.0.0.1\" } "
+ " ] } "
+ "] }, "
+ "\"reverse_ddns\" : {} "
+ "}";
+
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that we can parse the configuration.
+ answer_ = cfg_mgr_->parseConfig(config_set_);
+ ASSERT_TRUE(checkAnswer(0));
+
+ // Verify that the D2 context can be retrieved and is not null.
+ D2CfgContextPtr context;
+ ASSERT_NO_THROW(context = cfg_mgr_->getD2CfgContext());
+
+ // Verify that wild card domain is returned for any FQDN.
+ DdnsDomainPtr match;
+ EXPECT_TRUE(cfg_mgr_->matchForward("tmark.org", match));
+ EXPECT_EQ("*", match->getName());
+ EXPECT_TRUE(cfg_mgr_->matchForward("shouldbe.wildcard", match));
+ EXPECT_EQ("*", match->getName());
+
+ // Verify that an attempt to match an empty FQDN still throws.
+ ASSERT_THROW(cfg_mgr_->matchReverse("", match), D2CfgError);
+
+}
+
+/// @brief Tests the basics of the D2CfgMgr reverse FQDN-domain matching
+/// This test uses a valid configuration to exercise the D2CfgMgr's
+/// reverse FQDN-to-domain matching.
+/// It verifies that:
+/// 1. Given an FQDN which exactly matches a domain's name, that domain is
+/// returned as match.
+/// 2. Given a FQDN for sub-domain in the list, returns the proper match.
+/// 3. Given a FQDN that matches no domain name, returns the wild card domain
+/// as a match.
+TEST_F(D2CfgMgrTest, matchReverse) {
+ std::string config = "{ "
+ "\"interface\" : \"eth1\" , "
+ "\"ip_address\" : \"192.168.1.33\" , "
+ "\"port\" : 88 , "
+ "\"tsig_keys\": [] ,"
+ "\"forward_ddns\" : {}, "
+ "\"reverse_ddns\" : {"
+ "\"ddns_domains\": [ "
+ "{ \"name\": \"100.168.192.in-addr.arpa\" , "
+ " \"dns_servers\" : [ "
+ " { \"ip_address\": \"127.0.0.1\" } "
+ " ] }, "
+ "{ \"name\": \"168.192.in-addr.arpa\" , "
+ " \"dns_servers\" : [ "
+ " { \"ip_address\": \"127.0.0.1\" } "
+ " ] }, "
+ "{ \"name\": \"*\" , "
+ " \"dns_servers\" : [ "
+ " { \"ip_address\": \"127.0.0.1\" } "
+ " ] } "
+ "] } }";
+
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that we can parse the configuration.
+ answer_ = cfg_mgr_->parseConfig(config_set_);
+ ASSERT_TRUE(checkAnswer(0));
+
+ // Verify that the D2 context can be retrieved and is not null.
+ D2CfgContextPtr context;
+ ASSERT_NO_THROW(context = cfg_mgr_->getD2CfgContext());
+
+ DdnsDomainPtr match;
+ // Verify an exact match.
+ EXPECT_TRUE(cfg_mgr_->matchReverse("100.168.192.in-addr.arpa", match));
+ EXPECT_EQ("100.168.192.in-addr.arpa", match->getName());
+
+ // Verify a sub-domain match.
+ EXPECT_TRUE(cfg_mgr_->matchReverse("27.100.168.192.in-addr.arpa", match));
+ EXPECT_EQ("100.168.192.in-addr.arpa", match->getName());
+
+ // Verify a sub-domain match.
+ EXPECT_TRUE(cfg_mgr_->matchReverse("30.133.168.192.in-addr.arpa", match));
+ EXPECT_EQ("168.192.in-addr.arpa", match->getName());
+
+ // Verify a wild card match.
+ EXPECT_TRUE(cfg_mgr_->matchReverse("shouldbe.wildcard", match));
+ EXPECT_EQ("*", match->getName());
+
+ // Verify that an attempt to match an empty FQDN throws.
+ ASSERT_THROW(cfg_mgr_->matchReverse("", match), D2CfgError);
+}
+
+} // end of anonymous namespace
diff --git a/src/bin/d2/tests/d2_controller_unittests.cc b/src/bin/d2/tests/d2_controller_unittests.cc
index a2b3374..f4b4d41 100644
--- a/src/bin/d2/tests/d2_controller_unittests.cc
+++ b/src/bin/d2/tests/d2_controller_unittests.cc
@@ -54,7 +54,7 @@ public:
};
/// @brief Basic Controller instantiation testing.
-/// Verfies that the controller singleton gets created and that the
+/// Verifies that the controller singleton gets created and that the
/// basic derivation from the base class is intact.
TEST_F(D2ControllerTest, basicInstanceTesting) {
// Verify the we can the singleton instance can be fetched and that
@@ -80,12 +80,12 @@ TEST_F(D2ControllerTest, basicInstanceTesting) {
}
/// @brief Tests basic command line processing.
-/// Verfies that:
+/// Verifies that:
/// 1. Standard command line options are supported.
/// 2. Invalid options are detected.
TEST_F(D2ControllerTest, commandLineArgs) {
- char* argv[] = { const_cast<char*>("progName"),
- const_cast<char*>("-s"),
+ char* argv[] = { const_cast<char*>("progName"),
+ const_cast<char*>("-s"),
const_cast<char*>("-v") };
int argc = 3;
@@ -101,7 +101,7 @@ TEST_F(D2ControllerTest, commandLineArgs) {
EXPECT_TRUE(checkVerbose(true));
// Verify that an unknown option is detected.
- char* argv2[] = { const_cast<char*>("progName"),
+ char* argv2[] = { const_cast<char*>("progName"),
const_cast<char*>("-x") };
argc = 2;
EXPECT_THROW(parseArgs(argc, argv2), InvalidUsage);
@@ -119,8 +119,8 @@ TEST_F(D2ControllerTest, initProcessTesting) {
/// launches with a valid, stand-alone command line and no simulated errors.
TEST_F(D2ControllerTest, launchNormalShutdown) {
// command line to run standalone
- char* argv[] = { const_cast<char*>("progName"),
- const_cast<char*>("-s"),
+ char* argv[] = { const_cast<char*>("progName"),
+ const_cast<char*>("-s"),
const_cast<char*>("-v") };
int argc = 3;
@@ -165,10 +165,9 @@ TEST_F(D2ControllerTest, configUpdateTests) {
ASSERT_NO_THROW(initProcess());
EXPECT_TRUE(checkProcess());
- // Create a configuration set. Content is arbitrary, just needs to be
- // valid JSON.
- std::string config = "{ \"test-value\": 1000 } ";
- isc::data::ElementPtr config_set = isc::data::Element::fromJSON(config);
+ // Create a configuration set using a small, valid D2 configuration.
+ isc::data::ElementPtr config_set =
+ isc::data::Element::fromJSON(valid_d2_config);
// We are not stand-alone, so configuration should be rejected as there is
// no session. This is a pretty contrived situation that shouldn't be
@@ -182,6 +181,13 @@ TEST_F(D2ControllerTest, configUpdateTests) {
answer = DControllerBase::configHandler(config_set);
isc::config::parseAnswer(rcode, answer);
EXPECT_EQ(0, rcode);
+
+ // Use an invalid configuration to verify parsing error return.
+ std::string config = "{ \"bogus\": 1000 } ";
+ config_set = isc::data::Element::fromJSON(config);
+ answer = DControllerBase::configHandler(config_set);
+ isc::config::parseAnswer(rcode, answer);
+ EXPECT_EQ(1, rcode);
}
/// @brief Command execution tests.
diff --git a/src/bin/d2/tests/d2_process_unittests.cc b/src/bin/d2/tests/d2_process_unittests.cc
index 40c85f3..c507f2c 100644
--- a/src/bin/d2/tests/d2_process_unittests.cc
+++ b/src/bin/d2/tests/d2_process_unittests.cc
@@ -15,6 +15,7 @@
#include <config/ccsession.h>
#include <d2/d2_process.h>
+#include <d_test_stubs.h>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <gtest/gtest.h>
@@ -83,17 +84,23 @@ TEST(D2Process, construction) {
}
/// @brief Verifies basic configure method behavior.
-/// @TODO This test is simplistic and will need to be augmented as configuration
+/// This test is simplistic and will need to be augmented as configuration
/// ability is implemented.
TEST_F(D2ProcessTest, configure) {
- // Verify that given a configuration "set", configure returns
- // a successful response.
int rcode = -1;
- string config = "{ \"test-value\": 1000 } ";
- isc::data::ElementPtr json = isc::data::Element::fromJSON(config);
+
+ // Use a small, valid D2 configuration to verify successful parsing.
+ isc::data::ElementPtr json = isc::data::Element::fromJSON(valid_d2_config);
isc::data::ConstElementPtr answer = process_->configure(json);
isc::config::parseAnswer(rcode, answer);
EXPECT_EQ(0, rcode);
+
+ // Use an invalid configuration to verify parsing error return.
+ string config = "{ \"bogus\": 1000 } ";
+ json = isc::data::Element::fromJSON(config);
+ answer = process_->configure(json);
+ isc::config::parseAnswer(rcode, answer);
+ EXPECT_EQ(1, rcode);
}
/// @brief Verifies basic command method behavior.
diff --git a/src/bin/d2/tests/d_cfg_mgr_unittests.cc b/src/bin/d2/tests/d_cfg_mgr_unittests.cc
new file mode 100644
index 0000000..512b896
--- /dev/null
+++ b/src/bin/d2/tests/d_cfg_mgr_unittests.cc
@@ -0,0 +1,386 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config/ccsession.h>
+#include <config/module_spec.h>
+#include <dhcpsrv/dhcp_parsers.h>
+#include <d2/d_cfg_mgr.h>
+#include <d_test_stubs.h>
+
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <gtest/gtest.h>
+
+#include <config.h>
+#include <sstream>
+
+using namespace std;
+using namespace isc;
+using namespace isc::config;
+using namespace isc::d2;
+using namespace boost::posix_time;
+
+namespace {
+
+/// @brief Test Class for verifying that configuration context cannot be null
+/// during construction.
+class DCtorTestCfgMgr : public DCfgMgrBase {
+public:
+ /// @brief Constructor - Note that is passes in an empty configuration
+ /// pointer to the base class constructor.
+ DCtorTestCfgMgr() : DCfgMgrBase(DCfgContextBasePtr()) {
+ }
+
+ /// @brief Destructor
+ virtual ~DCtorTestCfgMgr() {
+ }
+
+ /// @brief Dummy implementation as this method is abstract.
+ virtual isc::dhcp::ParserPtr
+ createConfigParser(const std::string& /* element_id */) {
+ return (isc::dhcp::ParserPtr());
+ }
+};
+
+/// @brief Test fixture class for testing DCfgMgrBase class.
+/// It maintains an member instance of DStubCfgMgr and derives from
+/// ConfigParseTest fixture, thus providing methods for converting JSON
+/// strings to configuration element sets, checking parse results, and
+/// accessing the configuration context.
+class DStubCfgMgrTest : public ConfigParseTest {
+public:
+
+ /// @brief Constructor
+ DStubCfgMgrTest():cfg_mgr_(new DStubCfgMgr) {
+ }
+
+ /// @brief Destructor
+ ~DStubCfgMgrTest() {
+ }
+
+ /// @brief Convenience method which returns a DStubContextPtr to the
+ /// configuration context.
+ ///
+ /// @return returns a DStubContextPtr.
+ DStubContextPtr getStubContext() {
+ return (boost::dynamic_pointer_cast<DStubContext>
+ (cfg_mgr_->getContext()));
+ }
+
+ /// @brief Configuration manager instance.
+ DStubCfgMgrPtr cfg_mgr_;
+};
+
+///@brief Tests basic construction/destruction of configuration manager.
+/// Verifies that:
+/// 1. Proper construction succeeds.
+/// 2. Configuration context is initialized by construction.
+/// 3. Destruction works properly.
+/// 4. Construction with a null context is not allowed.
+TEST(DCfgMgrBase, construction) {
+ DCfgMgrBasePtr cfg_mgr;
+
+ // Verify that configuration manager constructions without error.
+ ASSERT_NO_THROW(cfg_mgr.reset(new DStubCfgMgr()));
+
+ // Verify that the context can be retrieved and is not null.
+ DCfgContextBasePtr context = cfg_mgr->getContext();
+ EXPECT_TRUE(context);
+
+ // Verify that the manager can be destructed without error.
+ EXPECT_NO_THROW(cfg_mgr.reset());
+
+ // Verify that an attempt to construct a manger with a null context fails.
+ ASSERT_THROW(DCtorTestCfgMgr(), DCfgMgrBaseError);
+}
+
+///@brief Tests fundamental aspects of configuration parsing.
+/// Verifies that:
+/// 1. A correctly formed simple configuration parses without error.
+/// 2. An error building the element is handled.
+/// 3. An error committing the element is handled.
+/// 4. An unknown element error is handled.
+TEST_F(DStubCfgMgrTest, basicParseTest) {
+ // Create a simple configuration.
+ string config = "{ \"test-value\": 1000 } ";
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that we can parse a simple configuration.
+ answer_ = cfg_mgr_->parseConfig(config_set_);
+ EXPECT_TRUE(checkAnswer(0));
+
+ // Verify that an error building the element is caught and returns a
+ // failed parse result.
+ SimFailure::set(SimFailure::ftElementBuild);
+ answer_ = cfg_mgr_->parseConfig(config_set_);
+ EXPECT_TRUE(checkAnswer(1));
+
+ // Verify that an error committing the element is caught and returns a
+ // failed parse result.
+ SimFailure::set(SimFailure::ftElementCommit);
+ answer_ = cfg_mgr_->parseConfig(config_set_);
+ EXPECT_TRUE(checkAnswer(1));
+
+ // Verify that an unknown element error is caught and returns a failed
+ // parse result.
+ SimFailure::set(SimFailure::ftElementUnknown);
+ answer_ = cfg_mgr_->parseConfig(config_set_);
+ EXPECT_TRUE(checkAnswer(1));
+}
+
+///@brief Tests ordered and non-ordered element parsing
+/// This test verifies that:
+/// 1. Non-ordered parsing parses elements in the order they are presented
+/// by the configuration set (as-they-come).
+/// 2. A parse order list with too few elements is detected.
+/// 3. Ordered parsing parses the elements in the order specified by the
+/// configuration manager's parse order list.
+/// 4. A parse order list with too many elements is detected.
+TEST_F(DStubCfgMgrTest, parseOrderTest) {
+ // Element ids used for test.
+ std::string charlie("charlie");
+ std::string bravo("bravo");
+ std::string alpha("alpha");
+
+ // Create the test configuration with the elements in "random" order.
+
+ // NOTE that element sets produced by isc::data::Element::fromJSON(),
+ // are in lexical order by element_id. This means that iterating over
+ // such an element set, will present the elements in lexical order. Should
+ // this change, this test will need to be modified accordingly.
+ string config = "{ \"bravo\": 2, "
+ " \"alpha\": 1, "
+ " \"charlie\": 3 } ";
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that non-ordered parsing, results in an as-they-come parse order.
+ // Create an expected parse order.
+ // (NOTE that iterating over Element sets produced by fromJSON() will
+ // present the elements in lexical order. Should this change, the expected
+ // order list below would need to be changed accordingly).
+ ElementIdList order_expected;
+ order_expected.push_back(alpha);
+ order_expected.push_back(bravo);
+ order_expected.push_back(charlie);
+
+ // Verify that the manager has an EMPTY parse order list. (Empty list
+ // instructs the manager to parse them as-they-come.)
+ EXPECT_EQ(0, cfg_mgr_->getParseOrder().size());
+
+ // Parse the configuration, verify it parses without error.
+ answer_ = cfg_mgr_->parseConfig(config_set_);
+ EXPECT_TRUE(checkAnswer(0));
+
+ // Verify that the parsed order matches what we expected.
+ EXPECT_TRUE(cfg_mgr_->parsed_order_ == order_expected);
+
+ // Clear the manager's parse order "memory".
+ cfg_mgr_->parsed_order_.clear();
+
+ // Create a parse order list that has too few entries. Verify that
+ // when parsing the test config, it fails.
+ cfg_mgr_->addToParseOrder(charlie);
+ // Verify the parse order list is the size we expect.
+ EXPECT_EQ(1, cfg_mgr_->getParseOrder().size());
+
+ // Verify the configuration fails.
+ answer_ = cfg_mgr_->parseConfig(config_set_);
+ EXPECT_TRUE(checkAnswer(1));
+
+ // Verify that the configuration parses correctly, when the parse order
+ // is correct. Add the needed entries to the parse order
+ cfg_mgr_->addToParseOrder(bravo);
+ cfg_mgr_->addToParseOrder(alpha);
+
+ // Verify the parse order list is the size we expect.
+ EXPECT_EQ(3, cfg_mgr_->getParseOrder().size());
+
+ // Clear the manager's parse order "memory".
+ cfg_mgr_->parsed_order_.clear();
+
+ // Verify the configuration parses without error.
+ answer_ = cfg_mgr_->parseConfig(config_set_);
+ EXPECT_TRUE(checkAnswer(0));
+
+ // Verify that the parsed order is the order we configured.
+ EXPECT_TRUE(cfg_mgr_->getParseOrder() == cfg_mgr_->parsed_order_);
+
+ // Create a parse order list that has too many entries. Verify that
+ // when parsing the test config, it fails.
+ cfg_mgr_->addToParseOrder("delta");
+
+ // Verify the parse order list is the size we expect.
+ EXPECT_EQ(4, cfg_mgr_->getParseOrder().size());
+
+ // Verify the configuration fails.
+ answer_ = cfg_mgr_->parseConfig(config_set_);
+ EXPECT_TRUE(checkAnswer(1));
+}
+
+/// @brief Tests that element ids supported by the base class as well as those
+/// added by the derived class function properly.
+/// This test verifies that:
+/// 1. Boolean parameters can be parsed and retrieved.
+/// 2. Uint32 parameters can be parsed and retrieved.
+/// 3. String parameters can be parsed and retrieved.
+/// 4. Derivation-specific parameters can be parsed and retrieved.
+/// 5. Parsing a second configuration, updates the existing context values
+/// correctly.
+TEST_F(DStubCfgMgrTest, simpleTypesTest) {
+ // Fetch a derivation specific pointer to the context.
+ DStubContextPtr context = getStubContext();
+ ASSERT_TRUE(context);
+
+ // Create a configuration with all of the parameters.
+ string config = "{ \"bool_test\": true , "
+ " \"uint32_test\": 77 , "
+ " \"string_test\": \"hmmm chewy\" , "
+ " \"extra_test\": 430 } ";
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that the configuration parses without error.
+ answer_ = cfg_mgr_->parseConfig(config_set_);
+ ASSERT_TRUE(checkAnswer(0));
+
+ // Verify that the boolean parameter was parsed correctly by retrieving
+ // its value from the context.
+ bool actual_bool = false;
+ EXPECT_NO_THROW(context->getParam("bool_test", actual_bool));
+ EXPECT_EQ(true, actual_bool);
+
+ // Verify that the uint32 parameter was parsed correctly by retrieving
+ // its value from the context.
+ uint32_t actual_uint32 = 0;
+ EXPECT_NO_THROW(context->getParam("uint32_test", actual_uint32));
+ EXPECT_EQ(77, actual_uint32);
+
+ // Verify that the string parameter was parsed correctly by retrieving
+ // its value from the context.
+ std::string actual_string = "";
+ EXPECT_NO_THROW(context->getParam("string_test", actual_string));
+ EXPECT_EQ("hmmm chewy", actual_string);
+
+ // Verify that the "extra" parameter was parsed correctly by retrieving
+ // its value from the context.
+ uint32_t actual_extra = 0;
+ EXPECT_NO_THROW(context->getExtraParam("extra_test", actual_extra));
+ EXPECT_EQ(430, actual_extra);
+
+ // Create a configuration which "updates" all of the parameter values.
+ string config2 = "{ \"bool_test\": false , "
+ " \"uint32_test\": 88 , "
+ " \"string_test\": \"ewww yuk!\" , "
+ " \"extra_test\": 11 } ";
+ ASSERT_TRUE(fromJSON(config2));
+
+ // Verify that the configuration parses without error.
+ answer_ = cfg_mgr_->parseConfig(config_set_);
+ EXPECT_TRUE(checkAnswer(0));
+
+ // Verify that the boolean parameter was updated correctly by retrieving
+ // its value from the context.
+ actual_bool = true;
+ EXPECT_NO_THROW(context->getParam("bool_test", actual_bool));
+ EXPECT_FALSE(actual_bool);
+
+ // Verify that the uint32 parameter was updated correctly by retrieving
+ // its value from the context.
+ actual_uint32 = 0;
+ EXPECT_NO_THROW(context->getParam("uint32_test", actual_uint32));
+ EXPECT_EQ(88, actual_uint32);
+
+ // Verify that the string parameter was updated correctly by retrieving
+ // its value from the context.
+ actual_string = "";
+ EXPECT_NO_THROW(context->getParam("string_test", actual_string));
+ EXPECT_EQ("ewww yuk!", actual_string);
+
+ // Verify that the "extra" parameter was updated correctly by retrieving
+ // its value from the context.
+ actual_extra = 0;
+ EXPECT_NO_THROW(context->getExtraParam("extra_test", actual_extra));
+ EXPECT_EQ(11, actual_extra);
+}
+
+/// @brief Tests that the configuration context is preserved after failure
+/// during parsing causes a rollback.
+/// 1. Verifies configuration context rollback.
+TEST_F(DStubCfgMgrTest, rollBackTest) {
+ // Fetch a derivation specific pointer to the context.
+ DStubContextPtr context = getStubContext();
+ ASSERT_TRUE(context);
+
+ // Create a configuration with all of the parameters.
+ string config = "{ \"bool_test\": true , "
+ " \"uint32_test\": 77 , "
+ " \"string_test\": \"hmmm chewy\" , "
+ " \"extra_test\": 430 } ";
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that the configuration parses without error.
+ answer_ = cfg_mgr_->parseConfig(config_set_);
+ EXPECT_TRUE(checkAnswer(0));
+
+ // Verify that all of parameters have the expected values.
+ bool actual_bool = false;
+ EXPECT_NO_THROW(context->getParam("bool_test", actual_bool));
+ EXPECT_EQ(true, actual_bool);
+
+ uint32_t actual_uint32 = 0;
+ EXPECT_NO_THROW(context->getParam("uint32_test", actual_uint32));
+ EXPECT_EQ(77, actual_uint32);
+
+ std::string actual_string = "";
+ EXPECT_NO_THROW(context->getParam("string_test", actual_string));
+ EXPECT_EQ("hmmm chewy", actual_string);
+
+ uint32_t actual_extra = 0;
+ EXPECT_NO_THROW(context->getExtraParam("extra_test", actual_extra));
+ EXPECT_EQ(430, actual_extra);
+
+ // Create a configuration which "updates" all of the parameter values
+ // plus one unknown at the end.
+ string config2 = "{ \"bool_test\": false , "
+ " \"uint32_test\": 88 , "
+ " \"string_test\": \"ewww yuk!\" , "
+ " \"extra_test\": 11 , "
+ " \"zeta_unknown\": 33 } ";
+ ASSERT_TRUE(fromJSON(config2));
+
+ // Force a failure on the last element
+ SimFailure::set(SimFailure::ftElementUnknown);
+ answer_ = cfg_mgr_->parseConfig(config_set_);
+ EXPECT_TRUE(checkAnswer(1));
+
+ // Refresh our local pointer.
+ context = getStubContext();
+
+ // Verify that all of parameters have the original values.
+ actual_bool = false;
+ EXPECT_NO_THROW(context->getParam("bool_test", actual_bool));
+ EXPECT_EQ(true, actual_bool);
+
+ actual_uint32 = 0;
+ EXPECT_NO_THROW(context->getParam("uint32_test", actual_uint32));
+ EXPECT_EQ(77, actual_uint32);
+
+ actual_string = "";
+ EXPECT_NO_THROW(context->getParam("string_test", actual_string));
+ EXPECT_EQ("hmmm chewy", actual_string);
+
+ actual_extra = 0;
+ EXPECT_NO_THROW(context->getExtraParam("extra_test", actual_extra));
+ EXPECT_EQ(430, actual_extra);
+}
+
+} // end of anonymous namespace
diff --git a/src/bin/d2/tests/d_test_stubs.cc b/src/bin/d2/tests/d_test_stubs.cc
index 33a2ff6..4e1cee6 100644
--- a/src/bin/d2/tests/d_test_stubs.cc
+++ b/src/bin/d2/tests/d_test_stubs.cc
@@ -21,6 +21,31 @@ using namespace asio;
namespace isc {
namespace d2 {
+const char* valid_d2_config = "{ "
+ "\"interface\" : \"eth1\" , "
+ "\"ip_address\" : \"192.168.1.33\" , "
+ "\"port\" : 88 , "
+ "\"tsig_keys\": ["
+ "{ \"name\": \"d2_key.tmark.org\" , "
+ " \"algorithm\": \"md5\" ,"
+ " \"secret\": \"0123456989\" "
+ "} ],"
+ "\"forward_ddns\" : {"
+ "\"ddns_domains\": [ "
+ "{ \"name\": \"tmark.org\" , "
+ " \"key_name\": \"d2_key.tmark.org\" , "
+ " \"dns_servers\" : [ "
+ " { \"hostname\": \"one.tmark\" } "
+ "] } ] }, "
+ "\"reverse_ddns\" : {"
+ "\"ddns_domains\": [ "
+ "{ \"name\": \" 0.168.192.in.addr.arpa.\" , "
+ " \"key_name\": \"d2_key.tmark.org\" , "
+ " \"dns_servers\" : [ "
+ " { \"ip_address\": \"127.0.0.101\" , "
+ " \"port\": 100 } ] } "
+ "] } }";
+
// Initialize the static failure flag.
SimFailure::FailureType SimFailure::failure_type_ = SimFailure::ftNoFailure;
@@ -28,7 +53,7 @@ SimFailure::FailureType SimFailure::failure_type_ = SimFailure::ftNoFailure;
const char* DStubProcess::stub_proc_command_("cool_proc_cmd");
DStubProcess::DStubProcess(const char* name, IOServicePtr io_service)
- : DProcessBase(name, io_service) {
+ : DProcessBase(name, io_service, DCfgMgrBasePtr(new DStubCfgMgr())) {
};
void
@@ -130,7 +155,7 @@ DStubController::DStubController()
if (getenv("B10_FROM_BUILD")) {
setSpecFileName(std::string(getenv("B10_FROM_BUILD")) +
- "/src/bin/d2/d2.spec");
+ "/src/bin/d2/dhcp-ddns.spec");
} else {
setSpecFileName(D2_SPECFILE_LOCATION);
}
@@ -191,5 +216,102 @@ DStubController::~DStubController() {
// Initialize controller wrapper's static instance getter member.
DControllerTest::InstanceGetter DControllerTest::instanceGetter_ = NULL;
+//************************** TestParser *************************
+
+TestParser::TestParser(const std::string& param_name):param_name_(param_name) {
+}
+
+TestParser::~TestParser(){
+}
+
+void
+TestParser::build(isc::data::ConstElementPtr new_config) {
+ if (SimFailure::shouldFailOn(SimFailure::ftElementBuild)) {
+ // Simulates an error during element data parsing.
+ isc_throw (DCfgMgrBaseError, "Simulated build exception");
+ }
+
+ value_ = new_config;
+}
+
+void
+TestParser::commit() {
+ if (SimFailure::shouldFailOn(SimFailure::ftElementCommit)) {
+ // Simulates an error while committing the parsed element data.
+ throw std::runtime_error("Simulated commit exception");
+ }
+}
+
+//************************** DStubContext *************************
+
+DStubContext::DStubContext(): extra_values_(new isc::dhcp::Uint32Storage()) {
+}
+
+DStubContext::~DStubContext() {
+}
+
+void
+DStubContext::getExtraParam(const std::string& name, uint32_t& value) {
+ value = extra_values_->getParam(name);
+}
+
+isc::dhcp::Uint32StoragePtr
+DStubContext::getExtraStorage() {
+ return (extra_values_);
+}
+
+DCfgContextBasePtr
+DStubContext::clone() {
+ return (DCfgContextBasePtr(new DStubContext(*this)));
+}
+
+DStubContext::DStubContext(const DStubContext& rhs): DCfgContextBase(rhs),
+ extra_values_(new isc::dhcp::Uint32Storage(*(rhs.extra_values_))) {
+}
+
+//************************** DStubCfgMgr *************************
+
+DStubCfgMgr::DStubCfgMgr()
+ : DCfgMgrBase(DCfgContextBasePtr(new DStubContext())) {
+}
+
+DStubCfgMgr::~DStubCfgMgr() {
+}
+
+isc::dhcp::ParserPtr
+DStubCfgMgr::createConfigParser(const std::string& element_id) {
+ isc::dhcp::DhcpConfigParser* parser = NULL;
+ DStubContextPtr context =
+ boost::dynamic_pointer_cast<DStubContext>(getContext());
+
+ if (element_id == "bool_test") {
+ parser = new isc::dhcp::BooleanParser(element_id,
+ context->getBooleanStorage());
+ } else if (element_id == "uint32_test") {
+ parser = new isc::dhcp::Uint32Parser(element_id,
+ context->getUint32Storage());
+ } else if (element_id == "string_test") {
+ parser = new isc::dhcp::StringParser(element_id,
+ context->getStringStorage());
+ } else if (element_id == "extra_test") {
+ parser = new isc::dhcp::Uint32Parser(element_id,
+ context->getExtraStorage());
+ } else {
+ // Fail only if SimFailure dictates we should. This makes it easier
+ // to test parse ordering, by permitting a wide range of element ids
+ // to "succeed" without specifically supporting them.
+ if (SimFailure::shouldFailOn(SimFailure::ftElementUnknown)) {
+ isc_throw(DCfgMgrBaseError, "Configuration parameter not supported: "
+ << element_id);
+ }
+
+ parsed_order_.push_back(element_id);
+ parser = new TestParser(element_id);
+ }
+
+ return (isc::dhcp::ParserPtr(parser));
+}
+
+
}; // namespace isc::d2
}; // namespace isc
diff --git a/src/bin/d2/tests/d_test_stubs.h b/src/bin/d2/tests/d_test_stubs.h
index e634762..58ebcd4 100644
--- a/src/bin/d2/tests/d_test_stubs.h
+++ b/src/bin/d2/tests/d_test_stubs.h
@@ -21,11 +21,18 @@
#include <config/ccsession.h>
#include <d2/d_controller.h>
+#include <d2/d_cfg_mgr.h>
+
#include <gtest/gtest.h>
namespace isc {
namespace d2 {
+/// @brief Provides a valid DHCP-DDNS configuration for testing basic
+/// parsing fundamentals.
+extern const char* valid_d2_config;
+
+
/// @brief Class is used to set a globally accessible value that indicates
/// a specific type of failure to simulate. Test derivations of base classes
/// can exercise error handling code paths by testing for specific SimFailure
@@ -43,7 +50,10 @@ public:
ftProcessConfigure,
ftControllerCommand,
ftProcessCommand,
- ftProcessShutdown
+ ftProcessShutdown,
+ ftElementBuild,
+ ftElementCommit,
+ ftElementUnknown
};
/// @brief Sets the SimFailure value to the given value.
@@ -80,10 +90,12 @@ public:
return (false);
}
+ /// @brief Resets the failure type to none.
static void clear() {
failure_type_ = ftNoFailure;
}
+ /// @brief Static value for holding the failure type to simulate.
static enum FailureType failure_type_;
};
@@ -397,7 +409,7 @@ public:
/// DControllerBase::launch for details.
void launch(int argc, char* argv[]) {
optind = 1;
- getController()->launch(argc, argv);
+ getController()->launch(argc, argv, true);
}
/// @Wrapper to invoke the Controller's disconnectSession method. Please
@@ -433,6 +445,207 @@ public:
}
};
+/// @brief Simple parser derivation for testing the basics of configuration
+/// parsing.
+class TestParser : public isc::dhcp::DhcpConfigParser {
+public:
+
+ /// @brief Constructor
+ ///
+ /// See @ref DhcpConfigParser class for details.
+ ///
+ /// @param param_name name of the parsed parameter
+ TestParser(const std::string& param_name);
+
+ /// @brief Destructor
+ virtual ~TestParser();
+
+ /// @brief Builds parameter value.
+ ///
+ /// See @ref DhcpConfigParser class for details.
+ ///
+ /// @param new_config pointer to the new configuration
+ /// @throw throws DCfgMgrBaseError if the SimFailure is set to
+ /// ftElementBuild. This allows for the simulation of an
+ /// exception during the build portion of parsing an element.
+ virtual void build(isc::data::ConstElementPtr new_config);
+
+ /// @brief Commits the parsed value to storage.
+ ///
+ /// See @ref DhcpConfigParser class for details.
+ ///
+ /// @throw throws DCfgMgrBaseError if SimFailure is set to ftElementCommit.
+ /// This allows for the simulation of an exception during the commit
+ /// portion of parsing an element.
+ virtual void commit();
+
+private:
+ /// name of the parsed parameter
+ std::string param_name_;
+
+ /// pointer to the parsed value of the parameter
+ isc::data::ConstElementPtr value_;
+};
+
+/// @brief Test Derivation of the DCfgContextBase class.
+///
+/// This class is used to test basic functionality of configuration context.
+/// It adds an additional storage container "extra values" to mimic an
+/// application extension of configuration storage. This permits testing that
+/// both the base class content as well as the application content is
+/// correctly copied during cloning. This is vital to configuration backup
+/// and rollback during configuration parsing.
+class DStubContext : public DCfgContextBase {
+public:
+
+ /// @brief Constructor
+ DStubContext();
+
+ /// @brief Destructor
+ virtual ~DStubContext();
+
+ /// @brief Fetches the value for a given "extra" configuration parameter
+ /// from the context.
+ ///
+ /// @param name is the name of the parameter to retrieve.
+ /// @param value is an output parameter in which to return the retrieved
+ /// value.
+ /// @throw throws DhcpConfigError if the context does not contain the
+ /// parameter.
+ void getExtraParam(const std::string& name, uint32_t& value);
+
+ /// @brief Fetches the extra storage.
+ ///
+ /// @return returns a pointer to the extra storage.
+ isc::dhcp::Uint32StoragePtr getExtraStorage();
+
+ /// @brief Creates a clone of a DStubContext.
+ ///
+ /// @return returns a pointer to the new clone.
+ virtual DCfgContextBasePtr clone();
+
+protected:
+ /// @brief Copy constructor
+ DStubContext(const DStubContext& rhs);
+
+private:
+ /// @brief Private assignment operator, not implemented.
+ DStubContext& operator=(const DStubContext& rhs);
+
+ /// @brief Extra storage for uint32 parameters.
+ isc::dhcp::Uint32StoragePtr extra_values_;
+};
+
+/// @brief Defines a pointer to DStubContext.
+typedef boost::shared_ptr<DStubContext> DStubContextPtr;
+
+/// @brief Test Derivation of the DCfgMgrBase class.
+///
+/// This class is used to test basic functionality of configuration management.
+/// It supports the following configuration elements:
+///
+/// "bool_test" - Boolean element, tests parsing and committing a boolean
+/// configuration parameter.
+/// "uint32_test" - Uint32 element, tests parsing and committing a uint32_t
+/// configuration parameter.
+/// "string_test" - String element, tests parsing and committing a string
+/// configuration parameter.
+/// "extra_test" - "Extra" element, tests parsing and committing an extra
+/// configuration parameter. (This is used to demonstrate
+/// derivation's addition of storage to configuration context.
+///
+/// It also keeps track of the element ids that are parsed in the order they
+/// are parsed. This is used to test ordered and non-ordered parsing.
+class DStubCfgMgr : public DCfgMgrBase {
+public:
+ /// @brief Constructor
+ DStubCfgMgr();
+
+ /// @brief Destructor
+ virtual ~DStubCfgMgr();
+
+ /// @brief Given an element_id returns an instance of the appropriate
+ /// parser. It supports the element ids as described in the class brief.
+ ///
+ /// @param element_id is the string name of the element as it will appear
+ /// in the configuration set.
+ ///
+ /// @return returns a ParserPtr to the parser instance.
+ /// @throw throws DCfgMgrBaseError if SimFailure is ftElementUnknown.
+ virtual isc::dhcp::ParserPtr
+ createConfigParser(const std::string& element_id);
+
+ /// @brief A list for remembering the element ids in the order they were
+ /// parsed.
+ ElementIdList parsed_order_;
+};
+
+/// @brief Defines a pointer to DStubCfgMgr.
+typedef boost::shared_ptr<DStubCfgMgr> DStubCfgMgrPtr;
+
+/// @brief Test fixture base class for any fixtures which test parsing.
+/// It provides methods for converting JSON strings to configuration element
+/// sets and checking parse results
+class ConfigParseTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor
+ ConfigParseTest(){
+ }
+
+ /// @brief Destructor
+ ~ConfigParseTest() {
+ }
+
+ /// @brief Converts a given JSON string into an Element set and stores the
+ /// result the member variable, config_set_.
+ ///
+ /// @param json_text contains the configuration text in JSON format to
+ /// convert.
+ /// @return returns AssertionSuccess if there were no parsing errors,
+ /// AssertionFailure otherwise.
+ ::testing::AssertionResult fromJSON(std::string& json_text) {
+ try {
+ config_set_ = isc::data::Element::fromJSON(json_text);
+ } catch (const isc::Exception &ex) {
+ return ::testing::AssertionFailure()
+ << "JSON text failed to parse:" << ex.what();
+ }
+
+ return ::testing::AssertionSuccess();
+ }
+
+
+ /// @brief Compares the status in the parse result stored in member
+ /// variable answer_ to a given value.
+ ///
+ /// @param should_be is an integer against which to compare the status.
+ ///
+ /// @return returns AssertionSuccess if there were no parsing errors,
+ /// AssertionFailure otherwise.
+ ::testing::AssertionResult checkAnswer(int should_be) {
+ int rcode = 0;
+ isc::data::ConstElementPtr comment;
+ comment = isc::config::parseAnswer(rcode, answer_);
+ if (rcode == should_be) {
+ return testing::AssertionSuccess();
+ }
+
+ return ::testing::AssertionFailure() << "checkAnswer rcode:"
+ << rcode << " comment: " << *comment;
+ }
+
+ /// @brief Configuration set being tested.
+ isc::data::ElementPtr config_set_;
+
+ /// @brief Results of most recent element parsing.
+ isc::data::ConstElementPtr answer_;
+};
+
+/// @brief Defines a small but valid DHCP-DDNS compliant configuration for
+/// testing configuration parsing fundamentals.
+extern const char* valid_d2_config;
+
}; // namespace isc::d2
}; // namespace isc
diff --git a/src/bin/d2/tests/dns_client_unittests.cc b/src/bin/d2/tests/dns_client_unittests.cc
new file mode 100644
index 0000000..9105ab8
--- /dev/null
+++ b/src/bin/d2/tests/dns_client_unittests.cc
@@ -0,0 +1,408 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <d2/dns_client.h>
+#include <asiodns/io_fetch.h>
+#include <asiodns/logger.h>
+#include <asiolink/interval_timer.h>
+#include <dns/rcode.h>
+#include <dns/rrclass.h>
+#include <dns/tsig.h>
+#include <asio/ip/udp.hpp>
+#include <asio/socket_base.hpp>
+#include <boost/bind.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::asiodns;
+using namespace isc::d2;
+
+using namespace isc;
+using namespace isc::dns;
+using namespace isc::util;
+using namespace asio;
+using namespace asio::ip;
+
+namespace {
+
+const char* TEST_ADDRESS = "127.0.0.1";
+const uint16_t TEST_PORT = 5301;
+const size_t MAX_SIZE = 1024;
+const long TEST_TIMEOUT = 5 * 1000;
+
+// @brief Test Fixture class.
+//
+// This test fixture class implements DNSClient::Callback so as it can be
+// installed as a completion callback for tests it implements. This callback
+// is called when a DDNS transaction (send and receive) completes. This allows
+// for the callback function to directly access class members. In particular,
+// the callback function can access IOService on which run() was called and
+// call stop() on it.
+//
+// Many of the tests defined here schedule execution of certain tasks and block
+// until tasks are completed or a timeout is hit. However, if timeout is not
+// properly handled a task may be hanging for a long time. In order to prevent
+// it, the asiolink::IntervalTimer is used to break a running test if test
+// timeout is hit. This will result in test failure.
+class DNSClientTest : public virtual ::testing::Test, DNSClient::Callback {
+public:
+ IOService service_;
+ D2UpdateMessagePtr response_;
+ DNSClient::Status status_;
+ uint8_t receive_buffer_[MAX_SIZE];
+ DNSClientPtr dns_client_;
+ bool corrupt_response_;
+ bool expect_response_;
+ asiolink::IntervalTimer test_timer_;
+
+ // @brief Constructor.
+ //
+ // This constructor overrides the default logging level of asiodns logger to
+ // prevent it from emitting debug messages from IOFetch class. Such an error
+ // message can be emitted if timeout occurs when DNSClient class is
+ // waiting for a response. Some of the tests are checking DNSClient behavior
+ // in case when response from the server is not received. Tests output would
+ // become messy if such errors were logged.
+ DNSClientTest()
+ : service_(),
+ status_(DNSClient::SUCCESS),
+ corrupt_response_(false),
+ expect_response_(true),
+ test_timer_(service_) {
+ asiodns::logger.setSeverity(isc::log::INFO);
+ response_.reset(new D2UpdateMessage(D2UpdateMessage::INBOUND));
+ dns_client_.reset(new DNSClient(response_, this));
+
+ // Set the test timeout to break any running tasks if they hang.
+ test_timer_.setup(boost::bind(&DNSClientTest::testTimeoutHandler, this),
+ TEST_TIMEOUT);
+ }
+
+ // @brief Destructor.
+ //
+ // Sets the asiodns logging level back to DEBUG.
+ virtual ~DNSClientTest() {
+ asiodns::logger.setSeverity(isc::log::DEBUG);
+ };
+
+ // @brief Exchange completion callback.
+ //
+ // This callback is called when the exchange with the DNS server is
+ // complete or an error occurred. This includes the occurrence of a timeout.
+ //
+ // @param status A status code returned by DNSClient.
+ virtual void operator()(DNSClient::Status status) {
+ status_ = status;
+ service_.stop();
+
+ if (expect_response_) {
+ if (!corrupt_response_) {
+ // We should have received a response.
+ EXPECT_EQ(DNSClient::SUCCESS, status_);
+
+ ASSERT_TRUE(response_);
+ EXPECT_EQ(D2UpdateMessage::RESPONSE, response_->getQRFlag());
+ ASSERT_EQ(1,
+ response_->getRRCount(D2UpdateMessage::SECTION_ZONE));
+ D2ZonePtr zone = response_->getZone();
+ ASSERT_TRUE(zone);
+ EXPECT_EQ("example.com.", zone->getName().toText());
+ EXPECT_EQ(RRClass::IN().getCode(), zone->getClass().getCode());
+
+ } else {
+ EXPECT_EQ(DNSClient::INVALID_RESPONSE, status_);
+
+ }
+ // If we don't expect a response, the status should indicate a timeout.
+ } else {
+ EXPECT_EQ(DNSClient::TIMEOUT, status_);
+
+ }
+ }
+
+ // @brief Handler invoked when test timeout is hit.
+ //
+ // This callback stops all running (hanging) tasks on IO service.
+ void testTimeoutHandler() {
+ service_.stop();
+ FAIL() << "Test timeout hit.";
+ }
+
+ // @brief Handler invoked when test request is received.
+ //
+ // This callback handler is installed when performing async read on a
+ // socket to emulate reception of the DNS Update request by a server.
+ // As a result, this handler will send an appropriate DNS Update response
+ // message back to the address from which the request has come.
+ //
+ // @param socket A pointer to a socket used to receive a query and send a
+ // response.
+ // @param remote A pointer to an object which specifies the host (address
+ // and port) from which a request has come.
+ // @param receive_length A length (in bytes) of the received data.
+ // @param corrupt_response A bool value which indicates that the server's
+ // response should be invalid (true) or valid (false)
+ void udpReceiveHandler(udp::socket* socket, udp::endpoint* remote,
+ size_t receive_length, const bool corrupt_response) {
+ // The easiest way to create a response message is to copy the entire
+ // request.
+ OutputBuffer response_buf(receive_length);
+ response_buf.writeData(receive_buffer_, receive_length);
+ // If a response is to be valid, we have to modify it slightly. If not,
+ // we leave it as is.
+ if (!corrupt_response) {
+ // For a valid response the QR bit must be set. This bit
+ // differentiates both types of messages. Note that the 3rd byte of
+ // the message header comprises this bit in the front followed by
+ // the message code and reserved zeros. Therefore, this byte
+ // has the following value:
+ // 10101000,
+ // where a leading bit is a QR flag. The hexadecimal value is 0xA8.
+ // Write it at message offset 2.
+ response_buf.writeUint8At(0xA8, 2);
+ }
+ // A response message is now ready to send. Send it!
+ socket->send_to(asio::buffer(response_buf.getData(),
+ response_buf.getLength()),
+ *remote);
+ }
+
+ // This test verifies that when invalid response placeholder object is
+ // passed to a constructor, constructor throws the appropriate exception.
+ // It also verifies that the constructor will not throw if the supplied
+ // callback object is NULL.
+ void runConstructorTest() {
+ D2UpdateMessagePtr null_response;
+ EXPECT_THROW(DNSClient(null_response, this, DNSClient::UDP),
+ isc::BadValue);
+ EXPECT_NO_THROW(DNSClient(response_, NULL, DNSClient::UDP));
+
+ // The TCP Transport is not supported right now. So, we return exception
+ // if caller specified TCP as a preferred protocol. This test will be
+ // removed once TCP is supported.
+ EXPECT_THROW(DNSClient(response_, NULL, DNSClient::TCP),
+ isc::NotImplemented);
+ }
+
+ // This test verifies that it accepted timeout values belong to the range of
+ // <0, DNSClient::getMaxTimeout()>.
+ void runInvalidTimeoutTest() {
+
+ expect_response_ = false;
+
+ // Create outgoing message. Simply set the required message fields:
+ // error code and Zone section. This is enough to create on-wire format
+ // of this message and send it.
+ D2UpdateMessage message(D2UpdateMessage::OUTBOUND);
+ ASSERT_NO_THROW(message.setRcode(Rcode(Rcode::NOERROR_CODE)));
+ ASSERT_NO_THROW(message.setZone(Name("example.com"), RRClass::IN()));
+
+ // Start with a valid timeout equal to maximal allowed. This way we will
+ // ensure that doUpdate doesn't throw an exception for valid timeouts.
+ unsigned int timeout = DNSClient::getMaxTimeout();
+ EXPECT_NO_THROW(dns_client_->doUpdate(service_, IOAddress(TEST_ADDRESS),
+ TEST_PORT, message, timeout));
+
+ // Cross the limit and expect that exception is thrown this time.
+ timeout = DNSClient::getMaxTimeout() + 1;
+ EXPECT_THROW(dns_client_->doUpdate(service_, IOAddress(TEST_ADDRESS),
+ TEST_PORT, message, timeout),
+ isc::BadValue);
+ }
+
+ // This test verifies that isc::NotImplemented exception is thrown when
+ // attempt to send DNS Update message with TSIG is attempted.
+ void runTSIGTest() {
+ // Create outgoing message. Simply set the required message fields:
+ // error code and Zone section. This is enough to create on-wire format
+ // of this message and send it.
+ D2UpdateMessage message(D2UpdateMessage::OUTBOUND);
+ ASSERT_NO_THROW(message.setRcode(Rcode(Rcode::NOERROR_CODE)));
+ ASSERT_NO_THROW(message.setZone(Name("example.com"), RRClass::IN()));
+
+ const int timeout = 0;
+ // Try to send DNS Update with TSIG key. Currently TSIG is not supported
+ // and therefore we expect an exception.
+ TSIGKey tsig_key("key.example:MSG6Ng==");
+ EXPECT_THROW(dns_client_->doUpdate(service_, IOAddress(TEST_ADDRESS),
+ TEST_PORT, message, timeout,
+ tsig_key),
+ isc::NotImplemented);
+ }
+
+ // This test verifies the DNSClient behavior when a server does not respond
+ // do the DNS Update message. In such case, the callback function is
+ // expected to be called and the TIME_OUT error code should be returned.
+ void runSendNoReceiveTest() {
+ // We expect no response from a server.
+ expect_response_ = false;
+
+ // Create outgoing message. Simply set the required message fields:
+ // error code and Zone section. This is enough to create on-wire format
+ // of this message and send it.
+ D2UpdateMessage message(D2UpdateMessage::OUTBOUND);
+ ASSERT_NO_THROW(message.setRcode(Rcode(Rcode::NOERROR_CODE)));
+ ASSERT_NO_THROW(message.setZone(Name("example.com"), RRClass::IN()));
+
+ // Set the response wait time to 0 so as our test is not hanging. This
+ // should cause instant timeout.
+ const int timeout = 0;
+ // The doUpdate() function starts asynchronous message exchange with DNS
+ // server. When message exchange is done or timeout occurs, the
+ // completion callback will be triggered. The doUpdate function returns
+ // immediately.
+ EXPECT_NO_THROW(dns_client_->doUpdate(service_, IOAddress(TEST_ADDRESS),
+ TEST_PORT, message, timeout));
+
+ // This starts the execution of tasks posted to IOService. run() blocks
+ // until stop() is called in the completion callback function.
+ service_.run();
+
+ }
+
+ // This test verifies that DNSClient can send DNS Update and receive a
+ // corresponding response from a server.
+ void runSendReceiveTest(const bool corrupt_response,
+ const bool two_sends) {
+ corrupt_response_ = corrupt_response;
+
+ // Create a request DNS Update message.
+ D2UpdateMessage message(D2UpdateMessage::OUTBOUND);
+ ASSERT_NO_THROW(message.setRcode(Rcode(Rcode::NOERROR_CODE)));
+ ASSERT_NO_THROW(message.setZone(Name("example.com"), RRClass::IN()));
+
+ // In order to perform the full test, when the client sends the request
+ // and receives a response from the server, we have to emulate the
+ // server's response in the test. A request will be sent via loopback
+ // interface to 127.0.0.1 and known test port. Response must be sent
+ // to 127.0.0.1 and a source port which has been used to send the
+ // request. A new socket is created, specifically to handle sending
+ // responses. The reuse address option is set so as both sockets can
+ // use the same address. This new socket is bound to the test address
+ // and port, where requests will be sent.
+ udp::socket udp_socket(service_.get_io_service(), asio::ip::udp::v4());
+ udp_socket.set_option(socket_base::reuse_address(true));
+ udp_socket.bind(udp::endpoint(address::from_string(TEST_ADDRESS),
+ TEST_PORT));
+ // Once socket is created, we can post an IO request to receive some
+ // a packet from this socket. This is asynchronous operation and
+ // nothing is received until another IO request to send a query is
+ // posted and the run() is invoked on this IO. A callback function is
+ // attached to this asynchronous read. This callback function requires
+ // that a socket object used to receive the request is passed to it,
+ // because the same socket will be used by the callback function to send
+ // a response. Also, the remote object is passed to the callback,
+ // because it holds a source address and port where request originated.
+ // Callback function will send a response to this address and port.
+ // The last parameter holds a length of the received request. It is
+ // required to construct a response.
+ udp::endpoint remote;
+ udp_socket.async_receive_from(asio::buffer(receive_buffer_,
+ sizeof(receive_buffer_)),
+ remote,
+ boost::bind(&DNSClientTest::udpReceiveHandler,
+ this, &udp_socket, &remote, _2,
+ corrupt_response));
+
+ // The socket is now ready to receive the data. Let's post some request
+ // message then.
+ const int timeout = 5;
+ dns_client_->doUpdate(service_, IOAddress(TEST_ADDRESS), TEST_PORT,
+ message, timeout);
+
+ // It is possible to request that two packets are sent concurrently.
+ if (two_sends) {
+ dns_client_->doUpdate(service_, IOAddress(TEST_ADDRESS), TEST_PORT,
+ message, timeout);
+
+ }
+
+ // Kick of the message exchange by actually running the scheduled
+ // "send" and "receive" operations.
+ service_.run();
+
+ udp_socket.close();
+
+ }
+};
+
+// Verify that the DNSClient object can be created if provided parameters are
+// valid. Constructor should throw exceptions when parameters are invalid.
+TEST_F(DNSClientTest, constructor) {
+ runConstructorTest();
+}
+
+// This test verifies that the maximal allowed timeout value is maximal int
+// value.
+TEST_F(DNSClientTest, getMaxTimeout) {
+ EXPECT_EQ(std::numeric_limits<int>::max(), DNSClient::getMaxTimeout());
+}
+
+// Verify that timeout is reported when no response is received from DNS.
+TEST_F(DNSClientTest, timeout) {
+ runSendNoReceiveTest();
+}
+
+// Verify that exception is thrown when invalid (too high) timeout value is
+// specified for asynchronous DNS Update.
+TEST_F(DNSClientTest, invalidTimeout) {
+ runInvalidTimeoutTest();
+}
+
+// Verify that exception is thrown when an attempt to send DNS Update with TSIG
+// is made. This test will be removed/changed once TSIG support is added.
+TEST_F(DNSClientTest, runTSIGTest) {
+ runTSIGTest();
+}
+
+// Verify that the DNSClient receives the response from DNS and the received
+// buffer can be decoded as DNS Update Response.
+TEST_F(DNSClientTest, sendReceive) {
+ // false means that server response is not corrupted.
+ runSendReceiveTest(false, false);
+}
+
+// Verify that the DNSClient reports an error when the response is received from
+// a DNS and this response is corrupted.
+TEST_F(DNSClientTest, sendReceiveCurrupted) {
+ // true means that server's response is corrupted.
+ runSendReceiveTest(true, false);
+}
+
+// Verify that it is possible to use the same DNSClient instance to
+// perform the following sequence of message exchanges:
+// 1. send
+// 2. receive
+// 3. send
+// 4. receive
+TEST_F(DNSClientTest, sendReceiveTwice) {
+ runSendReceiveTest(false, false);
+ runSendReceiveTest(false, false);
+}
+
+// Verify that it is possible to use the DNSClient instance to perform the
+// following sequence of message exchanges:
+// 1. send
+// 2. send
+// 3. receive
+// 4. receive
+TEST_F(DNSClientTest, concurrentSendReceive) {
+ runSendReceiveTest(false, true);
+}
+
+} // End of anonymous namespace
diff --git a/src/bin/d2/tests/ncr_unittests.cc b/src/bin/d2/tests/ncr_unittests.cc
new file mode 100644
index 0000000..8c7a9ac
--- /dev/null
+++ b/src/bin/d2/tests/ncr_unittests.cc
@@ -0,0 +1,401 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <d2/ncr_msg.h>
+#include <util/time_utilities.h>
+
+#include <gtest/gtest.h>
+#include <algorithm>
+
+using namespace std;
+using namespace isc;
+using namespace isc::d2;
+
+namespace {
+
+/// @brief Defines a list of valid JSON NameChangeRequest renditions.
+/// They are used as input to test conversion from JSON.
+const char *valid_msgs[] =
+{
+ // Valid Add.
+ "{"
+ " \"change_type\" : 0 , "
+ " \"forward_change\" : true , "
+ " \"reverse_change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip_address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease_expires_on\" : \"20130121132405\" , "
+ " \"lease_length\" : 1300 "
+ "}",
+ // Valid Remove.
+ "{"
+ " \"change_type\" : 1 , "
+ " \"forward_change\" : true , "
+ " \"reverse_change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip_address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease_expires_on\" : \"20130121132405\" , "
+ " \"lease_length\" : 1300 "
+ "}",
+ // Valid Add with IPv6 address
+ "{"
+ " \"change_type\" : 0 , "
+ " \"forward_change\" : true , "
+ " \"reverse_change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip_address\" : \"fe80::2acf:e9ff:fe12:e56f\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease_expires_on\" : \"20130121132405\" , "
+ " \"lease_length\" : 1300 "
+ "}"
+};
+
+/// @brief Defines a list of invalid JSON NameChangeRequest renditions.
+/// They are used as input to test conversion from JSON.
+const char *invalid_msgs[] =
+{
+ // Invalid change type.
+ "{"
+ " \"change_type\" : 7 , "
+ " \"forward_change\" : true , "
+ " \"reverse_change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip_address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease_expires_on\" : \"20130121132405\" , "
+ " \"lease_length\" : 1300 "
+ "}",
+ // Invalid forward change.
+ "{"
+ " \"change_type\" : 0 , "
+ " \"forward_change\" : \"bogus\" , "
+ " \"reverse_change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip_address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease_expires_on\" : \"20130121132405\" , "
+ " \"lease_length\" : 1300 "
+ "}",
+ // Invalid reverse change.
+ "{"
+ " \"change_type\" : 0 , "
+ " \"forward_change\" : true , "
+ " \"reverse_change\" : 500 , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip_address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease_expires_on\" : \"20130121132405\" , "
+ " \"lease_length\" : 1300 "
+ "}",
+ // Forward and reverse change both false.
+ "{"
+ " \"change_type\" : 0 , "
+ " \"forward_change\" : false , "
+ " \"reverse_change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip_address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease_expires_on\" : \"20130121132405\" , "
+ " \"lease_length\" : 1300 "
+ "}",
+ // Blank FQDN
+ "{"
+ " \"change_type\" : 0 , "
+ " \"forward_change\" : true , "
+ " \"reverse_change\" : false , "
+ " \"fqdn\" : \"\" , "
+ " \"ip_address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease_expires_on\" : \"20130121132405\" , "
+ " \"lease_length\" : 1300 "
+ "}",
+ // Bad IP address
+ "{"
+ " \"change_type\" : 0 , "
+ " \"forward_change\" : true , "
+ " \"reverse_change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip_address\" : \"xxxxxx\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease_expires_on\" : \"20130121132405\" , "
+ " \"lease_length\" : 1300 "
+ "}",
+ // Blank DHCID
+ "{"
+ " \"change_type\" : 0 , "
+ " \"forward_change\" : true , "
+ " \"reverse_change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip_address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"\" , "
+ " \"lease_expires_on\" : \"20130121132405\" , "
+ " \"lease_length\" : 1300 "
+ "}",
+ // Odd number of digits in DHCID
+ "{"
+ " \"change_type\" : 0 , "
+ " \"forward_change\" : true , "
+ " \"reverse_change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip_address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3\" , "
+ " \"lease_expires_on\" : \"20130121132405\" , "
+ " \"lease_length\" : 1300 "
+ "}",
+ // Text in DHCID
+ "{"
+ " \"change_type\" : 0 , "
+ " \"forward_change\" : true , "
+ " \"reverse_change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip_address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"THIS IS BOGUS!!!\" , "
+ " \"lease_expires_on\" : \"20130121132405\" , "
+ " \"lease_length\" : 1300 "
+ "}",
+ // Invalid lease expiration string
+ "{"
+ " \"change_type\" : 0 , "
+ " \"forward_change\" : true , "
+ " \"reverse_change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip_address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease_expires_on\" : \"Wed Jun 26 13:46:46 EDT 2013\" , "
+ " \"lease_length\" : 1300 "
+ "}",
+ // Non-integer for lease length.
+ "{"
+ " \"change_type\" : 0 , "
+ " \"forward_change\" : true , "
+ " \"reverse_change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip_address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease_expires_on\" : \"20130121132405\" , "
+ " \"lease_length\" : \"BOGUS\" "
+ "}"
+
+};
+
+/// @brief Tests the NameChangeRequest constructors.
+/// This test verifies that:
+/// 1. Default constructor works.
+/// 2. "Full" constructor, when given valid parameter values, works.
+/// 3. "Full" constructor, given a blank FQDN fails
+/// 4. "Full" constructor, given an invalid IP Address FQDN fails
+/// 5. "Full" constructor, given a blank DHCID fails
+/// 6. "Full" constructor, given false for both forward and reverse fails
+TEST(NameChangeRequestTest, constructionTests) {
+ // Verify the default constructor works.
+ NameChangeRequestPtr ncr;
+ EXPECT_NO_THROW(ncr.reset(new NameChangeRequest()));
+ EXPECT_TRUE(ncr);
+
+ // Verify that full constructor works.
+ uint64_t expiry = isc::util::detail::gettimeWrapper();
+ D2Dhcid dhcid("010203040A7F8E3D");
+
+ EXPECT_NO_THROW(ncr.reset(new NameChangeRequest(
+ CHG_ADD, true, true, "walah.walah.com",
+ "192.168.1.101", dhcid, expiry, 1300)));
+ EXPECT_TRUE(ncr);
+ ncr.reset();
+
+ // Verify blank FQDN is detected.
+ EXPECT_THROW(NameChangeRequest(CHG_ADD, true, true, "",
+ "192.168.1.101", dhcid, expiry, 1300), NcrMessageError);
+
+ // Verify that an invalid IP address is detected.
+ EXPECT_THROW(NameChangeRequest(CHG_ADD, true, true, "valid.fqdn",
+ "xxx.168.1.101", dhcid, expiry, 1300), NcrMessageError);
+
+ // Verify that a blank DHCID is detected.
+ D2Dhcid blank_dhcid;
+ EXPECT_THROW(NameChangeRequest(CHG_ADD, true, true, "walah.walah.com",
+ "192.168.1.101", blank_dhcid, expiry, 1300), NcrMessageError);
+
+ // Verify that one or both of direction flags must be true.
+ EXPECT_THROW(NameChangeRequest(CHG_ADD, false, false, "valid.fqdn",
+ "192.168.1.101", dhcid, expiry, 1300), NcrMessageError);
+
+}
+
+/// @brief Tests the basic workings of D2Dhcid to and from string conversions.
+/// It verifies that:
+/// 1. DHCID input strings must contain an even number of characters
+/// 2. DHCID input strings must contain only hexadecimal character digits
+/// 3. A valid DHCID string converts correctly.
+/// 4. Converting a D2Dhcid to a string works correctly.
+TEST(NameChangeRequestTest, dhcidTest) {
+ D2Dhcid dhcid;
+
+ // Odd number of digits should be rejected.
+ std::string test_str = "010203040A7F8E3";
+ EXPECT_THROW(dhcid.fromStr(test_str), NcrMessageError);
+
+ // Non digit content should be rejected.
+ test_str = "0102BOGUSA7F8E3D";
+ EXPECT_THROW(dhcid.fromStr(test_str), NcrMessageError);
+
+ // Verify that valid input converts into a proper byte array.
+ test_str = "010203040A7F8E3D";
+ ASSERT_NO_THROW(dhcid.fromStr(test_str));
+
+ // Create a test vector of expected byte contents.
+ const uint8_t bytes[] = { 0x1, 0x2, 0x3, 0x4, 0xA, 0x7F, 0x8E, 0x3D };
+ std::vector<uint8_t> expected_bytes(bytes, bytes + sizeof(bytes));
+
+ // Fetch the byte vector from the dhcid and verify if equals the expected
+ // content.
+ const std::vector<uint8_t>& converted_bytes = dhcid.getBytes();
+ EXPECT_EQ(expected_bytes.size(), converted_bytes.size());
+ EXPECT_TRUE (std::equal(expected_bytes.begin(),
+ expected_bytes.begin()+expected_bytes.size(),
+ converted_bytes.begin()));
+
+ // Convert the new dhcid back to string and verify it matches the original
+ // DHCID input string.
+ std::string next_str = dhcid.toStr();
+ EXPECT_EQ(test_str, next_str);
+}
+
+/// @brief Verifies the fundamentals of converting from and to JSON.
+/// It verifies that:
+/// 1. A NameChangeRequest can be created from a valid JSON string.
+/// 2. A valid JSON string can be created from a NameChangeRequest
+TEST(NameChangeRequestTest, basicJsonTest) {
+ // Define valid JSON rendition of a request.
+ std::string msg_str = "{"
+ "\"change_type\":1,"
+ "\"forward_change\":true,"
+ "\"reverse_change\":false,"
+ "\"fqdn\":\"walah.walah.com\","
+ "\"ip_address\":\"192.168.2.1\","
+ "\"dhcid\":\"010203040A7F8E3D\","
+ "\"lease_expires_on\":\"20130121132405\","
+ "\"lease_length\":1300"
+ "}";
+
+ // Verify that a NameChangeRequests can be instantiated from the
+ // a valid JSON rendition.
+ NameChangeRequestPtr ncr;
+ ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(msg_str));
+ ASSERT_TRUE(ncr);
+
+ // Verify that the JSON string created by the new request equals the
+ // original input string.
+ std::string json_str = ncr->toJSON();
+ EXPECT_EQ(msg_str, json_str);
+}
+
+/// @brief Tests a variety of invalid JSON message strings.
+/// This test iterates over a list of JSON messages, each containing a single
+/// content error. The list of messages is defined by the global array,
+/// invalid_messages. Currently that list contains the following invalid
+/// conditions:
+/// 1. Invalid change type
+/// 2. Invalid forward change
+/// 3. Invalid reverse change
+/// 4. Forward and reverse change both false
+/// 5. Invalid forward change
+/// 6. Blank FQDN
+/// 7. Bad IP address
+/// 8. Blank DHCID
+/// 9. Odd number of digits in DHCID
+/// 10. Text in DHCID
+/// 11. Invalid lease expiration string
+/// 12. Non-integer for lease length.
+/// If more permutations arise they can easily be added to the list.
+TEST(NameChangeRequestTest, invalidMsgChecks) {
+ // Iterate over the list of JSON strings, attempting to create a
+ // NameChangeRequest. The attempt should throw a NcrMessageError.
+ int num_msgs = sizeof(invalid_msgs)/sizeof(char*);
+ for (int i = 0; i < num_msgs; i++) {
+ EXPECT_THROW(NameChangeRequest::fromJSON(invalid_msgs[i]),
+ NcrMessageError) << "Invalid message not caught idx: "
+ << i << std::endl << " text:[" << invalid_msgs[i] << "]"
+ << std::endl;
+ }
+}
+
+/// @brief Tests a variety of valid JSON message strings.
+/// This test iterates over a list of JSON messages, each containing a single
+/// valid request rendition. The list of messages is defined by the global
+/// array, valid_messages. Currently that list contains the following valid
+/// messages:
+/// 1. Valid, IPv4 Add
+/// 2. Valid, IPv4 Remove
+/// 3. Valid, IPv6 Add
+/// If more permutations arise they can easily be added to the list.
+TEST(NameChangeRequestTest, validMsgChecks) {
+ // Iterate over the list of JSON strings, attempting to create a
+ // NameChangeRequest. The attempt should succeed.
+ int num_msgs = sizeof(valid_msgs)/sizeof(char*);
+ for (int i = 0; i < num_msgs; i++) {
+ EXPECT_NO_THROW(NameChangeRequest::fromJSON(valid_msgs[i]))
+ << "Valid message failed, message idx: " << i
+ << std::endl << " text:[" << valid_msgs[i] << "]"
+ << std::endl;
+ }
+}
+
+/// @brief Tests converting to and from JSON via isc::util buffer classes.
+/// This test verifies that:
+/// 1. A NameChangeRequest can be rendered in JSON written to an OutputBuffer
+/// 2. A InputBuffer containing a valid JSON request rendition can be used
+/// to create a NameChangeRequest.
+TEST(NameChangeRequestTest, toFromBufferTest) {
+ // Define a string containing a valid JSON NameChangeRequest rendition.
+ std::string msg_str = "{"
+ "\"change_type\":1,"
+ "\"forward_change\":true,"
+ "\"reverse_change\":false,"
+ "\"fqdn\":\"walah.walah.com\","
+ "\"ip_address\":\"192.168.2.1\","
+ "\"dhcid\":\"010203040A7F8E3D\","
+ "\"lease_expires_on\":\"20130121132405\","
+ "\"lease_length\":1300"
+ "}";
+
+ // Create a request from JSON directly.
+ NameChangeRequestPtr ncr;
+ ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(msg_str));
+
+ // Verify that we output the request as JSON text to a buffer
+ // without error.
+ isc::util::OutputBuffer output_buffer(1024);
+ ASSERT_NO_THROW(ncr->toFormat(FMT_JSON, output_buffer));
+
+ // Make an InputBuffer from the OutputBuffer.
+ isc::util::InputBuffer input_buffer(output_buffer.getData(),
+ output_buffer.getLength());
+
+ // Verify that we can create a new request from the InputBuffer.
+ NameChangeRequestPtr ncr2;
+ ASSERT_NO_THROW(ncr2 =
+ NameChangeRequest::fromFormat(FMT_JSON, input_buffer));
+
+ // Convert the new request to JSON directly.
+ std::string final_str = ncr2->toJSON();
+
+ // Verify that the final string matches the original.
+ ASSERT_EQ(final_str, msg_str);
+}
+
+
+} // end of anonymous namespace
+
diff --git a/src/bin/d2/tests/test_data_files_config.h.in b/src/bin/d2/tests/test_data_files_config.h.in
new file mode 100644
index 0000000..6064d3d
--- /dev/null
+++ b/src/bin/d2/tests/test_data_files_config.h.in
@@ -0,0 +1,17 @@
+// Copyright (C) 2009 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/// @brief Path to D2 source dir so tests against the dhcp-ddns.spec file
+/// can find it reliably.
+#define D2_SRC_DIR "@abs_top_srcdir@/src/bin/d2"
diff --git a/src/bin/memmgr/.gitignore b/src/bin/memmgr/.gitignore
index 3743d58..b92e3f5 100644
--- a/src/bin/memmgr/.gitignore
+++ b/src/bin/memmgr/.gitignore
@@ -2,3 +2,4 @@
/memmgr.py
/memmgr.spec
/b10-memmgr.8
+/memmgr.spec.pre
diff --git a/src/bin/xfrin/b10-xfrin.xml b/src/bin/xfrin/b10-xfrin.xml
index 5063732..903a3a0 100644
--- a/src/bin/xfrin/b10-xfrin.xml
+++ b/src/bin/xfrin/b10-xfrin.xml
@@ -231,98 +231,107 @@ operation
<variablelist>
<varlistentry>
- <term><replaceable>zonename</replaceable></term>
+ <term><replaceable>classname</replaceable></term>
<listitem><simpara>
- An actual zone name or special zone name
- <quote>_SERVER_</quote> representing the entire server.
- Zone classes (e.g. IN, CH, and HS) are mixed and counted so
- far. But these will be distinguished in future release.
+ An actual RR class name of the zone, e.g. IN, CH, and HS
</simpara>
<variablelist>
<varlistentry>
- <term>soaoutv4</term>
+ <term><replaceable>zonename</replaceable></term>
<listitem><simpara>
- Number of IPv4 SOA queries sent from Xfrin
- </simpara></listitem>
- </varlistentry>
-
- <varlistentry>
- <term>soaoutv6</term>
- <listitem><simpara>
- Number of IPv6 SOA queries sent from Xfrin
- </simpara></listitem>
- </varlistentry>
-
- <varlistentry>
- <term>axfrreqv4</term>
- <listitem><simpara>
- Number of IPv4 AXFR requests sent from Xfrin
- </simpara></listitem>
- </varlistentry>
-
- <varlistentry>
- <term>axfrreqv6</term>
- <listitem><simpara>
- Number of IPv6 AXFR requests sent from Xfrin
- </simpara></listitem>
- </varlistentry>
-
- <varlistentry>
- <term>ixfrreqv4</term>
- <listitem><simpara>
- Number of IPv4 IXFR requests sent from Xfrin
- </simpara></listitem>
- </varlistentry>
-
- <varlistentry>
- <term>ixfrreqv6</term>
- <listitem><simpara>
- Number of IPv6 IXFR requests sent from Xfrin
- </simpara></listitem>
- </varlistentry>
-
- <varlistentry>
- <term>xfrsuccess</term>
- <listitem><simpara>
- Number of zone transfer requests succeeded.
- These include the case where the zone turns
- out to be the latest as a result of an
- initial SOA query (and there is actually no
- AXFR or IXFR transaction).
- </simpara></listitem>
- </varlistentry>
-
- <varlistentry>
- <term>xfrfail</term>
- <listitem><simpara>
- Number of zone transfer requests failed
- </simpara></listitem>
- </varlistentry>
-
- <varlistentry>
- <term>last_axfr_duration</term>
- <listitem><simpara>
- Duration in seconds of the last successful AXFR. 0.0
- means no successful AXFR done or means a successful AXFR
- done in less than a microsecond. If an AXFR is aborted
- due to some failure, this duration won't be updated.
- </simpara></listitem>
- </varlistentry>
-
- <varlistentry>
- <term>last_ixfr_duration</term>
- <listitem><simpara>
- Duration in seconds of the last successful IXFR. 0.0
- means no successful IXFR done or means a successful IXFR
- done in less than a microsecond. If an IXFR is aborted
- due to some failure, this duration won't be updated.
- </simpara></listitem>
- </varlistentry>
+ An actual zone name or special zone name
+ <quote>_SERVER_</quote> representing the entire server
+ </simpara>
+ <variablelist>
+
+ <varlistentry>
+ <term>soaoutv4</term>
+ <listitem><simpara>
+ Number of IPv4 SOA queries sent from Xfrin
+ </simpara></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>soaoutv6</term>
+ <listitem><simpara>
+ Number of IPv6 SOA queries sent from Xfrin
+ </simpara></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>axfrreqv4</term>
+ <listitem><simpara>
+ Number of IPv4 AXFR requests sent from Xfrin
+ </simpara></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>axfrreqv6</term>
+ <listitem><simpara>
+ Number of IPv6 AXFR requests sent from Xfrin
+ </simpara></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>ixfrreqv4</term>
+ <listitem><simpara>
+ Number of IPv4 IXFR requests sent from Xfrin
+ </simpara></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>ixfrreqv6</term>
+ <listitem><simpara>
+ Number of IPv6 IXFR requests sent from Xfrin
+ </simpara></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>xfrsuccess</term>
+ <listitem><simpara>
+ Number of zone transfer requests succeeded.
+ These include the case where the zone turns
+ out to be the latest as a result of an
+ initial SOA query (and there is actually no
+ AXFR or IXFR transaction).
+ </simpara></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>xfrfail</term>
+ <listitem><simpara>
+ Number of zone transfer requests failed
+ </simpara></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>last_axfr_duration</term>
+ <listitem><simpara>
+ Duration in seconds of the last successful AXFR. 0.0
+ means no successful AXFR done or means a successful AXFR
+ done in less than a microsecond. If an AXFR is aborted
+ due to some failure, this duration won't be updated.
+ </simpara></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>last_ixfr_duration</term>
+ <listitem><simpara>
+ Duration in seconds of the last successful IXFR. 0.0
+ means no successful IXFR done or means a successful IXFR
+ done in less than a microsecond. If an IXFR is aborted
+ due to some failure, this duration won't be updated.
+ </simpara></listitem>
+ </varlistentry>
+
+ </variablelist>
+ </listitem>
+ </varlistentry><!-- end of zonename -->
</variablelist>
</listitem>
- </varlistentry><!-- end of zonename -->
+ </varlistentry><!-- end of classname -->
</variablelist>
</listitem>
diff --git a/src/bin/xfrin/tests/xfrin_test.py b/src/bin/xfrin/tests/xfrin_test.py
index c431f4c..0a10311 100644
--- a/src/bin/xfrin/tests/xfrin_test.py
+++ b/src/bin/xfrin/tests/xfrin_test.py
@@ -2182,6 +2182,7 @@ class TestStatisticsXfrinConn(TestXfrinConnection):
name2count.update(overwrite)
for (name, exp) in name2count.items():
act = self.conn._counters.get(self.__zones,
+ TEST_RRCLASS_STR,
TEST_ZONE_NAME_STR,
name)
msg = '%s is expected %s but actually %s' % (name, exp, act)
diff --git a/src/bin/xfrin/xfrin.py.in b/src/bin/xfrin/xfrin.py.in
index 6ff2805..ec38952 100755
--- a/src/bin/xfrin/xfrin.py.in
+++ b/src/bin/xfrin/xfrin.py.in
@@ -875,8 +875,9 @@ class XfrinConnection(asyncore.dispatcher):
self._send_query(RRType.SOA)
# count soaoutv4 or soaoutv6 requests
- self._counters.inc('zones', self._zone_name.to_text(),
- 'soaout' + self._get_ipver_str())
+ self._counters.inc('zones', self._rrclass.to_text(),
+ self._zone_name.to_text(), 'soaout' +
+ self._get_ipver_str())
data_len = self._get_request_response(2)
msg_len = socket.htons(struct.unpack('H', data_len)[0])
soa_response = self._get_request_response(msg_len)
@@ -918,12 +919,17 @@ class XfrinConnection(asyncore.dispatcher):
# Note: If the timer for the zone is already started but
# not yet stopped due to some error, the last start time
# is overwritten at this point.
- self._counters.start_timer('zones', self._zone_name.to_text(),
- 'last_' + req_str.lower() + '_duration')
+ self._counters.start_timer('zones',
+ self._rrclass.to_text(),
+ self._zone_name.to_text(),
+ 'last_' + req_str.lower() +
+ '_duration')
logger.info(XFRIN_XFR_TRANSFER_STARTED, req_str, self.zone_str())
# An AXFR or IXFR is being requested.
- self._counters.inc('zones', self._zone_name.to_text(),
- req_str.lower() + 'req' + self._get_ipver_str())
+ self._counters.inc('zones', self._rrclass.to_text(),
+ self._zone_name.to_text(),
+ req_str.lower() + 'req' +
+ self._get_ipver_str())
self._send_query(self._request_type)
self.__state = XfrinInitialSOA()
self._handle_xfrin_responses()
@@ -988,11 +994,13 @@ class XfrinConnection(asyncore.dispatcher):
# A xfrsuccess or xfrfail counter is incremented depending on
# the result.
result = {XFRIN_OK: 'xfrsuccess', XFRIN_FAIL: 'xfrfail'}[ret]
- self._counters.inc('zones', self._zone_name.to_text(), result)
+ self._counters.inc('zones', self._rrclass.to_text(),
+ self._zone_name.to_text(), result)
# The started statistics timer is finally stopped only in
# a successful case.
if ret == XFRIN_OK:
self._counters.stop_timer('zones',
+ self._rrclass.to_text(),
self._zone_name.to_text(),
'last_' + req_str.lower() +
'_duration')
diff --git a/src/bin/xfrin/xfrin.spec b/src/bin/xfrin/xfrin.spec
index 6d1e3a0..d09685b 100644
--- a/src/bin/xfrin/xfrin.spec
+++ b/src/bin/xfrin/xfrin.spec
@@ -135,110 +135,120 @@
"item_type": "named_set",
"item_optional": false,
"item_default": {
- "_SERVER_" : {
- "soaoutv4": 0,
- "soaoutv6": 0,
- "axfrreqv4": 0,
- "axfrreqv6": 0,
- "ixfrreqv4": 0,
- "ixfrreqv6": 0,
- "xfrsuccess": 0,
- "xfrfail": 0,
- "last_ixfr_duration": 0.0,
- "last_axfr_duration": 0.0
+ "IN" : {
+ "_SERVER_" : {
+ "soaoutv4": 0,
+ "soaoutv6": 0,
+ "axfrreqv4": 0,
+ "axfrreqv6": 0,
+ "ixfrreqv4": 0,
+ "ixfrreqv6": 0,
+ "xfrsuccess": 0,
+ "xfrfail": 0,
+ "last_ixfr_duration": 0.0,
+ "last_axfr_duration": 0.0
+ }
}
},
"item_title": "Zone names",
"item_description": "A directory name of per-zone statistics",
"named_set_item_spec": {
- "item_name": "zonename",
- "item_type": "map",
+ "item_name": "classname",
+ "item_type": "named_set",
"item_optional": false,
"item_default": {},
- "item_title": "Zone name",
- "item_description": "An actual zone name or special zone name _SERVER_ representing the entire server. Zone classes (e.g. IN, CH, and HS) are mixed and counted so far. But these will be distinguished in future release.",
- "map_item_spec": [
- {
- "item_name": "soaoutv4",
- "item_type": "integer",
- "item_optional": false,
- "item_default": 0,
- "item_title": "SOAOutv4",
- "item_description": "Number of IPv4 SOA queries sent from Xfrin"
- },
- {
- "item_name": "soaoutv6",
- "item_type": "integer",
- "item_optional": false,
- "item_default": 0,
- "item_title": "SOAOutv6",
- "item_description": "Number of IPv6 SOA queries sent from Xfrin"
- },
- {
- "item_name": "axfrreqv4",
- "item_type": "integer",
- "item_optional": false,
- "item_default": 0,
- "item_title": "AXFRReqv4",
- "item_description": "Number of IPv4 AXFR requests sent from Xfrin"
- },
- {
- "item_name": "axfrreqv6",
- "item_type": "integer",
- "item_optional": false,
- "item_default": 0,
- "item_title": "AXFRReqv6",
- "item_description": "Number of IPv6 AXFR requests sent from Xfrin"
- },
- {
- "item_name": "ixfrreqv4",
- "item_type": "integer",
- "item_optional": false,
- "item_default": 0,
- "item_title": "IXFRReqv4",
- "item_description": "Number of IPv4 IXFR requests sent from Xfrin"
- },
- {
- "item_name": "ixfrreqv6",
- "item_type": "integer",
- "item_optional": false,
- "item_default": 0,
- "item_title": "IXFRReqv6",
- "item_description": "Number of IPv6 IXFR requests sent from Xfrin"
- },
- {
- "item_name": "xfrsuccess",
- "item_type": "integer",
- "item_optional": false,
- "item_default": 0,
- "item_title": "XfrSuccess",
- "item_description": "Number of zone transfer requests succeeded. These include the case where the zone turns out to be the latest as a result of an initial SOA query (and there is actually no AXFR or IXFR transaction)."
- },
- {
- "item_name": "xfrfail",
- "item_type": "integer",
- "item_optional": false,
- "item_default": 0,
- "item_title": "XfrFail",
- "item_description": "Number of zone transfer requests failed"
- },
- {
- "item_name": "last_axfr_duration",
- "item_type": "real",
- "item_optional": false,
- "item_default": 0.0,
- "item_title": "Last AXFR duration",
- "item_description": "Duration in seconds of the last successful AXFR. 0.0 means no successful AXFR done or means a successful AXFR done in less than a microsecond. If an AXFR is aborted due to some failure, this duration won't be updated."
- },
- {
- "item_name": "last_ixfr_duration",
- "item_type": "real",
- "item_optional": false,
- "item_default": 0.0,
- "item_title": "Last IXFR duration",
- "item_description": "Duration in seconds of the last successful IXFR. 0.0 means no successful IXFR done or means a successful IXFR done in less than a microsecond. If an IXFR is aborted due to some failure, this duration won't be updated."
- }
- ]
+ "item_title": "RR class name",
+ "item_description": "An actual RR class name of the zone, e.g. IN, CH, and HS",
+ "named_set_item_spec": {
+ "item_name": "zonename",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "item_title": "Zone name",
+ "item_description": "An actual zone name or special zone name _SERVER_ representing the entire server",
+ "map_item_spec": [
+ {
+ "item_name": "soaoutv4",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "SOAOutv4",
+ "item_description": "Number of IPv4 SOA queries sent from Xfrin"
+ },
+ {
+ "item_name": "soaoutv6",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "SOAOutv6",
+ "item_description": "Number of IPv6 SOA queries sent from Xfrin"
+ },
+ {
+ "item_name": "axfrreqv4",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "AXFRReqv4",
+ "item_description": "Number of IPv4 AXFR requests sent from Xfrin"
+ },
+ {
+ "item_name": "axfrreqv6",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "AXFRReqv6",
+ "item_description": "Number of IPv6 AXFR requests sent from Xfrin"
+ },
+ {
+ "item_name": "ixfrreqv4",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "IXFRReqv4",
+ "item_description": "Number of IPv4 IXFR requests sent from Xfrin"
+ },
+ {
+ "item_name": "ixfrreqv6",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "IXFRReqv6",
+ "item_description": "Number of IPv6 IXFR requests sent from Xfrin"
+ },
+ {
+ "item_name": "xfrsuccess",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "XfrSuccess",
+ "item_description": "Number of zone transfer requests succeeded. These include the case where the zone turns out to be the latest as a result of an initial SOA query (and there is actually no AXFR or IXFR transaction)."
+ },
+ {
+ "item_name": "xfrfail",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "XfrFail",
+ "item_description": "Number of zone transfer requests failed"
+ },
+ {
+ "item_name": "last_axfr_duration",
+ "item_type": "real",
+ "item_optional": false,
+ "item_default": 0.0,
+ "item_title": "Last AXFR duration",
+ "item_description": "Duration in seconds of the last successful AXFR. 0.0 means no successful AXFR done or means a successful AXFR done in less than a microsecond. If an AXFR is aborted due to some failure, this duration won't be updated."
+ },
+ {
+ "item_name": "last_ixfr_duration",
+ "item_type": "real",
+ "item_optional": false,
+ "item_default": 0.0,
+ "item_title": "Last IXFR duration",
+ "item_description": "Duration in seconds of the last successful IXFR. 0.0 means no successful IXFR done or means a successful IXFR done in less than a microsecond. If an IXFR is aborted due to some failure, this duration won't be updated."
+ }
+ ]
+ }
}
}
]
diff --git a/src/bin/xfrout/b10-xfrout.xml b/src/bin/xfrout/b10-xfrout.xml
index 5c71e05..468c6af 100644
--- a/src/bin/xfrout/b10-xfrout.xml
+++ b/src/bin/xfrout/b10-xfrout.xml
@@ -171,44 +171,56 @@
<variablelist>
<varlistentry>
- <term><replaceable>zonename</replaceable></term>
+ <term><replaceable>classname</replaceable></term>
<listitem><simpara>
- A actual zone name or special zone name <quote>_SERVER_</quote>
- representing an entire server
+ An actual RR class name of the zone, e.g. IN, CH, and HS
</simpara>
<variablelist>
<varlistentry>
- <term>notifyoutv4</term>
+ <term><replaceable>zonename</replaceable></term>
<listitem><simpara>
- Number of IPv4 notifies per zone name sent out from Xfrout
- </simpara></listitem>
- </varlistentry>
-
- <varlistentry>
- <term>notifyoutv6</term>
- <listitem><simpara>
- Number of IPv6 notifies per zone name sent out from Xfrout
- </simpara></listitem>
- </varlistentry>
-
- <varlistentry>
- <term>xfrrej</term>
- <listitem><simpara>
- Number of XFR requests per zone name rejected by Xfrout
- </simpara></listitem>
- </varlistentry>
-
- <varlistentry>
- <term>xfrreqdone</term>
- <listitem><simpara>
- Number of requested zone transfers per zone name completed
- </simpara></listitem>
- </varlistentry>
+ An actual zone name or special zone
+ name <quote>_SERVER_</quote> representing an entire
+ server
+ </simpara>
+ <variablelist>
+
+ <varlistentry>
+ <term>notifyoutv4</term>
+ <listitem><simpara>
+ Number of IPv4 notifies per zone name sent out from Xfrout
+ </simpara></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>notifyoutv6</term>
+ <listitem><simpara>
+ Number of IPv6 notifies per zone name sent out from Xfrout
+ </simpara></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>xfrrej</term>
+ <listitem><simpara>
+ Number of XFR requests per zone name rejected by Xfrout
+ </simpara></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>xfrreqdone</term>
+ <listitem><simpara>
+ Number of requested zone transfers per zone name completed
+ </simpara></listitem>
+ </varlistentry>
+
+ </variablelist>
+ </listitem>
+ </varlistentry><!-- end of zonename -->
</variablelist>
</listitem>
- </varlistentry><!-- end of zonename -->
+ </varlistentry><!-- end of classname -->
</variablelist>
</listitem>
diff --git a/src/bin/xfrout/tests/xfrout_test.py.in b/src/bin/xfrout/tests/xfrout_test.py.in
index 7c71e78..d20ebe8 100644
--- a/src/bin/xfrout/tests/xfrout_test.py.in
+++ b/src/bin/xfrout/tests/xfrout_test.py.in
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2012 Internet Systems Consortium.
+# Copyright (C) 2010-2013 Internet Systems Consortium.
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
@@ -41,6 +41,7 @@ TSIG_KEY = TSIGKey("example.com:SFuWd/q99SzF8Yzd1QbB9g==")
TEST_ZONE_NAME_STR = "example.com."
TEST_ZONE_NAME = Name(TEST_ZONE_NAME_STR)
TEST_RRCLASS = RRClass.IN
+TEST_RRCLASS_STR = TEST_RRCLASS.to_text()
IXFR_OK_VERSION = 2011111802
IXFR_NG_VERSION = 2011111803
SOA_CURRENT_VERSION = 2011112001
@@ -441,7 +442,8 @@ class TestXfroutSession(TestXfroutSessionBase):
# check the 'xfrrej' counter initially
self.assertRaises(isc.cc.data.DataNotFoundError,
self.xfrsess._counters.get, 'zones',
- TEST_ZONE_NAME_STR, 'xfrrej')
+ TEST_RRCLASS_STR, TEST_ZONE_NAME_STR,
+ 'xfrrej')
# Localhost (the default in this test) is accepted
rcode, msg = self.xfrsess._parse_query_message(self.mdata)
self.assertEqual(rcode.to_text(), "NOERROR")
@@ -457,7 +459,8 @@ class TestXfroutSession(TestXfroutSessionBase):
self.assertEqual(rcode.to_text(), "REFUSED")
# check the 'xfrrej' counter after incrementing
self.assertEqual(self.xfrsess._counters.get(
- 'zones', TEST_ZONE_NAME_STR, 'xfrrej'), 1)
+ 'zones', TEST_RRCLASS_STR, TEST_ZONE_NAME_STR,
+ 'xfrrej'), 1)
# TSIG signed request
request_data = self.create_request_data(with_tsig=True)
@@ -488,7 +491,8 @@ class TestXfroutSession(TestXfroutSessionBase):
self.assertEqual(rcode.to_text(), "REFUSED")
# check the 'xfrrej' counter after incrementing
self.assertEqual(self.xfrsess._counters.get(
- 'zones', TEST_ZONE_NAME_STR, 'xfrrej'), 2)
+ 'zones', TEST_RRCLASS_STR, TEST_ZONE_NAME_STR,
+ 'xfrrej'), 2)
# ACL using TSIG: no TSIG; should be rejected
acl_setter(isc.acl.dns.REQUEST_LOADER.load([
@@ -498,7 +502,8 @@ class TestXfroutSession(TestXfroutSessionBase):
self.assertEqual(rcode.to_text(), "REFUSED")
# check the 'xfrrej' counter after incrementing
self.assertEqual(self.xfrsess._counters.get(
- 'zones', TEST_ZONE_NAME_STR, 'xfrrej'), 3)
+ 'zones', TEST_RRCLASS_STR, TEST_ZONE_NAME_STR,
+ 'xfrrej'), 3)
#
# ACL using IP + TSIG: both should match
@@ -520,7 +525,8 @@ class TestXfroutSession(TestXfroutSessionBase):
self.assertEqual(rcode.to_text(), "REFUSED")
# check the 'xfrrej' counter after incrementing
self.assertEqual(self.xfrsess._counters.get(
- 'zones', TEST_ZONE_NAME_STR, 'xfrrej'), 4)
+ 'zones', TEST_RRCLASS_STR, TEST_ZONE_NAME_STR,
+ 'xfrrej'), 4)
# Address matches, but TSIG doesn't (not included)
self.xfrsess._remote = (socket.AF_INET, socket.SOCK_STREAM,
('192.0.2.1', 12345))
@@ -528,7 +534,8 @@ class TestXfroutSession(TestXfroutSessionBase):
self.assertEqual(rcode.to_text(), "REFUSED")
# check the 'xfrrej' counter after incrementing
self.assertEqual(self.xfrsess._counters.get(
- 'zones', TEST_ZONE_NAME_STR, 'xfrrej'), 5)
+ 'zones', TEST_RRCLASS_STR, TEST_ZONE_NAME_STR,
+ 'xfrrej'), 5)
# Neither address nor TSIG matches
self.xfrsess._remote = (socket.AF_INET, socket.SOCK_STREAM,
('192.0.2.2', 12345))
@@ -536,7 +543,8 @@ class TestXfroutSession(TestXfroutSessionBase):
self.assertEqual(rcode.to_text(), "REFUSED")
# check the 'xfrrej' counter after incrementing
self.assertEqual(self.xfrsess._counters.get(
- 'zones', TEST_ZONE_NAME_STR, 'xfrrej'), 6)
+ 'zones', TEST_RRCLASS_STR, TEST_ZONE_NAME_STR,
+ 'xfrrej'), 6)
def test_transfer_acl(self):
# ACL checks only with the default ACL
@@ -936,12 +944,14 @@ class TestXfroutSession(TestXfroutSessionBase):
self.assertRaises(isc.cc.data.DataNotFoundError,
self.xfrsess._counters.get,
- 'zones', TEST_ZONE_NAME_STR, 'xfrreqdone')
+ 'zones', TEST_RRCLASS_STR,
+ TEST_ZONE_NAME_STR, 'xfrreqdone')
self.xfrsess._reply_xfrout_query = myreply
self.xfrsess.dns_xfrout_start(self.sock, self.mdata)
self.assertEqual(self.sock.readsent(), b"success")
self.assertGreater(self.xfrsess._counters.get(
- 'zones', TEST_ZONE_NAME_STR, 'xfrreqdone'), 0)
+ 'zones', TEST_RRCLASS_STR, TEST_ZONE_NAME_STR,
+ 'xfrreqdone'), 0)
def test_reply_xfrout_query_axfr(self):
self.xfrsess._soa = self.soa_rrset
diff --git a/src/bin/xfrout/xfrout.py.in b/src/bin/xfrout/xfrout.py.in
index 0055bb1..e26bc9a 100755
--- a/src/bin/xfrout/xfrout.py.in
+++ b/src/bin/xfrout/xfrout.py.in
@@ -301,7 +301,8 @@ class XfroutSession():
return None, None
elif acl_result == REJECT:
# count rejected Xfr request by each zone name
- self._counters.inc('zones', zone_name.to_text(), 'xfrrej')
+ self._counters.inc('zones', zone_class.to_text(),
+ zone_name.to_text(), 'xfrrej')
logger.debug(DBG_XFROUT_TRACE, XFROUT_QUERY_REJECTED,
self._request_type, format_addrinfo(self._remote),
format_zone_str(zone_name, zone_class))
@@ -571,7 +572,8 @@ class XfroutSession():
else:
self._counters.dec('ixfr_running')
# count done Xfr requests by each zone name
- self._counters.inc('zones', zone_name.to_text(), 'xfrreqdone')
+ self._counters.inc('zones', zone_class.to_text(),
+ zone_name.to_text(), 'xfrreqdone')
logger.info(XFROUT_XFR_TRANSFER_DONE, self._request_typestr,
format_addrinfo(self._remote), zone_str)
diff --git a/src/bin/xfrout/xfrout.spec.pre.in b/src/bin/xfrout/xfrout.spec.pre.in
index 4277c3b..570f73e 100644
--- a/src/bin/xfrout/xfrout.spec.pre.in
+++ b/src/bin/xfrout/xfrout.spec.pre.in
@@ -121,56 +121,66 @@
"item_type": "named_set",
"item_optional": false,
"item_default": {
- "_SERVER_" : {
- "notifyoutv4" : 0,
- "notifyoutv6" : 0,
- "xfrrej" : 0,
- "xfrreqdone" : 0
+ "IN" : {
+ "_SERVER_" : {
+ "notifyoutv4" : 0,
+ "notifyoutv6" : 0,
+ "xfrrej" : 0,
+ "xfrreqdone" : 0
+ }
}
},
"item_title": "Zone names",
"item_description": "A directory name of per-zone statistics",
"named_set_item_spec": {
- "item_name": "zonename",
- "item_type": "map",
+ "item_name": "classname",
+ "item_type": "named_set",
"item_optional": false,
"item_default": {},
- "item_title": "Zone name",
- "item_description": "A actual zone name or special zone name _SERVER_ representing an entire server",
- "map_item_spec": [
- {
- "item_name": "notifyoutv4",
- "item_type": "integer",
- "item_optional": false,
- "item_default": 0,
- "item_title": "IPv4 notifies",
- "item_description": "Number of IPv4 notifies per zone name sent out from Xfrout"
- },
- {
- "item_name": "notifyoutv6",
- "item_type": "integer",
- "item_optional": false,
- "item_default": 0,
- "item_title": "IPv6 notifies",
- "item_description": "Number of IPv6 notifies per zone name sent out from Xfrout"
- },
- {
- "item_name": "xfrrej",
- "item_type": "integer",
- "item_optional": false,
- "item_default": 0,
- "item_title": "XFR rejected requests",
- "item_description": "Number of XFR requests per zone name rejected by Xfrout"
- },
- {
- "item_name": "xfrreqdone",
- "item_type": "integer",
- "item_optional": false,
- "item_default": 0,
- "item_title": "Requested zone transfers",
- "item_description": "Number of requested zone transfers completed per zone name"
- }
- ]
+ "item_title": "RR class name",
+ "item_description": "An actual RR class name of the zone, e.g. IN, CH, and HS",
+ "named_set_item_spec": {
+ "item_name": "zonename",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "item_title": "Zone name",
+ "item_description": "An actual zone name or special zone name _SERVER_ representing an entire server",
+ "map_item_spec": [
+ {
+ "item_name": "notifyoutv4",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "IPv4 notifies",
+ "item_description": "Number of IPv4 notifies per zone name sent out from Xfrout"
+ },
+ {
+ "item_name": "notifyoutv6",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "IPv6 notifies",
+ "item_description": "Number of IPv6 notifies per zone name sent out from Xfrout"
+ },
+ {
+ "item_name": "xfrrej",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "XFR rejected requests",
+ "item_description": "Number of XFR requests per zone name rejected by Xfrout"
+ },
+ {
+ "item_name": "xfrreqdone",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "Requested zone transfers",
+ "item_description": "Number of requested zone transfers completed per zone name"
+ }
+ ]
+ }
}
},
{
diff --git a/src/bin/zonemgr/tests/zonemgr_test.py b/src/bin/zonemgr/tests/zonemgr_test.py
index 111d650..5a17476 100644
--- a/src/bin/zonemgr/tests/zonemgr_test.py
+++ b/src/bin/zonemgr/tests/zonemgr_test.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010 Internet Systems Consortium.
+# Copyright (C) 2010-2013 Internet Systems Consortium.
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
@@ -22,6 +22,7 @@ import tempfile
from zonemgr import *
from isc.testutils.ccsession_mock import MockModuleCCSession
from isc.notify import notify_out
+from isc.datasrc import ZoneFinder
ZONE_NAME_CLASS1_IN = ("example.net.", "IN")
ZONE_NAME_CLASS1_CH = ("example.net.", "CH")
@@ -36,8 +37,6 @@ LOWERBOUND_RETRY = 5
REFRESH_JITTER = 0.10
RELOAD_JITTER = 0.75
-TEST_SQLITE3_DBFILE = os.getenv("TESTDATAOBJDIR") + '/initdb.file'
-
class ZonemgrTestException(Exception):
pass
@@ -46,16 +45,53 @@ class FakeCCSession(isc.config.ConfigData, MockModuleCCSession):
module_spec = isc.config.module_spec_from_file(SPECFILE_LOCATION)
ConfigData.__init__(self, module_spec)
MockModuleCCSession.__init__(self)
+ # For inspection
+ self.added_remote_modules = []
+
+ def add_remote_config_by_name(self, name, callback):
+ self.added_remote_modules.append((name, callback))
def rpc_call(self, command, module, instance="*", to="*", params=None):
if module not in ("Auth", "Xfrin"):
raise ZonemgrTestException("module name not exist")
- def get_remote_config_value(self, module_name, identifier):
- if module_name == "Auth" and identifier == "database_file":
- return TEST_SQLITE3_DBFILE, False
+class MockDataSourceClient():
+ '''A simple mock data source client.'''
+ def __init__(self):
+ self.rdata_net = 'a.example.net. root.example.net. 2009073106 ' + \
+ '7200 3600 2419200 21600'
+ self.rdata_org = 'a.example.org. root.example.org. 2009073112 ' + \
+ '7200 3600 2419200 21600'
+
+ def find_zone(self, zone_name):
+ '''Mock version of DataSourceClient.find_zone().'''
+ return (isc.datasrc.DataSourceClient.SUCCESS, self)
+
+ def find(self, name, rrtype, options=ZoneFinder.FIND_DEFAULT):
+ '''Mock version of ZoneFinder.find().'''
+ if name == Name('example.net'):
+ rdata = Rdata(RRType.SOA, RRClass.IN, self.rdata_net)
+ elif name == 'example.org.':
+ rdata = Rdata(RRType.SOA, RRClass.IN, self.rdata_org)
else:
- return "unknown", False
+ return (ZoneFinder.NXDOMAIN, None, 0)
+ rrset = RRset(name, RRClass.IN, RRType.SOA, RRTTL(3600))
+ rrset.add_rdata(rdata)
+ return (ZoneFinder.SUCCESS, rrset, 0)
+
+class MockDataSrcClientsMgr():
+ '''A simple mock data source client manager.'''
+ def __init__(self):
+ self.datasrc_client = MockDataSourceClient()
+
+ def get_client_list(self, rrclass):
+ return self
+
+ def find(self, zone_name, want_exact_match, want_finder):
+ """Pretending find method on the object returned by get_client_list"""
+ if issubclass(type(self.datasrc_client), Exception):
+ raise self.datasrc_client
+ return self.datasrc_client, None, None
class MyZonemgrRefresh(ZonemgrRefresh):
def __init__(self):
@@ -66,19 +102,8 @@ class MyZonemgrRefresh(ZonemgrRefresh):
self._reload_jitter = 0.75
self._refresh_jitter = 0.25
- def get_zone_soa(zone_name, db_file):
- if zone_name == 'example.net.':
- return (1, 2, 'example.net.', 'example.net.sd.', 21600, 'SOA', None,
- 'a.example.net. root.example.net. 2009073106 7200 3600 2419200 21600')
- elif zone_name == 'example.org.':
- return (1, 2, 'example.org.', 'example.org.sd.', 21600, 'SOA', None,
- 'a.example.org. root.example.org. 2009073112 7200 3600 2419200 21600')
- else:
- return None
- sqlite3_ds.get_zone_soa = get_zone_soa
-
- ZonemgrRefresh.__init__(self, TEST_SQLITE3_DBFILE, self._slave_socket,
- FakeCCSession())
+ ZonemgrRefresh.__init__(self, self._slave_socket, FakeCCSession())
+ self._datasrc_clients_mgr = MockDataSrcClientsMgr()
current_time = time.time()
self._zonemgr_refresh_info = {
('example.net.', 'IN'): {
@@ -95,19 +120,23 @@ class MyZonemgrRefresh(ZonemgrRefresh):
class TestZonemgrRefresh(unittest.TestCase):
def setUp(self):
- if os.path.exists(TEST_SQLITE3_DBFILE):
- os.unlink(TEST_SQLITE3_DBFILE)
self.stderr_backup = sys.stderr
sys.stderr = open(os.devnull, 'w')
self.zone_refresh = MyZonemgrRefresh()
self.cc_session = FakeCCSession()
def tearDown(self):
- if os.path.exists(TEST_SQLITE3_DBFILE):
- os.unlink(TEST_SQLITE3_DBFILE)
sys.stderr.close()
sys.stderr = self.stderr_backup
+ def test_init(self):
+ """Check some initial configuration after construction"""
+ # data source "module" should have been registrered as a necessary
+ # remote config
+ self.assertEqual([('data_sources',
+ self.zone_refresh._datasrc_config_handler)],
+ self.zone_refresh._module_cc.added_remote_modules)
+
def test_random_jitter(self):
max = 100025.120
jitter = 0
@@ -195,16 +224,9 @@ class TestZonemgrRefresh(unittest.TestCase):
def test_zonemgr_reload_zone(self):
soa_rdata = 'a.example.net. root.example.net. 2009073106 1800 900 2419200 21600'
- # We need to restore this not to harm other tests
- old_get_zone_soa = sqlite3_ds.get_zone_soa
- def get_zone_soa(zone_name, db_file):
- return (1, 2, 'example.net.', 'example.net.sd.', 21600, 'SOA', None,
- 'a.example.net. root.example.net. 2009073106 1800 900 2419200 21600')
- sqlite3_ds.get_zone_soa = get_zone_soa
-
+ self.zone_refresh._datasrc_clients_mgr.datasrc_client.rdata_net = soa_rdata
self.zone_refresh.zonemgr_reload_zone(ZONE_NAME_CLASS1_IN)
self.assertEqual(soa_rdata, self.zone_refresh._zonemgr_refresh_info[ZONE_NAME_CLASS1_IN]["zone_soa_rdata"])
- sqlite3_ds.get_zone_soa = old_get_zone_soa
def test_get_zone_notifier_master(self):
notify_master = "192.168.1.1"
@@ -275,24 +297,10 @@ class TestZonemgrRefresh(unittest.TestCase):
def test_send_command(self):
self.assertRaises(ZonemgrTestException, self.zone_refresh._send_command, "Unknown", "Notify", None)
- def test_zone_mgr_is_empty(self):
- self.assertFalse(self.zone_refresh._zone_mgr_is_empty())
- self.zone_refresh._zonemgr_refresh_info = {}
- self.assertTrue(self.zone_refresh._zone_mgr_is_empty())
-
def test_zonemgr_add_zone(self):
soa_rdata = 'a.example.net. root.example.net. 2009073106 1800 900 2419200 21600'
- # This needs to be restored. The following test actually failed if we left
- # this unclean
- old_get_zone_soa = sqlite3_ds.get_zone_soa
+ self.zone_refresh._datasrc_clients_mgr.datasrc_client.rdata_net = soa_rdata
time1 = time.time()
-
- def get_zone_soa(zone_name, db_file):
- return (1, 2, 'example.net.', 'example.net.sd.', 21600, 'SOA', None,
- 'a.example.net. root.example.net. 2009073106 1800 900 2419200 21600')
-
- sqlite3_ds.get_zone_soa = get_zone_soa
-
self.zone_refresh._zonemgr_refresh_info = {}
self.zone_refresh.zonemgr_add_zone(ZONE_NAME_CLASS1_IN)
self.assertEqual(1, len(self.zone_refresh._zonemgr_refresh_info))
@@ -306,12 +314,13 @@ class TestZonemgrRefresh(unittest.TestCase):
self.assertTrue((time1 + 900 * (1 - self.zone_refresh._reload_jitter)) <= zone_timeout)
self.assertTrue(zone_timeout <= time2 + 900)
- def get_zone_soa2(zone_name, db_file):
+ old_get_zone_soa = self.zone_refresh._get_zone_soa
+ def get_zone_soa2(zone_name_class):
return None
- sqlite3_ds.get_zone_soa = get_zone_soa2
+ self.zone_refresh._get_zone_soa = get_zone_soa2
self.zone_refresh.zonemgr_add_zone(ZONE_NAME_CLASS2_IN)
self.assertTrue(self.zone_refresh._zonemgr_refresh_info[ZONE_NAME_CLASS2_IN]["zone_soa_rdata"] is None)
- sqlite3_ds.get_zone_soa = old_get_zone_soa
+ self.zone_refresh._get_zone_soa = old_get_zone_soa
def test_zone_handle_notify(self):
self.assertTrue(self.zone_refresh.zone_handle_notify(
@@ -333,10 +342,7 @@ class TestZonemgrRefresh(unittest.TestCase):
def test_zone_refresh_success(self):
soa_rdata = 'a.example.net. root.example.net. 2009073106 1800 900 2419200 21600'
- def get_zone_soa(zone_name, db_file):
- return (1, 2, 'example.net.', 'example.net.sd.', 21600, 'SOA', None,
- 'a.example.net. root.example.net. 2009073106 1800 900 2419200 21600')
- sqlite3_ds.get_zone_soa = get_zone_soa
+ self.zone_refresh._datasrc_clients_mgr.datasrc_client.rdata_net = soa_rdata
time1 = time.time()
self.zone_refresh._zonemgr_refresh_info[ZONE_NAME_CLASS1_IN]["zone_state"] = ZONE_REFRESHING
self.zone_refresh.zone_refresh_success(ZONE_NAME_CLASS1_IN)
@@ -373,14 +379,14 @@ class TestZonemgrRefresh(unittest.TestCase):
self.assertRaises(ZonemgrException, self.zone_refresh.zone_refresh_fail, ZONE_NAME_CLASS3_CH)
self.assertRaises(ZonemgrException, self.zone_refresh.zone_refresh_fail, ZONE_NAME_CLASS3_IN)
- old_get_zone_soa = sqlite3_ds.get_zone_soa
- def get_zone_soa(zone_name, db_file):
+ old_get_zone_soa = self.zone_refresh._get_zone_soa
+ def get_zone_soa(zone_name_class):
return None
- sqlite3_ds.get_zone_soa = get_zone_soa
+ self.zone_refresh._get_zone_soa = get_zone_soa
self.zone_refresh.zone_refresh_fail(ZONE_NAME_CLASS1_IN)
self.assertEqual(self.zone_refresh._zonemgr_refresh_info[ZONE_NAME_CLASS1_IN]["zone_state"],
ZONE_EXPIRED)
- sqlite3_ds.get_zone_soa = old_get_zone_soa
+ self.zone_refresh._get_zone_soa = old_get_zone_soa
def test_find_need_do_refresh_zone(self):
time1 = time.time()
@@ -628,7 +634,6 @@ class MyZonemgr(Zonemgr):
def __exit__(self, type, value, traceback): pass
def __init__(self):
- self._db_file = TEST_SQLITE3_DBFILE
self._zone_refresh = None
self._shutdown_event = threading.Event()
self._module_cc = FakeCCSession()
@@ -649,14 +654,8 @@ class MyZonemgr(Zonemgr):
class TestZonemgr(unittest.TestCase):
def setUp(self):
- if os.path.exists(TEST_SQLITE3_DBFILE):
- os.unlink(TEST_SQLITE3_DBFILE)
self.zonemgr = MyZonemgr()
- def tearDown(self):
- if os.path.exists(TEST_SQLITE3_DBFILE):
- os.unlink(TEST_SQLITE3_DBFILE)
-
def test_config_handler(self):
config_data1 = {
"lowerbound_refresh" : 60,
@@ -676,9 +675,8 @@ class TestZonemgr(unittest.TestCase):
config_data3 = {"refresh_jitter" : 0.7}
self.zonemgr.config_handler(config_data3)
self.assertEqual(0.5, self.zonemgr._config_data.get("refresh_jitter"))
- # The zone doesn't exist in database, simply skip loading soa for it and log an warning
- self.zonemgr._zone_refresh = ZonemgrRefresh(TEST_SQLITE3_DBFILE, None,
- FakeCCSession())
+ # The zone doesn't exist in database, simply skip loading soa for it and log a warning
+ self.zonemgr._zone_refresh = ZonemgrRefresh(None, FakeCCSession())
config_data1["secondary_zones"] = [{"name": "nonexistent.example",
"class": "IN"}]
self.assertEqual(self.zonemgr.config_handler(config_data1),
@@ -689,9 +687,6 @@ class TestZonemgr(unittest.TestCase):
is None)
self.assertEqual(0.1, self.zonemgr._config_data.get("refresh_jitter"))
- def test_get_db_file(self):
- self.assertEqual(TEST_SQLITE3_DBFILE, self.zonemgr.get_db_file())
-
def test_parse_cmd_params(self):
params1 = {"zone_name" : "example.com.", "zone_class" : "CH",
"master" : "127.0.0.1"}
diff --git a/src/bin/zonemgr/zonemgr.py.in b/src/bin/zonemgr/zonemgr.py.in
index 71c7aae..2d5167b 100755
--- a/src/bin/zonemgr/zonemgr.py.in
+++ b/src/bin/zonemgr/zonemgr.py.in
@@ -1,6 +1,6 @@
#!@PYTHON@
-# Copyright (C) 2010 Internet Systems Consortium.
+# Copyright (C) 2010-2013 Internet Systems Consortium.
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
@@ -34,12 +34,14 @@ import threading
import select
import socket
import errno
-from isc.datasrc import sqlite3_ds
from optparse import OptionParser, OptionValueError
from isc.config.ccsession import *
import isc.util.process
from isc.log_messages.zonemgr_messages import *
from isc.notify import notify_out
+from isc.server_common.datasrc_clients_mgr import DataSrcClientsMgr, ConfigError
+from isc.datasrc import DataSourceClient, ZoneFinder
+from isc.dns import *
# Initialize logging for called modules.
isc.log.init("b10-zonemgr", buffer=True)
@@ -66,7 +68,9 @@ if "B10_FROM_BUILD" in os.environ:
else:
PREFIX = "@prefix@"
DATAROOTDIR = "@datarootdir@"
- SPECFILE_PATH = "@datadir@/@PACKAGE@".replace("${datarootdir}", DATAROOTDIR).replace("${prefix}", PREFIX)
+ SPECFILE_PATH = "@datadir@/@PACKAGE@".replace("${datarootdir}",
+ DATAROOTDIR).replace("${prefix}",
+ PREFIX)
AUTH_SPECFILE_PATH = SPECFILE_PATH
SPECFILE_LOCATION = SPECFILE_PATH + "/zonemgr.spec"
@@ -76,7 +80,6 @@ __version__ = "BIND10"
# define module name
XFRIN_MODULE_NAME = 'Xfrin'
-AUTH_MODULE_NAME = 'Auth'
# define command name
ZONE_REFRESH_COMMAND = 'refresh_from_zonemgr'
@@ -103,18 +106,23 @@ class ZonemgrRefresh:
can be stopped by calling shutdown() in another thread.
"""
- def __init__(self, db_file, slave_socket, module_cc_session):
- self._mccs = module_cc_session
+ def __init__(self, slave_socket, module_cc):
+ self._module_cc = module_cc
self._check_sock = slave_socket
- self._db_file = db_file
self._zonemgr_refresh_info = {}
self._lowerbound_refresh = None
self._lowerbound_retry = None
self._max_transfer_timeout = None
self._refresh_jitter = None
self._reload_jitter = None
- self.update_config_data(module_cc_session.get_full_config(),
- module_cc_session)
+ # This is essentially private, but we allow tests to customize it.
+ self._datasrc_clients_mgr = DataSrcClientsMgr()
+ # data_sources configuration should be ready with cfgmgr, so this
+ # shouldn't fail; if it ever does we simply propagate the exception
+ # to terminate the program.
+ self._module_cc.add_remote_config_by_name('data_sources',
+ self._datasrc_config_handler)
+ self.update_config_data(module_cc.get_full_config(), module_cc)
self._running = False
def _random_jitter(self, max, jitter):
@@ -133,27 +141,32 @@ class ZonemgrRefresh:
def _set_zone_timer(self, zone_name_class, max, jitter):
"""Set zone next refresh time.
jitter should not be bigger than half the original value."""
- self._set_zone_next_refresh_time(zone_name_class, self._get_current_time() + \
+ self._set_zone_next_refresh_time(zone_name_class,
+ self._get_current_time() +
self._random_jitter(max, jitter))
def _set_zone_refresh_timer(self, zone_name_class):
"""Set zone next refresh time after zone refresh success.
now + refresh - refresh_jitter <= next_refresh_time <= now + refresh
"""
- zone_refresh_time = float(self._get_zone_soa_rdata(zone_name_class).split(" ")[REFRESH_OFFSET])
+ zone_refresh_time = float(self._get_zone_soa_rdata(zone_name_class).
+ split(" ")[REFRESH_OFFSET])
zone_refresh_time = max(self._lowerbound_refresh, zone_refresh_time)
- self._set_zone_timer(zone_name_class, zone_refresh_time, self._refresh_jitter * zone_refresh_time)
+ self._set_zone_timer(zone_name_class, zone_refresh_time,
+ self._refresh_jitter * zone_refresh_time)
def _set_zone_retry_timer(self, zone_name_class):
"""Set zone next refresh time after zone refresh fail.
now + retry - retry_jitter <= next_refresh_time <= now + retry
"""
- if (self._get_zone_soa_rdata(zone_name_class) is not None):
- zone_retry_time = float(self._get_zone_soa_rdata(zone_name_class).split(" ")[RETRY_OFFSET])
+ if self._get_zone_soa_rdata(zone_name_class) is not None:
+ zone_retry_time = float(self._get_zone_soa_rdata(zone_name_class).
+ split(" ")[RETRY_OFFSET])
else:
zone_retry_time = 0.0
zone_retry_time = max(self._lowerbound_retry, zone_retry_time)
- self._set_zone_timer(zone_name_class, zone_retry_time, self._refresh_jitter * zone_retry_time)
+ self._set_zone_timer(zone_name_class, zone_retry_time,
+ self._refresh_jitter * zone_retry_time)
def _set_zone_notify_timer(self, zone_name_class):
"""Set zone next refresh time after receiving notify
@@ -167,19 +180,22 @@ class ZonemgrRefresh:
def zone_refresh_success(self, zone_name_class):
"""Update zone info after zone refresh success"""
- if (self._zone_not_exist(zone_name_class)):
- logger.error(ZONEMGR_UNKNOWN_ZONE_SUCCESS, zone_name_class[0], zone_name_class[1])
+ if self._zone_not_exist(zone_name_class):
+ logger.error(ZONEMGR_UNKNOWN_ZONE_SUCCESS, zone_name_class[0],
+ zone_name_class[1])
raise ZonemgrException("[b10-zonemgr] Zone (%s, %s) doesn't "
"belong to zonemgr" % zone_name_class)
self.zonemgr_reload_zone(zone_name_class)
self._set_zone_refresh_timer(zone_name_class)
self._set_zone_state(zone_name_class, ZONE_OK)
- self._set_zone_last_refresh_time(zone_name_class, self._get_current_time())
+ self._set_zone_last_refresh_time(zone_name_class,
+ self._get_current_time())
def zone_refresh_fail(self, zone_name_class):
"""Update zone info after zone refresh fail"""
- if (self._zone_not_exist(zone_name_class)):
- logger.error(ZONEMGR_UNKNOWN_ZONE_FAIL, zone_name_class[0], zone_name_class[1])
+ if self._zone_not_exist(zone_name_class):
+ logger.error(ZONEMGR_UNKNOWN_ZONE_FAIL, zone_name_class[0],
+ zone_name_class[1])
raise ZonemgrException("[b10-zonemgr] Zone (%s, %s) doesn't "
"belong to zonemgr" % zone_name_class)
# Is zone expired?
@@ -199,11 +215,6 @@ class ZonemgrRefresh:
zone; the Auth module should have rejected the case where it's not
even authoritative for the zone.
- Note: to be more robust and less independent from other module's
- behavior, it's probably safer to check the authority condition here,
- too. But right now it uses SQLite3 specific API (to be deprecated),
- so we rather rely on Auth.
-
Parameters:
zone_name_class (Name, RRClass): the notified zone name and class.
master (str): textual address of the NOTIFY sender.
@@ -219,35 +230,86 @@ class ZonemgrRefresh:
def zonemgr_reload_zone(self, zone_name_class):
""" Reload a zone."""
- zone_soa = sqlite3_ds.get_zone_soa(str(zone_name_class[0]), self._db_file)
- self._zonemgr_refresh_info[zone_name_class]["zone_soa_rdata"] = zone_soa[7]
+ self._zonemgr_refresh_info[zone_name_class]["zone_soa_rdata"] = \
+ self._get_zone_soa(zone_name_class)
def zonemgr_add_zone(self, zone_name_class):
""" Add a zone into zone manager."""
-
- logger.debug(DBG_ZONEMGR_BASIC, ZONEMGR_LOAD_ZONE, zone_name_class[0], zone_name_class[1])
+ logger.debug(DBG_ZONEMGR_BASIC, ZONEMGR_LOAD_ZONE, zone_name_class[0],
+ zone_name_class[1])
zone_info = {}
- zone_soa = sqlite3_ds.get_zone_soa(str(zone_name_class[0]), self._db_file)
+ zone_soa = self._get_zone_soa(zone_name_class)
if zone_soa is None:
logger.warn(ZONEMGR_NO_SOA, zone_name_class[0], zone_name_class[1])
zone_info["zone_soa_rdata"] = None
zone_reload_time = 0.0
else:
- zone_info["zone_soa_rdata"] = zone_soa[7]
- zone_reload_time = float(zone_soa[7].split(" ")[RETRY_OFFSET])
+ zone_info["zone_soa_rdata"] = zone_soa
+ zone_reload_time = float(zone_soa.split(" ")[RETRY_OFFSET])
zone_info["zone_state"] = ZONE_OK
zone_info["last_refresh_time"] = self._get_current_time()
self._zonemgr_refresh_info[zone_name_class] = zone_info
- # Imposes some random jitters to avoid many zones need to do refresh at the same time.
+ # Imposes some random jitters to avoid many zones need to do refresh
+ # at the same time.
zone_reload_time = max(self._lowerbound_retry, zone_reload_time)
- self._set_zone_timer(zone_name_class, zone_reload_time, self._reload_jitter * zone_reload_time)
+ self._set_zone_timer(zone_name_class, zone_reload_time,
+ self._reload_jitter * zone_reload_time)
+
+ def _get_zone_soa(self, zone_name_class):
+ """Retrieve the current SOA RR of the zone to be transferred."""
+
+ def get_zone_soa_rrset(datasrc_client, zone_name, zone_class):
+ """Retrieve the current SOA RR of the zone to be transferred."""
+ # get the zone finder. this must be SUCCESS (not even
+ # PARTIALMATCH) because we are specifying the zone origin name.
+ result, finder = datasrc_client.find_zone(zone_name)
+ if result != DataSourceClient.SUCCESS:
+ # The data source doesn't know the zone. In the context in
+ # which this function is called, this shouldn't happen.
+ raise ZonemgrException(
+ "unexpected result: zone %s/%s doesn't exist" %
+ (zone_name.to_text(True), str(zone_class)))
+ result, soa_rrset, _ = finder.find(zone_name, RRType.SOA)
+ if result != ZoneFinder.SUCCESS:
+ logger.warn(ZONEMGR_NO_SOA,
+ zone_name.to_text(True), str(zone_class))
+ return None
+ return soa_rrset
+
+ # Identify the data source to which the zone content is transferred,
+ # and get the current zone SOA from the data source (if available).
+ datasrc_client = None
+ clist = self._datasrc_clients_mgr.get_client_list(zone_name_class[1])
+ if clist is None:
+ return None
+ try:
+ datasrc_client = clist.find(zone_name_class[0], True, False)[0]
+ if datasrc_client is None: # can happen, so log it separately.
+ logger.error(ZONEMGR_DATASRC_UNKNOWN,
+ zone_name_class[0] + '/' + zone_name_class[1])
+ return None
+ zone_soa = get_zone_soa_rrset(datasrc_client,
+ Name(zone_name_class[0]),
+ RRClass(zone_name_class[1]))
+ if zone_soa == None:
+ return None
+ else:
+ return zone_soa.get_rdata()[0].to_text()
+ except isc.datasrc.Error as ex:
+ # rare case error. re-raise as ZonemgrException so it'll be logged
+ # in command_handler().
+ raise ZonemgrException('unexpected failure in datasrc module: ' +
+ str(ex))
def _zone_is_expired(self, zone_name_class):
"""Judge whether a zone is expired or not."""
- zone_expired_time = float(self._get_zone_soa_rdata(zone_name_class).split(" ")[EXPIRED_OFFSET])
- zone_last_refresh_time = self._get_zone_last_refresh_time(zone_name_class)
+ zone_expired_time = float(self._get_zone_soa_rdata(zone_name_class).
+ split(" ")[EXPIRED_OFFSET])
+ zone_last_refresh_time = \
+ self._get_zone_last_refresh_time(zone_name_class)
if (ZONE_EXPIRED == self._get_zone_state(zone_name_class) or
- zone_last_refresh_time + zone_expired_time <= self._get_current_time()):
+ zone_last_refresh_time + zone_expired_time <=
+ self._get_current_time()):
return True
return False
@@ -262,16 +324,19 @@ class ZonemgrRefresh:
self._zonemgr_refresh_info[zone_name_class]["last_refresh_time"] = time
def _get_zone_notifier_master(self, zone_name_class):
- if ("notify_master" in self._zonemgr_refresh_info[zone_name_class].keys()):
+ if ("notify_master" in
+ self._zonemgr_refresh_info[zone_name_class].keys()):
return self._zonemgr_refresh_info[zone_name_class]["notify_master"]
return None
def _set_zone_notifier_master(self, zone_name_class, master_addr):
- self._zonemgr_refresh_info[zone_name_class]["notify_master"] = master_addr
+ self._zonemgr_refresh_info[zone_name_class]["notify_master"] = \
+ master_addr
def _clear_zone_notifier_master(self, zone_name_class):
- if ("notify_master" in self._zonemgr_refresh_info[zone_name_class].keys()):
+ if ("notify_master" in
+ self._zonemgr_refresh_info[zone_name_class].keys()):
del self._zonemgr_refresh_info[zone_name_class]["notify_master"]
def _get_zone_state(self, zone_name_class):
@@ -295,7 +360,7 @@ class ZonemgrRefresh:
def _send_command(self, module_name, command_name, params):
"""Send command between modules."""
try:
- self._mccs.rpc_call(command_name, module_name, params=params)
+ self._module_cc.rpc_call(command_name, module_name, params=params)
except socket.error:
# FIXME: WTF? Where does socket.error come from? And how do we ever
# dare ignore such serious error? It can only be broken link to
@@ -314,7 +379,8 @@ class ZonemgrRefresh:
# If hasn't received refresh response but are within refresh
# timeout, skip the zone
if (ZONE_REFRESHING == zone_state and
- (self._get_zone_refresh_timeout(zone_name_class) > self._get_current_time())):
+ (self._get_zone_refresh_timeout(zone_name_class) >
+ self._get_current_time())):
continue
# Get the zone with minimum next_refresh_time
@@ -324,7 +390,8 @@ class ZonemgrRefresh:
zone_need_refresh = zone_name_class
# Find the zone need do refresh
- if (self._get_zone_next_refresh_time(zone_need_refresh) < self._get_current_time()):
+ if (self._get_zone_next_refresh_time(zone_need_refresh) <
+ self._get_current_time()):
break
return zone_need_refresh
@@ -332,9 +399,12 @@ class ZonemgrRefresh:
def _do_refresh(self, zone_name_class):
"""Do zone refresh."""
- logger.debug(DBG_ZONEMGR_BASIC, ZONEMGR_REFRESH_ZONE, zone_name_class[0], zone_name_class[1])
+ logger.debug(DBG_ZONEMGR_BASIC, ZONEMGR_REFRESH_ZONE,
+ zone_name_class[0], zone_name_class[1])
self._set_zone_state(zone_name_class, ZONE_REFRESHING)
- self._set_zone_refresh_timeout(zone_name_class, self._get_current_time() + self._max_transfer_timeout)
+ self._set_zone_refresh_timeout(zone_name_class,
+ self._get_current_time() +
+ self._max_transfer_timeout)
notify_master = self._get_zone_notifier_master(zone_name_class)
# If the zone has notify master, send notify command to xfrin module
if notify_master:
@@ -351,13 +421,6 @@ class ZonemgrRefresh:
}
self._send_command(XFRIN_MODULE_NAME, ZONE_REFRESH_COMMAND, param)
- def _zone_mgr_is_empty(self):
- """Does zone manager has no zone?"""
- if not len(self._zonemgr_refresh_info):
- return True
-
- return False
-
def _run_timer(self, start_event):
while self._running:
# Notify run_timer that we already started and are inside the loop.
@@ -367,24 +430,29 @@ class ZonemgrRefresh:
if start_event:
start_event.set()
start_event = None
- # If zonemgr has no zone, set timer timeout to self._lowerbound_retry.
- if self._zone_mgr_is_empty():
+ # If zonemgr has no zone, set timeout to minimum
+ if not self._zonemgr_refresh_info:
timeout = self._lowerbound_retry
else:
zone_need_refresh = self._find_need_do_refresh_zone()
- # If don't get zone with minimum next refresh time, set timer timeout to self._lowerbound_retry.
+ # If don't get zone with minimum next refresh time, set
+ # timeout to minimum
if not zone_need_refresh:
timeout = self._lowerbound_retry
else:
- timeout = self._get_zone_next_refresh_time(zone_need_refresh) - self._get_current_time()
- if (timeout < 0):
+ timeout = \
+ self._get_zone_next_refresh_time(zone_need_refresh) - \
+ self._get_current_time()
+ if timeout < 0:
self._do_refresh(zone_need_refresh)
continue
""" Wait for the socket notification for a maximum time of timeout
in seconds (as float)."""
try:
- rlist, wlist, xlist = select.select([self._check_sock, self._read_sock], [], [], timeout)
+ rlist, wlist, xlist = \
+ select.select([self._check_sock, self._read_sock],
+ [], [], timeout)
except select.error as e:
if e.args[0] == errno.EINTR:
(rlist, wlist, xlist) = ([], [], [])
@@ -403,8 +471,8 @@ class ZonemgrRefresh:
def run_timer(self, daemon=False):
"""
- Keep track of zone timers. Spawns and starts a thread. The thread object
- is returned.
+ Keep track of zone timers. Spawns and starts a thread. The thread
+ object is returned.
You can stop it by calling shutdown().
"""
@@ -429,6 +497,20 @@ class ZonemgrRefresh:
# Return the thread to anyone interested
return self._thread
+ def _datasrc_config_handler(self, new_config, config_data):
+ """Configuration handler of the 'data_sources' module.
+
+ The actual handling is delegated to the DataSrcClientsMgr class;
+ this method is a simple wrapper.
+
+ This is essentially private, but implemented as 'protected' so tests
+ can refer to it; other external use is prohibited.
+ """
+ try:
+ self._datasrc_clients_mgr.reconfigure(new_config, config_data)
+ except isc.server_common.datasrc_clients_mgr.ConfigError as ex:
+ logger.error(ZONEMGR_DATASRC_CONFIG_ERROR, ex)
+
def shutdown(self):
"""
Stop the run_timer() thread. Block until it finished. This must be
@@ -450,7 +532,7 @@ class ZonemgrRefresh:
self._read_sock = None
self._write_sock = None
- def update_config_data(self, new_config, module_cc_session):
+ def update_config_data(self, new_config, module_cc):
""" update ZonemgrRefresh config """
# Get a new value, but only if it is defined (commonly used below)
# We don't use "value or default", because if value would be
@@ -499,7 +581,7 @@ class ZonemgrRefresh:
# Currently we use an explicit get_default_value call
# in case the class hasn't been set. Alternatively, we
# could use
- # module_cc_session.get_value('secondary_zones[INDEX]/class')
+ # module_cc.get_value('secondary_zones[INDEX]/class')
# To get either the value that was set, or the default if
# it wasn't set.
# But the real solution would be to make new_config a type
@@ -509,7 +591,7 @@ class ZonemgrRefresh:
if 'class' in secondary_zone:
rr_class = secondary_zone['class']
else:
- rr_class = module_cc_session.get_default_value(
+ rr_class = module_cc.get_default_value(
'secondary_zones/class')
# Convert rr_class to and from RRClass to check its value
try:
@@ -521,10 +603,12 @@ class ZonemgrRefresh:
required[name_class] = True
# Add it only if it isn't there already
if not name_class in self._zonemgr_refresh_info:
- # If we are not able to find it in database, log an warning
+ # If we are not able to find it in database, log an
+ # warning
self.zonemgr_add_zone(name_class)
# Drop the zones that are no longer there
- # Do it in two phases, python doesn't like deleting while iterating
+ # Do it in two phases, python doesn't like deleting while
+ # iterating
to_drop = []
for old_zone in self._zonemgr_refresh_info:
if not old_zone in required:
@@ -539,10 +623,11 @@ class Zonemgr:
def __init__(self):
self._zone_refresh = None
self._setup_session()
- self._db_file = self.get_db_file()
- # Create socket pair for communicating between main thread and zonemgr timer thread
- self._master_socket, self._slave_socket = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)
- self._zone_refresh = ZonemgrRefresh(self._db_file, self._slave_socket, self._module_cc)
+ # Create socket pair for communicating between main thread and zonemgr
+ # timer thread
+ self._master_socket, self._slave_socket = \
+ socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)
+ self._zone_refresh = ZonemgrRefresh(self._slave_socket, self._module_cc)
self._zone_refresh.run_timer()
self._lock = threading.Lock()
@@ -550,9 +635,10 @@ class Zonemgr:
self.running = False
def _setup_session(self):
- """Setup two sessions for zonemgr, one(self._module_cc) is used for receiving
- commands and config data sent from other modules, another one (self._cc)
- is used to send commands to proper modules."""
+ """Setup two sessions for zonemgr, one(self._module_cc) is used for
+ receiving commands and config data sent from other modules, another
+ one (self._cc) is used to send commands to proper modules.
+ """
self._module_cc = isc.config.ModuleCCSession(SPECFILE_LOCATION,
self.config_handler,
self.command_handler)
@@ -561,18 +647,9 @@ class Zonemgr:
self._config_data_check(self._config_data)
self._module_cc.start()
- def get_db_file(self):
- db_file, is_default = self._module_cc.get_remote_config_value(AUTH_MODULE_NAME, "database_file")
- # this too should be unnecessary, but currently the
- # 'from build' override isn't stored in the config
- # (and we don't have indirect python access to datasources yet)
- if is_default and "B10_FROM_BUILD" in os.environ:
- db_file = os.environ["B10_FROM_BUILD"] + "/bind10_zones.sqlite3"
- return db_file
-
def shutdown(self):
"""Shutdown the zonemgr process. The thread which is keeping track of
- zone timers should be terminated.
+ zone timers should be terminated.
"""
self._zone_refresh.shutdown()
@@ -596,7 +673,8 @@ class Zonemgr:
self._config_data_check(complete)
if self._zone_refresh is not None:
try:
- self._zone_refresh.update_config_data(complete, self._module_cc)
+ self._zone_refresh.update_config_data(complete,
+ self._module_cc)
except Exception as e:
answer = create_answer(1, str(e))
ok = False
@@ -608,7 +686,8 @@ class Zonemgr:
def _config_data_check(self, config_data):
"""Check whether the new config data is valid or
not. It contains only basic logic, not full check against
- database."""
+ database.
+ """
# jitter should not be bigger than half of the original value
if config_data.get('refresh_jitter') > 0.5:
config_data['refresh_jitter'] = 0.5
@@ -625,7 +704,7 @@ class Zonemgr:
logger.error(ZONEMGR_NO_ZONE_CLASS)
raise ZonemgrException("zone class should be provided")
- if (command != ZONE_NOTIFY_COMMAND):
+ if command != ZONE_NOTIFY_COMMAND:
return (zone_name, zone_class)
master_str = args.get("master")
@@ -641,7 +720,8 @@ class Zonemgr:
ZONE_NOTIFY_COMMAND is issued by Auth process;
ZONE_NEW_DATA_READY_CMD and ZONE_XFRIN_FAILED are issued by
Xfrin process;
- shutdown is issued by a user or Init process. """
+ shutdown is issued by a user or Init process.
+ """
answer = create_answer(0)
if command == ZONE_NOTIFY_COMMAND:
""" Handle Auth notify command"""
diff --git a/src/bin/zonemgr/zonemgr_messages.mes b/src/bin/zonemgr/zonemgr_messages.mes
index 0334858..e749e3b 100644
--- a/src/bin/zonemgr/zonemgr_messages.mes
+++ b/src/bin/zonemgr/zonemgr_messages.mes
@@ -1,4 +1,4 @@
-# Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+# Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
@@ -19,6 +19,16 @@
An error was encountered on the command channel. The message indicates
the nature of the error.
+% ZONEMGR_DATASRC_CONFIG_ERROR failed to update data source configuration: %1
+Configuration for the global data sources is updated, but the update
+cannot be applied to zonemgr. The zonemgr module will still keep running
+with the previous configuration, but the cause of the failure and
+other log messages must be carefully examined because if only zonemgr
+rejects the new configuration then the entire BIND 10 system will have
+inconsistent state among different modules. If other modules accept
+the update but zonemgr produces this error, the zonemgr module should
+probably be restarted.
+
% ZONEMGR_JITTER_TOO_BIG refresh_jitter is too big, setting to 0.5
The value specified in the configuration for the refresh jitter is too large
so its value has been set to the maximum of 0.5.
diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am
index f636c0d..79a8263 100644
--- a/src/lib/Makefile.am
+++ b/src/lib/Makefile.am
@@ -1,3 +1,3 @@
-SUBDIRS = exceptions util log cryptolink dns cc config acl xfr bench \
+SUBDIRS = exceptions util log hooks cryptolink dns cc config acl xfr bench \
asiolink asiodns nsas cache resolve testutils datasrc \
server_common python dhcp dhcpsrv statistics
diff --git a/src/lib/dhcpsrv/dhcp_parsers.cc b/src/lib/dhcpsrv/dhcp_parsers.cc
index d21538b..4d1dc73 100644
--- a/src/lib/dhcpsrv/dhcp_parsers.cc
+++ b/src/lib/dhcpsrv/dhcp_parsers.cc
@@ -79,9 +79,9 @@ DebugParser::DebugParser(const std::string& param_name)
void
DebugParser::build(ConstElementPtr new_config) {
+ value_ = new_config;
std::cout << "Build for token: [" << param_name_ << "] = ["
<< value_->str() << "]" << std::endl;
- value_ = new_config;
}
void
diff --git a/src/lib/hooks/Makefile.am b/src/lib/hooks/Makefile.am
new file mode 100644
index 0000000..838564a
--- /dev/null
+++ b/src/lib/hooks/Makefile.am
@@ -0,0 +1,56 @@
+SUBDIRS = . tests
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+
+# Some versions of GCC warn about some versions of Boost regarding
+# missing initializer for members in its posix_time.
+# https://svn.boost.org/trac/boost/ticket/3477
+# But older GCC compilers don't have the flag.
+AM_CXXFLAGS += $(WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG)
+
+
+# Define rule to build logging source files from message file
+hooks_messages.h hooks_messages.cc: hooks_messages.mes
+ $(top_builddir)/src/lib/log/compiler/message $(top_srcdir)/src/lib/hooks/hooks_messages.mes
+
+# Tell automake that the message files are built as part of the build process
+# (so that they are built before the main library is built).
+BUILT_SOURCES = hooks_messages.h hooks_messages.cc
+
+# Ensure that the message file is included in the distribution
+EXTRA_DIST = hooks_messages.mes
+
+# Get rid of generated message files on a clean
+CLEANFILES = *.gcno *.gcda hooks_messages.h hooks_messages.cc
+
+lib_LTLIBRARIES = libb10-hooks.la
+libb10_hooks_la_SOURCES =
+libb10_hooks_la_SOURCES += callout_handle.cc callout_handle.h
+libb10_hooks_la_SOURCES += callout_manager.cc callout_manager.h
+libb10_hooks_la_SOURCES += hooks.h
+libb10_hooks_la_SOURCES += hooks_log.cc hooks_log.h
+libb10_hooks_la_SOURCES += hooks_manager.cc hooks_manager.h
+libb10_hooks_la_SOURCES += library_handle.cc library_handle.h
+libb10_hooks_la_SOURCES += library_manager.cc library_manager.h
+libb10_hooks_la_SOURCES += library_manager_collection.cc library_manager_collection.h
+libb10_hooks_la_SOURCES += pointer_converter.h
+libb10_hooks_la_SOURCES += server_hooks.cc server_hooks.h
+
+nodist_libb10_hooks_la_SOURCES = hooks_messages.cc hooks_messages.h
+
+libb10_hooks_la_CXXFLAGS = $(AM_CXXFLAGS)
+libb10_hooks_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
+libb10_hooks_la_LDFLAGS = $(AM_LDFLAGS)
+libb10_hooks_la_LIBADD =
+libb10_hooks_la_LIBADD += $(top_builddir)/src/lib/log/libb10-log.la
+libb10_hooks_la_LIBADD += $(top_builddir)/src/lib/util/libb10-util.la
+libb10_hooks_la_LIBADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
+
+
+if USE_CLANGPP
+# Disable unused parameter warning caused by some of the
+# Boost headers when compiling with clang.
+libb10_hooks_la_CXXFLAGS += -Wno-unused-parameter
+endif
diff --git a/src/lib/hooks/callout_handle.cc b/src/lib/hooks/callout_handle.cc
new file mode 100644
index 0000000..ce9ef82
--- /dev/null
+++ b/src/lib/hooks/callout_handle.cc
@@ -0,0 +1,162 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <hooks/callout_handle.h>
+#include <hooks/callout_manager.h>
+#include <hooks/library_handle.h>
+#include <hooks/server_hooks.h>
+
+#include <string>
+#include <utility>
+#include <vector>
+
+using namespace std;
+
+namespace isc {
+namespace hooks {
+
+// Constructor.
+CalloutHandle::CalloutHandle(const boost::shared_ptr<CalloutManager>& manager,
+ const boost::shared_ptr<LibraryManagerCollection>& lmcoll)
+ : lm_collection_(lmcoll), arguments_(), context_collection_(),
+ manager_(manager), skip_(false) {
+
+ // Call the "context_create" hook. We should be OK doing this - although
+ // the constructor has not finished running, all the member variables
+ // have been created.
+ manager_->callCallouts(ServerHooks::CONTEXT_CREATE, *this);
+}
+
+// Destructor
+CalloutHandle::~CalloutHandle() {
+
+ // Call the "context_destroy" hook. We should be OK doing this - although
+ // the destructor is being called, all the member variables are still in
+ // existence.
+ manager_->callCallouts(ServerHooks::CONTEXT_DESTROY, *this);
+
+ // Explicitly clear the argument and context objects. This should free up
+ // all memory that could have been allocated by libraries that were loaded.
+ arguments_.clear();
+ context_collection_.clear();
+
+ // Normal destruction of the remaining variables will include the
+ // destruction of lm_collection_, an action that decrements the reference
+ // count on the library manager collection (which holds the libraries that
+ // could have allocated memory in the argument and context members.) When
+ // that goes to zero, the libraries will be unloaded: at that point nothing
+ // in the hooks framework will be pointing to memory in the libraries'
+ // address space.
+ //
+ // It is possible that some other data structure in the server (the program
+ // using the hooks library) still references the address space and attempts
+ // to access it causing a segmentation fault. That issue is outside the
+ // scope of this framework and is not addressed by it.
+}
+
+// Return the name of all argument items.
+
+vector<string>
+CalloutHandle::getArgumentNames() const {
+
+ vector<string> names;
+ for (ElementCollection::const_iterator i = arguments_.begin();
+ i != arguments_.end(); ++i) {
+ names.push_back(i->first);
+ }
+
+ return (names);
+}
+
+// Return the library handle allowing the callout to access the CalloutManager
+// registration/deregistration functions.
+
+LibraryHandle&
+CalloutHandle::getLibraryHandle() const {
+ return (manager_->getLibraryHandle());
+}
+
+// Return the context for the currently pointed-to library. This version is
+// used by the "setContext()" method and creates a context for the current
+// library if it does not exist.
+
+CalloutHandle::ElementCollection&
+CalloutHandle::getContextForLibrary() {
+ int libindex = manager_->getLibraryIndex();
+
+ // Access a reference to the element collection for the given index,
+ // creating a new element collection if necessary, and return it.
+ return (context_collection_[libindex]);
+}
+
+// The "const" version of the above, used by the "getContext()" method. If
+// the context for the current library doesn't exist, throw an exception.
+
+const CalloutHandle::ElementCollection&
+CalloutHandle::getContextForLibrary() const {
+ int libindex = manager_->getLibraryIndex();
+
+ ContextCollection::const_iterator libcontext =
+ context_collection_.find(libindex);
+ if (libcontext == context_collection_.end()) {
+ isc_throw(NoSuchCalloutContext, "unable to find callout context "
+ "associated with the current library index (" << libindex <<
+ ")");
+ }
+
+ // Return a reference to the context's element collection.
+ return (libcontext->second);
+}
+
+// Return the name of all items in the context associated with the current]
+// library.
+
+vector<string>
+CalloutHandle::getContextNames() const {
+
+ vector<string> names;
+
+ const ElementCollection& elements = getContextForLibrary();
+ for (ElementCollection::const_iterator i = elements.begin();
+ i != elements.end(); ++i) {
+ names.push_back(i->first);
+ }
+
+ return (names);
+}
+
+// Return name of current hook (the hook to which the current callout is
+// attached) or the empty string if not called within the context of a
+// callout.
+
+string
+CalloutHandle::getHookName() const {
+ // Get the current hook index.
+ int index = manager_->getHookIndex();
+
+ // ... and look up the hook.
+ string hook = "";
+ try {
+ hook = ServerHooks::getServerHooks().getName(index);
+ } catch (const NoSuchHook&) {
+ // Hook index is invalid, so this methods probably called from outside
+ // a callout being executed via a call to CalloutManager::callCallouts.
+ // In this case, the empty string is returned.
+ }
+
+ return (hook);
+}
+
+} // namespace util
+} // namespace isc
diff --git a/src/lib/hooks/callout_handle.h b/src/lib/hooks/callout_handle.h
new file mode 100644
index 0000000..eb57fd4
--- /dev/null
+++ b/src/lib/hooks/callout_handle.h
@@ -0,0 +1,383 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef CALLOUT_HANDLE_H
+#define CALLOUT_HANDLE_H
+
+#include <exceptions/exceptions.h>
+#include <hooks/library_handle.h>
+
+#include <boost/any.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include <map>
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace hooks {
+
+/// @brief No such argument
+///
+/// Thrown if an attempt is made access an argument that does not exist.
+
+class NoSuchArgument : public Exception {
+public:
+ NoSuchArgument(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// @brief No such callout context item
+///
+/// Thrown if an attempt is made to get an item of data from this callout's
+/// context and either the context or an item in the context with that name
+/// does not exist.
+
+class NoSuchCalloutContext : public Exception {
+public:
+ NoSuchCalloutContext(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+// Forward declaration of the library handle and related collection classes.
+
+class CalloutManager;
+class LibraryHandle;
+class LibraryManagerCollection;
+
+/// @brief Per-packet callout handle
+///
+/// An object of this class is associated with every packet (or request)
+/// processed by the server. It forms the principle means of passing data
+/// between the server and the user-library callouts.
+///
+/// The class allows access to the following information:
+///
+/// - Arguments. When the callouts associated with a hook are called, they
+/// are passed information by the server (and can return information to it)
+/// through name/value pairs. Each of these pairs is an argument and the
+/// information is accessed through the {get,set}Argument() methods.
+///
+/// - Per-packet context. Each packet has a context associated with it, this
+/// context being on a per-library basis. In other words, As a packet passes
+/// through the callouts associated with a given library, the callouts can
+/// associate and retrieve information with the packet. The per-library
+/// nature of the context means that the callouts within a given library can
+/// pass packet-specific information between one another, but they cannot pass
+/// information to callous within another library. Typically such context
+/// is created in the "context_create" callout and destroyed in the
+/// "context_destroy" callout. The information is accessed through the
+/// {get,set}Context() methods.
+///
+/// - Per-library handle (LibraryHandle). The library handle allows the
+/// callout to dynamically register and deregister callouts. In the latter
+/// case, only functions registered by functions in the same library as the
+/// callout doing the deregistration can be removed: callouts registered by
+/// other libraries cannot be modified.
+
+class CalloutHandle {
+public:
+
+ /// Typedef to allow abbreviation of iterator specification in methods.
+ /// The std::string is the argument name and the "boost::any" is the
+ /// corresponding value associated with it.
+ typedef std::map<std::string, boost::any> ElementCollection;
+
+ /// Typedef to allow abbreviations in specifications when accessing
+ /// context. The ElementCollection is the name/value collection for
+ /// a particular context. The "int" corresponds to the index of an
+ /// associated library - there is a 1:1 correspondence between libraries
+ /// and a name.value collection.
+ ///
+ /// The collection of contexts is stored in a map, as not every library
+ /// will require creation of a context associated with each packet. In
+ /// addition, the structure is more flexible in that the size does not
+ /// need to be set when the CalloutHandle is constructed.
+ typedef std::map<int, ElementCollection> ContextCollection;
+
+ /// @brief Constructor
+ ///
+ /// Creates the object and calls the callouts on the "context_create"
+ /// hook.
+ ///
+ /// Of the two arguments passed, only the pointer to the callout manager is
+ /// actively used. The second argument, the pointer to the library manager
+ /// collection, is used for lifetime control: after use, the callout handle
+ /// may contain pointers to memory allocated by the loaded libraries. The
+ /// used of a shared pointer to the collection of library managers means
+ /// that the libraries that could have allocated memory in a callout handle
+ /// will not be unloaded until all such handles have been destroyed. This
+ /// issue is discussed in more detail in the documentation for
+ /// isc::hooks::LibraryManager.
+ ///
+ /// @param manager Pointer to the callout manager object.
+ /// @param lmcoll Pointer to the library manager collection. This has a
+ /// null default for testing purposes.
+ CalloutHandle(const boost::shared_ptr<CalloutManager>& manager,
+ const boost::shared_ptr<LibraryManagerCollection>& lmcoll =
+ boost::shared_ptr<LibraryManagerCollection>());
+
+ /// @brief Destructor
+ ///
+ /// Calls the context_destroy callback to release any per-packet context.
+ /// It also clears stored data to avoid problems during member destruction.
+ ~CalloutHandle();
+
+ /// @brief Set argument
+ ///
+ /// Sets the value of an argument. The argument is created if it does not
+ /// already exist.
+ ///
+ /// @param name Name of the argument.
+ /// @param value Value to set. That can be of any data type.
+ template <typename T>
+ void setArgument(const std::string& name, T value) {
+ arguments_[name] = value;
+ }
+
+ /// @brief Get argument
+ ///
+ /// Gets the value of an argument.
+ ///
+ /// @param name Name of the element in the argument list to get.
+ /// @param value [out] Value to set. The type of "value" is important:
+ /// it must match the type of the value set.
+ ///
+ /// @throw NoSuchArgument No argument with the given name is present.
+ /// @throw boost::bad_any_cast An argument with the given name is present,
+ /// but the data type of the value is not the same as the type of
+ /// the variable provided to receive the value.
+ template <typename T>
+ void getArgument(const std::string& name, T& value) const {
+ ElementCollection::const_iterator element_ptr = arguments_.find(name);
+ if (element_ptr == arguments_.end()) {
+ isc_throw(NoSuchArgument, "unable to find argument with name " <<
+ name);
+ }
+
+ value = boost::any_cast<T>(element_ptr->second);
+ }
+
+ /// @brief Get argument names
+ ///
+ /// Returns a vector holding the names of arguments in the argument
+ /// vector.
+ ///
+ /// @return Vector of strings reflecting argument names.
+ std::vector<std::string> getArgumentNames() const;
+
+ /// @brief Delete argument
+ ///
+ /// Deletes an argument of the given name. If an argument of that name
+ /// does not exist, the method is a no-op.
+ ///
+ /// N.B. If the element is a raw pointer, the pointed-to data is NOT deleted
+ /// by this method.
+ ///
+ /// @param name Name of the element in the argument list to set.
+ void deleteArgument(const std::string& name) {
+ static_cast<void>(arguments_.erase(name));
+ }
+
+ /// @brief Delete all arguments
+ ///
+ /// Deletes all arguments associated with this context.
+ ///
+ /// N.B. If any elements are raw pointers, the pointed-to data is NOT
+ /// deleted by this method.
+ void deleteAllArguments() {
+ arguments_.clear();
+ }
+
+ /// @brief Set skip flag
+ ///
+ /// Sets the "skip" variable in the callout handle. This variable is
+ /// interrogated by the server to see if the remaining callouts associated
+ /// with the current hook should be bypassed.
+ ///
+ /// @param skip New value of the "skip" flag.
+ void setSkip(bool skip) {
+ skip_ = skip;
+ }
+
+ /// @brief Get skip flag
+ ///
+ /// Gets the current value of the "skip" flag.
+ ///
+ /// @return Current value of the skip flag.
+ bool getSkip() const {
+ return (skip_);
+ }
+
+ /// @brief Access current library handle
+ ///
+ /// Returns a reference to the current library handle. This function is
+ /// only available when called by a callout (which in turn is called
+ /// through the "callCallouts" method), as it is only then that the current
+ /// library index is valid. A callout uses the library handle to
+ /// dynamically register or deregister callouts.
+ ///
+ /// @return Reference to the library handle.
+ ///
+ /// @throw InvalidIndex thrown if this method is called when the current
+ /// library index is invalid (typically if it is called outside of
+ /// the active callout).
+ LibraryHandle& getLibraryHandle() const;
+
+ /// @brief Set context
+ ///
+ /// Sets an element in the context associated with the current library. If
+ /// an element of the name is already present, it is replaced.
+ ///
+ /// @param name Name of the element in the context to set.
+ /// @param value Value to set.
+ template <typename T>
+ void setContext(const std::string& name, T value) {
+ getContextForLibrary()[name] = value;
+ }
+
+ /// @brief Get context
+ ///
+ /// Gets an element from the context associated with the current library.
+ ///
+ /// @param name Name of the element in the context to get.
+ /// @param value [out] Value to set. The type of "value" is important:
+ /// it must match the type of the value set.
+ ///
+ /// @throw NoSuchCalloutContext Thrown if no context element with the name
+ /// "name" is present.
+ /// @throw boost::bad_any_cast Thrown if the context element is present
+ /// but the type of the data is not the same as the type of the
+ /// variable provided to receive its value.
+ template <typename T>
+ void getContext(const std::string& name, T& value) const {
+ const ElementCollection& lib_context = getContextForLibrary();
+
+ ElementCollection::const_iterator element_ptr = lib_context.find(name);
+ if (element_ptr == lib_context.end()) {
+ isc_throw(NoSuchCalloutContext, "unable to find callout context "
+ "item " << name << " in the context associated with "
+ "current library");
+ }
+
+ value = boost::any_cast<T>(element_ptr->second);
+ }
+
+ /// @brief Get context names
+ ///
+ /// Returns a vector holding the names of items in the context associated
+ /// with the current library.
+ ///
+ /// @return Vector of strings reflecting the names of items in the callout
+ /// context associated with the current library.
+ std::vector<std::string> getContextNames() const;
+
+ /// @brief Delete context element
+ ///
+ /// Deletes an item of the given name from the context associated with the
+ /// current library. If an item of that name does not exist, the method is
+ /// a no-op.
+ ///
+ /// N.B. If the element is a raw pointer, the pointed-to data is NOT deleted
+ /// by this.
+ ///
+ /// @param name Name of the context item to delete.
+ void deleteContext(const std::string& name) {
+ static_cast<void>(getContextForLibrary().erase(name));
+ }
+
+ /// @brief Delete all context items
+ ///
+ /// Deletes all items from the context associated with the current library.
+ ///
+ /// N.B. If any elements are raw pointers, the pointed-to data is NOT
+ /// deleted by this.
+ void deleteAllContext() {
+ getContextForLibrary().clear();
+ }
+
+ /// @brief Get hook name
+ ///
+ /// Get the name of the hook to which the current callout is attached.
+ /// This can be the null string if the CalloutHandle is being accessed
+ /// outside of the CalloutManager's "callCallouts" method.
+ ///
+ /// @return Name of the current hook or the empty string if none.
+ std::string getHookName() const;
+
+private:
+ /// @brief Check index
+ ///
+ /// Gets the current library index, throwing an exception if it is not set
+ /// or is invalid for the current library collection.
+ ///
+ /// @return Current library index, valid for this library collection.
+ ///
+ /// @throw InvalidIndex current library index is not valid for the library
+ /// handle collection.
+ int getLibraryIndex() const;
+
+ /// @brief Return reference to context for current library
+ ///
+ /// Called by all context-setting functions, this returns a reference to
+ /// the callout context for the current library, creating a context if it
+ /// does not exist.
+ ///
+ /// @return Reference to the collection of name/value pairs associated
+ /// with the current library.
+ ///
+ /// @throw InvalidIndex current library index is not valid for the library
+ /// handle collection.
+ ElementCollection& getContextForLibrary();
+
+ /// @brief Return reference to context for current library (const version)
+ ///
+ /// Called by all context-accessing functions, this a reference to the
+ /// callout context for the current library. An exception is thrown if
+ /// it does not exist.
+ ///
+ /// @return Reference to the collection of name/value pairs associated
+ /// with the current library.
+ ///
+ /// @throw NoSuchCalloutContext Thrown if there is no ElementCollection
+ /// associated with the current library.
+ const ElementCollection& getContextForLibrary() const;
+
+ // Member variables
+
+ /// Pointer to the collection of libraries for which this handle has been
+ /// created.
+ boost::shared_ptr<LibraryManagerCollection> lm_collection_;
+
+ /// Collection of arguments passed to the callouts
+ ElementCollection arguments_;
+
+ /// Context collection - there is one entry per library context.
+ ContextCollection context_collection_;
+
+ /// Callout manager.
+ boost::shared_ptr<CalloutManager> manager_;
+
+ /// "Skip" flag, indicating if the caller should bypass remaining callouts.
+ bool skip_;
+};
+
+/// A shared pointer to a CalloutHandle object.
+typedef boost::shared_ptr<CalloutHandle> CalloutHandlePtr;
+
+} // namespace hooks
+} // namespace isc
+
+
+#endif // CALLOUT_HANDLE_H
diff --git a/src/lib/hooks/callout_manager.cc b/src/lib/hooks/callout_manager.cc
new file mode 100644
index 0000000..4ba3640
--- /dev/null
+++ b/src/lib/hooks/callout_manager.cc
@@ -0,0 +1,256 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <hooks/callout_handle.h>
+#include <hooks/callout_manager.h>
+#include <hooks/hooks_log.h>
+#include <hooks/pointer_converter.h>
+
+#include <boost/static_assert.hpp>
+
+#include <algorithm>
+#include <climits>
+#include <functional>
+#include <utility>
+
+using namespace std;
+
+namespace isc {
+namespace hooks {
+
+// Check that the index of a library is valid. It can range from 1 - n
+// (n is the number of libraries), 0 (pre-user library callouts), or INT_MAX
+// (post-user library callouts). It can also be -1 to indicate an invalid
+// value.
+
+void
+CalloutManager::checkLibraryIndex(int library_index) const {
+ if (((library_index >= -1) && (library_index <= num_libraries_)) ||
+ (library_index == INT_MAX)) {
+ return;
+ }
+
+ isc_throw(NoSuchLibrary, "library index " << library_index <<
+ " is not valid for the number of loaded libraries (" <<
+ num_libraries_ << ")");
+}
+
+// Set the number of libraries handled by the CalloutManager.
+
+void
+CalloutManager::setNumLibraries(int num_libraries) {
+ if (num_libraries < 0) {
+ isc_throw(isc::BadValue, "number of libraries passed to the "
+ "CalloutManager must be >= 0");
+ }
+
+ num_libraries_ = num_libraries;
+}
+
+// Register a callout for the current library.
+
+void
+CalloutManager::registerCallout(const std::string& name, CalloutPtr callout) {
+ // Note the registration.
+ LOG_DEBUG(hooks_logger, HOOKS_DBG_CALLS, HOOKS_CALLOUT_REGISTRATION)
+ .arg(current_library_).arg(name);
+
+ // Sanity check that the current library index is set to a valid value.
+ checkLibraryIndex(current_library_);
+
+ // Get the index associated with this hook (validating the name in the
+ // process).
+ int hook_index = ServerHooks::getServerHooks().getIndex(name);
+
+ // Iterate through the callout vector for the hook from start to end,
+ // looking for the first entry where the library index is greater than
+ // the present index.
+ for (CalloutVector::iterator i = hook_vector_[hook_index].begin();
+ i != hook_vector_[hook_index].end(); ++i) {
+ if (i->first > current_library_) {
+ // Found an element whose library index number is greater than the
+ // current index, so insert the new element ahead of this one.
+ hook_vector_[hook_index].insert(i, make_pair(current_library_,
+ callout));
+ return;
+ }
+ }
+
+ // Reached the end of the vector, so there is no element in the (possibly
+ // empty) set of callouts with a library index greater than the current
+ // library index. Inset the callout at the end of the list.
+ hook_vector_[hook_index].push_back(make_pair(current_library_, callout));
+}
+
+// Check if callouts are present for a given hook index.
+
+bool
+CalloutManager::calloutsPresent(int hook_index) const {
+ // Validate the hook index.
+ if ((hook_index < 0) || (hook_index >= hook_vector_.size())) {
+ isc_throw(NoSuchHook, "hook index " << hook_index <<
+ " is not valid for the list of registered hooks");
+ }
+
+ // Valid, so are there any callouts associated with that hook?
+ return (!hook_vector_[hook_index].empty());
+}
+
+// Call all the callouts for a given hook.
+
+void
+CalloutManager::callCallouts(int hook_index, CalloutHandle& callout_handle) {
+
+ // Only initialize and iterate if there are callouts present. This check
+ // also catches the case of an invalid index.
+ if (calloutsPresent(hook_index)) {
+
+ // Clear the "skip" flag so we don't carry state from a previous call.
+ callout_handle.setSkip(false);
+
+ // Set the current hook index. This is used should a callout wish to
+ // determine to what hook it is attached.
+ current_hook_ = hook_index;
+
+ // Duplicate the callout vector for this hook and work through that.
+ // This step is needed because we allow dynamic registration and
+ // deregistration of callouts. If a callout attached to a hook modified
+ // the list of callouts on that hook, the underlying CalloutVector would
+ // change and potentially affect the iteration through that vector.
+ CalloutVector callouts(hook_vector_[hook_index]);
+
+ // Call all the callouts.
+ for (CalloutVector::const_iterator i = callouts.begin();
+ i != callouts.end(); ++i) {
+ // In case the callout tries to register or deregister a callout,
+ // set the current library index to the index associated with the
+ // library that registered the callout being called.
+ current_library_ = i->first;
+
+ // Call the callout
+ try {
+ int status = (*i->second)(callout_handle);
+ if (status == 0) {
+ LOG_DEBUG(hooks_logger, HOOKS_DBG_EXTENDED_CALLS,
+ HOOKS_CALLOUT_CALLED).arg(current_library_)
+ .arg(ServerHooks::getServerHooks()
+ .getName(current_hook_))
+ .arg(PointerConverter(i->second).dlsymPtr());
+ } else {
+ LOG_ERROR(hooks_logger, HOOKS_CALLOUT_ERROR)
+ .arg(current_library_)
+ .arg(ServerHooks::getServerHooks()
+ .getName(current_hook_))
+ .arg(PointerConverter(i->second).dlsymPtr());
+ }
+ } catch (...) {
+ // Any exception, not just ones based on isc::Exception
+ LOG_ERROR(hooks_logger, HOOKS_CALLOUT_EXCEPTION)
+ .arg(current_library_)
+ .arg(ServerHooks::getServerHooks().getName(current_hook_))
+ .arg(PointerConverter(i->second).dlsymPtr());
+ }
+
+ }
+
+ // Reset the current hook and library indexs to an invalid value to
+ // catch any programming errors.
+ current_hook_ = -1;
+ current_library_ = -1;
+ }
+}
+
+// Deregister a callout registered by the current library on a particular hook.
+
+bool
+CalloutManager::deregisterCallout(const std::string& name, CalloutPtr callout) {
+ // Sanity check that the current library index is set to a valid value.
+ checkLibraryIndex(current_library_);
+
+ // Get the index associated with this hook (validating the name in the
+ // process).
+ int hook_index = ServerHooks::getServerHooks().getIndex(name);
+
+ /// Construct a CalloutEntry matching the current library and the callout
+ /// we want to remove.
+ CalloutEntry target(current_library_, callout);
+
+ /// To decide if any entries were removed, we'll record the initial size
+ /// of the callout vector for the hook, and compare it with the size after
+ /// the removal.
+ size_t initial_size = hook_vector_[hook_index].size();
+
+ // The next bit is standard STL (see "Item 33" in "Effective STL" by
+ // Scott Meyers).
+ //
+ // remove_if reorders the hook vector so that all items not matching
+ // the predicate are at the start of the vector and returns a pointer
+ // to the next element. (In this case, the predicate is that the item
+ // is equal to the value of the passed callout.) The erase() call
+ // removes everything from that element to the end of the vector, i.e.
+ // all the matching elements.
+ hook_vector_[hook_index].erase(remove_if(hook_vector_[hook_index].begin(),
+ hook_vector_[hook_index].end(),
+ bind1st(equal_to<CalloutEntry>(),
+ target)),
+ hook_vector_[hook_index].end());
+
+ // Return an indication of whether anything was removed.
+ bool removed = initial_size != hook_vector_[hook_index].size();
+ if (removed) {
+ LOG_DEBUG(hooks_logger, HOOKS_DBG_EXTENDED_CALLS,
+ HOOKS_CALLOUT_DEREGISTERED).arg(current_library_).arg(name);
+ }
+
+ return (removed);
+}
+
+// Deregister all callouts on a given hook.
+
+bool
+CalloutManager::deregisterAllCallouts(const std::string& name) {
+
+ // Get the index associated with this hook (validating the name in the
+ // process).
+ int hook_index = ServerHooks::getServerHooks().getIndex(name);
+
+ /// Construct a CalloutEntry matching the current library (the callout
+ /// pointer is NULL as we are not checking that).
+ CalloutEntry target(current_library_, NULL);
+
+ /// To decide if any entries were removed, we'll record the initial size
+ /// of the callout vector for the hook, and compare it with the size after
+ /// the removal.
+ size_t initial_size = hook_vector_[hook_index].size();
+
+ // Remove all callouts matching this library.
+ hook_vector_[hook_index].erase(remove_if(hook_vector_[hook_index].begin(),
+ hook_vector_[hook_index].end(),
+ bind1st(CalloutLibraryEqual(),
+ target)),
+ hook_vector_[hook_index].end());
+
+ // Return an indication of whether anything was removed.
+ bool removed = initial_size != hook_vector_[hook_index].size();
+ if (removed) {
+ LOG_DEBUG(hooks_logger, HOOKS_DBG_EXTENDED_CALLS,
+ HOOKS_ALL_CALLOUTS_DEREGISTERED).arg(current_library_)
+ .arg(name);
+ }
+
+ return (removed);
+}
+
+} // namespace util
+} // namespace isc
diff --git a/src/lib/hooks/callout_manager.h b/src/lib/hooks/callout_manager.h
new file mode 100644
index 0000000..4619006
--- /dev/null
+++ b/src/lib/hooks/callout_manager.h
@@ -0,0 +1,404 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef CALLOUT_MANAGER_H
+#define CALLOUT_MANAGER_H
+
+#include <exceptions/exceptions.h>
+#include <hooks/library_handle.h>
+#include <hooks/server_hooks.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <climits>
+#include <map>
+#include <string>
+
+namespace isc {
+namespace hooks {
+
+/// @brief No such library
+///
+/// Thrown if an attempt is made to set the current library index to a value
+/// that is invalid for the number of loaded libraries.
+class NoSuchLibrary : public Exception {
+public:
+ NoSuchLibrary(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// @brief Callout Manager
+///
+/// This class manages the registration, deregistration and execution of the
+/// library callouts. It is part of the hooks framework used by the BIND 10
+/// server, and is not for use by user-written code in a hooks library.
+///
+/// In operation, the class needs to know two items of data:
+///
+/// - The list of server hooks, which is used in two ways. Firstly, when a
+/// callout registers or deregisters a hook, it does so by name: the
+/// @ref isc::util::ServerHooks object supplies the names of registered
+/// hooks. Secondly, when the callouts associated with a hook are called by
+/// the server, the server supplies the index of the relevant hook: this is
+/// validated by reference to the list of hook.
+///
+/// - The number of loaded libraries. Each callout registered by a user
+/// library is associated with that library, the callout manager storing both
+/// a pointer to the callout and the index of the library in the list of
+/// loaded libraries. Callouts are allowed to dynamically register and
+/// deregister callouts in the same library (including themselves): they
+/// cannot affect callouts registered by another library. When calling a
+/// callout, the callout manager maintains the idea of a "current library
+/// index": if the callout calls one of the callout registration functions
+/// (indirectly via the @ref LibraryHandle object), the registration
+/// functions use the "current library index" in their processing.
+///
+/// These two items of data are supplied when an object of this class is
+/// constructed. The latter (number of libraries) can be updated after the
+/// class is constructed. (Such an update is used during library loading where
+/// the CalloutManager has to be constructed before the libraries are loaded,
+/// but one of the libraries subsequently fails to load.)
+///
+/// The library index is important because it determines in what order callouts
+/// on a particular hook are called. For each hook, the CalloutManager
+/// maintains a vector of callouts ordered by library index. When a callout
+/// is added to the list, it is added at the end of the callouts associated
+/// with the current library. To clarify this further, suppose that three
+/// libraries are loaded, A (assigned an index 1), B (assigned an index 2) and
+/// C (assigned an index 3). Suppose A registers two callouts on a given hook,
+/// A1 and A2 (in that order) B registers B1 and B2 (in that order) and C
+/// registers C1 and C2 (in that order). Internally, the callouts are stored
+/// in the order A1, A2, B1, B2, C1, and C2: this is also the order in which
+/// the are called. If B now registers another callout (B3), it is added to
+/// the vector after the list of callouts associated with B: the new order is
+/// therefore A1, A2, B1, B2, B3, C1 and C2.
+///
+/// Indexes range between 1 and n (where n is the number of the libraries
+/// loaded) and are assigned to libraries based on the order the libraries
+/// presented to the hooks framework for loading (something that occurs in the
+/// isc::util::HooksManager) class. However, two other indexes are recognised,
+/// 0 and INT_MAX. These are used when the server itself registers callouts -
+/// the server is able to register callouts that get called before any
+/// user-library callouts, and ones that get called after user-library callouts.
+/// In other words, assuming the callouts on a hook are A1, A2, B1, B2, B3, C2,
+/// C2 as before, and that the server registers S1 (to run before the
+/// user-registered callouts) and S2 (to run after them), the callouts are
+/// stored (and executed) in the order S1, A1, A2, B1, B2, B3, C2, C2, S2. In
+/// summary, the recognised index values are:
+///
+/// - < 0: invalid.
+/// - 0: used for server-registered callouts that are called before
+/// user-registered callouts.
+/// - 1 - n: callouts from user libraries.
+/// - INT_MAX: used for server-registered callouts called after
+/// user-registered callouts.
+///
+/// Note that the callout functions do not access the CalloutManager: instead,
+/// they use a LibraryHandle object. This contains an internal pointer to
+/// the CalloutManager, but provides a restricted interface. In that way,
+/// callouts are unable to affect callouts supplied by other libraries.
+
+class CalloutManager {
+private:
+
+ // Private typedefs
+
+ /// Element in the vector of callouts. The elements in the pair are the
+ /// index of the library from which this callout was registered, and a#
+ /// pointer to the callout itself.
+ typedef std::pair<int, CalloutPtr> CalloutEntry;
+
+ /// An element in the hook vector. Each element is a vector of callouts
+ /// associated with a given hook.
+ typedef std::vector<CalloutEntry> CalloutVector;
+
+public:
+
+ /// @brief Constructor
+ ///
+ /// Initializes member variables, in particular sizing the hook vector
+ /// (the vector of callout vectors) to the appropriate size.
+ ///
+ /// @param num_libraries Number of loaded libraries.
+ ///
+ /// @throw isc::BadValue if the number of libraries is less than or equal
+ /// to 0, or if the pointer to the server hooks object is empty.
+ CalloutManager(int num_libraries = 0)
+ : current_hook_(-1), current_library_(-1),
+ hook_vector_(ServerHooks::getServerHooks().getCount()),
+ library_handle_(this), pre_library_handle_(this, 0),
+ post_library_handle_(this, INT_MAX), num_libraries_(num_libraries)
+ {
+ // Check that the number of libraries is OK. (This does a redundant
+ // set of the number of libraries, but it's only a single assignment
+ // and avoids the need for a separate "check" method.
+ setNumLibraries(num_libraries);
+ }
+
+ /// @brief Register a callout on a hook for the current library
+ ///
+ /// Registers a callout function for the current library with a given hook
+ /// (the index of the "current library" being given by the current_library_
+ /// member). The callout is added to the end of the callouts for this
+ /// library that are associated with that hook.
+ ///
+ /// @param name Name of the hook to which the callout is added.
+ /// @param callout Pointer to the callout function to be registered.
+ ///
+ /// @throw NoSuchHook The hook name is unrecognised.
+ /// @throw Unexpected The hook name is valid but an internal data structure
+ /// is of the wrong size.
+ void registerCallout(const std::string& name, CalloutPtr callout);
+
+ /// @brief De-Register a callout on a hook for the current library
+ ///
+ /// Searches through the functions registered by the the current library
+ /// (the index of the "current library" being given by the current_library_
+ /// member) with the named hook and removes all entries matching the
+ /// callout.
+ ///
+ /// @param name Name of the hook from which the callout is removed.
+ /// @param callout Pointer to the callout function to be removed.
+ ///
+ /// @return true if a one or more callouts were deregistered.
+ ///
+ /// @throw NoSuchHook The hook name is unrecognised.
+ /// @throw Unexpected The hook name is valid but an internal data structure
+ /// is of the wrong size.
+ bool deregisterCallout(const std::string& name, CalloutPtr callout);
+
+ /// @brief Removes all callouts on a hook for the current library
+ ///
+ /// Removes all callouts associated with a given hook that were registered
+ /// by the current library (the index of the "current library" being given
+ /// by the current_library_ member).
+ ///
+ /// @param name Name of the hook from which the callouts are removed.
+ ///
+ /// @return true if one or more callouts were deregistered.
+ ///
+ /// @throw NoSuchHook Thrown if the hook name is unrecognised.
+ bool deregisterAllCallouts(const std::string& name);
+
+ /// @brief Checks if callouts are present on a hook
+ ///
+ /// Checks all loaded libraries and returns true if at least one callout
+ /// has been registered by any of them for the given hook.
+ ///
+ /// @param hook_index Hook index for which callouts are checked.
+ ///
+ /// @return true if callouts are present, false if not.
+ ///
+ /// @throw NoSuchHook Given index does not correspond to a valid hook.
+ bool calloutsPresent(int hook_index) const;
+
+ /// @brief Calls the callouts for a given hook
+ ///
+ /// Iterates through the libray handles and calls the callouts associated
+ /// with the given hook index.
+ ///
+ /// @note This method invalidates the current library index set with
+ /// setLibraryIndex().
+ ///
+ /// @param hook_index Index of the hook to call.
+ /// @param callout_handle Reference to the CalloutHandle object for the
+ /// current object being processed.
+ void callCallouts(int hook_index, CalloutHandle& callout_handle);
+
+ /// @brief Get current hook index
+ ///
+ /// Made available during callCallouts, this is the index of the hook
+ /// on which callouts are being called.
+ int getHookIndex() const {
+ return (current_hook_);
+ }
+
+ /// @brief Set number of libraries
+ ///
+ /// Sets the number of libraries. Although the value is passed to the
+ /// constructor, in some cases that is only an estimate and the number
+ /// can only be determined after the CalloutManager is created.
+ ///
+ /// @note If the number if libraries is reset, it must be done *before*
+ /// any callouts are registered.
+ ///
+ /// @param num_libraries Number of libraries served by this CalloutManager.
+ ///
+ /// @throw BadValue Number of libraries must be >= 0.
+ /// @throw LibraryCountChanged Number of libraries has been changed after
+ /// callouts have been registered.
+ void setNumLibraries(int num_libraries);
+
+ /// @brief Get number of libraries
+ ///
+ /// Returns the number of libraries that this CalloutManager is expected
+ /// to serve. This is the number passed to its constructor.
+ ///
+ /// @return Number of libraries served by this CalloutManager.
+ int getNumLibraries() const {
+ return (num_libraries_);
+ }
+
+ /// @brief Get current library index
+ ///
+ /// Returns the index of the "current" library. This the index associated
+ /// with the currently executing callout when callCallouts is executing.
+ /// When callCallouts() is not executing (as is the case when the load()
+ /// function in a user-library is called during the library load process),
+ /// the index can be set by setLibraryIndex().
+ ///
+ /// @note The value set by this method is lost after a call to
+ /// callCallouts.
+ ///
+ /// @return Current library index.
+ int getLibraryIndex() const {
+ return (current_library_);
+ }
+
+ /// @brief Set current library index
+ ///
+ /// Sets the current library index. This has the following valid values:
+ ///
+ /// - -1: invalidate current index.
+ /// - 0: pre-user library callout.
+ /// - 1 - numlib: user-library callout (where "numlib" is the number of
+ /// libraries loaded in the system, this figure being passed to this
+ /// object at construction time).
+ /// - INT_MAX: post-user library callout.
+ ///
+ /// @param library_index New library index.
+ ///
+ /// @throw NoSuchLibrary if the index is not valid.
+ void setLibraryIndex(int library_index) {
+ checkLibraryIndex(library_index);
+ current_library_ = library_index;
+ }
+
+ /// @defgroup calloutManagerLibraryHandles Callout manager library handles
+ ///
+ /// The CalloutManager offers three library handles:
+ ///
+ /// - a "standard" one, used to register and deregister callouts for
+ /// the library index that is marked as current in the CalloutManager.
+ /// When a callout is called, it is passed this one.
+ /// - a pre-library callout handle, used by the server to register
+ // callouts to run prior to user-library callouts.
+ /// - a post-library callout handle, used by the server to register
+ /// callouts to run after the user-library callouts.
+ //@{
+
+ /// @brief Return library handle
+ ///
+ /// The library handle is available to the user callout via the callout
+ /// handle object. It provides a cut-down view of the CalloutManager,
+ /// allowing the callout to register and deregister callouts in the
+ /// library of which it is part, whilst denying access to anything that
+ /// may affect other libraries.
+ ///
+ /// @return Reference to library handle for this manager
+ LibraryHandle& getLibraryHandle() {
+ return (library_handle_);
+ }
+
+ /// @brief Return pre-user callouts library handle
+ ///
+ /// The LibraryHandle to affect callouts that will run before the
+ /// user-library callouts.
+ ///
+ /// @return Reference to pre-user library handle for this manager
+ LibraryHandle& getPreLibraryHandle() {
+ return (pre_library_handle_);
+ }
+
+ /// @brief Return post-user callouts library handle
+ ///
+ /// The LibraryHandle to affect callouts that will run before the
+ /// user-library callouts.
+ ///
+ /// @return Reference to post-user library handle for this manager
+ LibraryHandle& getPostLibraryHandle() {
+ return (post_library_handle_);
+ }
+
+ //@}
+
+private:
+ /// @brief Check library index
+ ///
+ /// Ensures that the current library index is valid. This is called by
+ /// the hook registration functions.
+ ///
+ /// @param library_index Value to check for validity as a library index.
+ /// Valid values are 0 - numlib+1 and -1: see @ref setLibraryIndex
+ /// for the meaning of the various values.
+ ///
+ /// @throw NoSuchLibrary Library index is not valid.
+ void checkLibraryIndex(int library_index) const;
+
+ /// @brief Compare two callout entries for library equality
+ ///
+ /// This is used in callout removal code when all callouts on a hook for a
+ /// given library are being removed. It checks whether two callout entries
+ /// have the same library index.
+ ///
+ /// @param ent1 First callout entry to check
+ /// @param ent2 Second callout entry to check
+ ///
+ /// @return bool true if the library entries are the same
+ class CalloutLibraryEqual :
+ public std::binary_function<CalloutEntry, CalloutEntry, bool> {
+ public:
+ bool operator()(const CalloutEntry& ent1,
+ const CalloutEntry& ent2) const {
+ return (ent1.first == ent2.first);
+ }
+ };
+
+ /// Current hook. When a call is made to callCallouts, this holds the
+ /// index of the current hook. It is set to an invalid value (-1)
+ /// otherwise.
+ int current_hook_;
+
+ /// Current library index. When a call is made to any of the callout
+ /// registration methods, this variable indicates the index of the user
+ /// library that should be associated with the call.
+ int current_library_;
+
+ /// Vector of callout vectors. There is one entry in this outer vector for
+ /// each hook. Each element is itself a vector, with one entry for each
+ /// callout registered for that hook.
+ std::vector<CalloutVector> hook_vector_;
+
+ /// LibraryHandle object user by the callout to access the callout
+ /// registration methods on this CalloutManager object. The object is set
+ /// such that the index of the library associated with any operation is
+ /// whatever is currently set in the CalloutManager.
+ LibraryHandle library_handle_;
+
+ /// LibraryHandle for callouts to be registered as being called before
+ /// the user-registered callouts.
+ LibraryHandle pre_library_handle_;
+
+ /// LibraryHandle for callouts to be registered as being called after
+ /// the user-registered callouts.
+ LibraryHandle post_library_handle_;
+
+ /// Number of libraries.
+ int num_libraries_;
+};
+
+} // namespace util
+} // namespace isc
+
+#endif // CALLOUT_MANAGER_H
diff --git a/src/lib/hooks/hooks.h b/src/lib/hooks/hooks.h
new file mode 100644
index 0000000..a059d79
--- /dev/null
+++ b/src/lib/hooks/hooks.h
@@ -0,0 +1,38 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef HOOKS_H
+#define HOOKS_H
+
+#include <hooks/callout_handle.h>
+#include <hooks/library_handle.h>
+
+namespace {
+
+// Version 1 of the hooks framework.
+const int BIND10_HOOKS_VERSION = 1;
+
+// Names of the framework functions.
+const char* const LOAD_FUNCTION_NAME = "load";
+const char* const UNLOAD_FUNCTION_NAME = "unload";
+const char* const VERSION_FUNCTION_NAME = "version";
+
+// Typedefs for pointers to the framework functions.
+typedef int (*version_function_ptr)();
+typedef int (*load_function_ptr)(isc::hooks::LibraryHandle&);
+typedef int (*unload_function_ptr)();
+
+} // Anonymous namespace
+
+#endif // HOOKS_H
diff --git a/src/lib/hooks/hooks_component_developer.dox b/src/lib/hooks/hooks_component_developer.dox
new file mode 100644
index 0000000..edf59a8
--- /dev/null
+++ b/src/lib/hooks/hooks_component_developer.dox
@@ -0,0 +1,483 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/**
+ at page hooksComponentDeveloperGuide Guide to Hooks for the BIND 10 Component Developer
+
+ at section hooksComponentIntroduction Introduction
+
+The hooks framework is a BIND 10 system that simplifies the way that
+users can write code to modify the behavior of BIND 10. Instead of
+altering the BIND 10 source code, they write functions that are compiled
+and linked into a shared library. The library is specified in the BIND 10
+configuration database and run time, BIND 10 dynamically loads the library
+into its address space. At various points in the processing, the component
+"calls out" to functions in the library, passing to them the data is it
+currently working on. They can examine and modify the data as required.
+
+This guide is aimed at BIND 10 developers who want to write or modify a
+BIND 10 component to use hooks. It shows how the component should be written
+to load a shared library at run-time and how to call functions in it.
+
+For information about writing a hooks library containing functions called by BIND 10
+during its execution, see the document @ref hooksDevelopersGuide.
+
+ at subsection hooksComponentTerminology Terminology
+
+In the remainder of this guide, the following terminology is used:
+
+- Component - a BIND 10 process, e.g. the authoritative DNS server or the
+DHCPv4 server.
+
+- Hook/Hook Point - used interchageably, this is a point in the code at
+which a call to user-written functions is made. Each hook has a name and
+each hook can have any number (including 0) of user-written functions
+attached to it.
+
+- Callout - a user-written function called by the component at a hook
+point. This is so-named because the component "calls out" to the library
+to execute a user-written function.
+
+- User code/user library - non-BIND 10 code that is compiled into a
+shared library and loaded by BIND 10 into its address space. Multiple
+user libraries can be loaded at the same time, each containing callouts for
+the same hooks. The hooks framework calls these libraries one after the
+other. (See the document @ref hooksDevelopersGuide for more details.)
+
+ at subsection hooksComponentLanguages Languages
+
+The core of BIND 10 is written in C++ with some parts in Python. While it is
+the intention to provide the hooks framework for all languages, the initial
+version is for C++. All examples in this guide are in that language.
+
+ at section hooksComponentBasicIdeas Basic Ideas
+
+From the point of view of the component author, the basic ideas of the hooks
+framework are quite simple:
+
+- The location of hook points in the code need to be determined.
+
+- Name the hook points and register them.
+
+- At each hook point, the component needs to complete the following steps to
+ execute callouts registered by the user-library:
+ -# copy data into the object used to pass information to the callout.
+ -# call the callout.
+ -# copy data back from the object used to exchange information.
+ -# take action based on information returned.
+
+Of course, to set up the system the libraries need to be loaded in the first
+place. The component also needs to:
+
+- Define the configuration item that specifies the user libraries for this
+component.
+
+- Handle configuration changes and load/unload the user libraries.
+
+The following sections will describe these tasks in more detail.
+
+ at section hooksComponentDefinition Determing the Hook Points
+
+Before any other action takes place, the location of the hook points
+in the code need to be determined. This of course depends on the
+component but as a general guideline, hook locations should be chosen
+where a callout is able to obtain useful information from BIND 10 and/or
+affect processing. Typically this means at the start or end of a major
+step in the processing of a request, at a point where either useful
+information can be passed to a callout and/or the callout can affect
+the processing of the component. The latter is achieved in either or both
+of the following eays:
+
+- Setting the "skip" flag. This is a boolean flag that the callout can set
+ and is a quick way of passing information back to the component. It is used
+ to indicate that the component should skip the processing step associated with
+ the hook. The exact action is up to the component, but is likely to be one
+ of skipping the processing step (probably because the callout has
+ done its own processing for the action) or dropping the current packet
+ and starting on a new request.
+
+- Modifying data passed to it. The component should be prepared to continue
+ processing with the data returned by the callout. It is up to the component
+ author whether the data is validated before being used, but doing so will
+ have performance implications.
+
+ at section hooksComponentRegistration Naming and Registering the Hooks Points
+
+Once the location of the hook point has been determined, it should be
+given a name. This name should be unique amongst all hook points and is
+subject to certain restrictions (see below).
+
+Before the callouts at any hook point are called and any user libraries
+loaded - so typically during component initialization - the component must
+register the names of all the hooks. The registration is done using
+the static method isc::hooks::HooksManager::registerHook():
+
+ at code
+
+#include <hooks/hooks_manager.h>
+ :
+ int example_index = HooksManager::registerHook("lease_allocate");
+ at endcode
+
+The name of the hook is passed as the sole argument to the registerHook()
+method. The value returned is the index of that hook point and should
+be retained - it is needed to call the callouts attached to that hook.
+
+Note that a hook only needs to be registered once. There is no mechanism for
+unregistering a hook and there is no need to do so.
+
+ at subsection hooksComponentAutomaticRegistration Automatic Registration of Hooks
+
+In some components, it may be convenient to set up a single initialization
+function that registers all hooks. For others, it may be more convenient
+for each module within the component to perform its own initialization.
+Since the isc::hooks::HooksManager object is a singleton and is created when first
+accessed, a useful trick is to automatically register the hooks when
+the module is loaded.
+
+This technique involves declaring an object outside of any execution
+unit in the module. When the module is loaded, the object's constructor
+is run. By placing the hook registration calls in the constructor,
+the hooks in the module are defined at load time, before any function in
+the module is run. The code for such an initialization sequence would
+be similar to:
+
+ at code
+#include <hooks/hooks_manager.h>
+
+namespace {
+
+// Declare structure to perform initialization and store the hook indexes.
+//
+struct MyHooks {
+ int pkt_rcvd; // Index of "packet received" hook
+ int pkt_sent; // Index of "packet sent" hook
+
+ // Constructor
+ MyHooks() {
+ pkt_rcvd = HooksManager::registerHook("pkt_rcvd");
+ pkt_sent = HooksManager::registerHook("pkt_sent");
+ }
+};
+
+// Declare a "MyHooks" object. As this is outside any function or method, it
+// will be instantiated (and the constructor run) when the module is loaded.
+// As a result, the hook indexes will be defined before any method in this
+// module is called.
+MyHooks my_hooks;
+
+} // Anonymous namespace
+
+void Someclass::someFunction() {
+ :
+ // Check if any callouts are defined on the pkt_rcvd hook.
+ if (HooksManager::calloutPresent(my_hooks.pkt_rcvd)) {
+ :
+ }
+ :
+}
+ at endcode
+
+ at subsection hooksComponentHookNames Hook Names
+
+Hook names are strings and in principle, any string can be used as the
+name of a hook, even one containing spaces and non-printable characters.
+However, the following guidelines should be observed:
+
+- The names <b>context_create</b> and <b>context_destroy</b> are reserved to
+the hooks system and are automatically registered: an attempt to register
+one of these will lead to a isc::hooks::DuplicateHook exception being thrown.
+
+- The hook name should be a valid "C" function name. If a user gives a
+callout the same name as one of the hooks, the hooks framework will
+automatically load that callout and attach it to the hook: the user does not
+have to explicitly register it.
+
+- The hook name should not conflict with the name of a function in any of
+the system libraries (e.g. naming a hook "sqrt" could lead to the
+square-root function in the system's maths library being attached to the hook
+as a callout).
+
+- Although hook names can be in any case (including mixed case), the BIND 10
+convention is that they are lower-case.
+
+ at section hooksComponentCallingCallouts Calling Callouts on a Hook
+
+ at subsection hooksComponentArgument The Callout Handle
+
+Before describing how to call user code at a hook point, we must first consider
+how to pass data to it.
+
+Each user callout has the signature:
+ at code
+int callout_name(isc::hooks::CalloutHandle& handle);
+ at endcode
+
+The isc::hooks::CalloutHandle object is the object used to pass data to
+and from the callout. This holds the data as a set of name/value pairs,
+each pair being considered an argument to the callout. If there are
+multiple callouts attached to a hook, the CalloutHandle is passed to
+each in turn. Should a callout modify an argument, the updated data is
+passed subsequent callouts (each of which could also modify it) before
+being returned to the component.
+
+Two methods are provided to get and set the arguments passed to
+the callout called (naturally enough) getArgument and SetArgument.
+Their usage is illustrated by the following code snippets.
+
+ at code
+ int count = 10;
+ boost::shared_ptr<Pkt4> pktptr = ... // Set to appropriate value
+
+ // Assume that "handle_ptr" has been created and is a pointer to a
+ // CalloutHandle.
+ handle_ptr->setArgument("data_count", count);
+ handle_ptr->setArgument("inpacket", pktptr);
+
+ // Call the hook code. lease_assigned_index is the value returned from
+ // HooksManager::registerHook() when the hook was registered.
+ HooksManager::callCallouts(lease_assigned_index, *handle_ptr);
+
+ // Retrieve the modified values
+ handle_ptr->getArgument("data_count", count);
+ handle_ptr->getArgument("inpacket", pktptr);
+ at endcode
+
+As can be seen "getArgument" is used to retrieve data from the
+CalloutHandle, and "setArgument" used to put data into it. If a callout
+wishes to alter data and pass it back to the component, it should retrieve
+the data with getArgument, modify it, and call setArgument to send
+it back.
+
+There are a couple points to be aware of:
+
+- The data type of the variable in the call to getArgument must
+match the data type of the variable passed to the corresponding
+setArgument <B>exactly</B>: using what would normally be considered
+to be a "compatible" type is not enough. For example, if the callout
+passed an argument back to the component as an "int" and the component
+attempted to retrieve it as a "long", an exception would be thrown even
+though any value that can be stored in an "int" will fit into a "long".
+This restriction also applies the "const" attribute but only as applied to
+data pointed to by pointers, e.g. if an argument is defined as a "char*",
+an exception will be thrown if an attempt is made to retrieve it into
+a variable of type "const char*". (However, if an argument is set as a
+"const int", it can be retrieved into an "int".) The documentation of
+a hook point should detail the exact data type of each argument.
+
+- If a pointer to an object is passed to a callout (either a "raw"
+pointer, or a boost smart pointer (as in the example above), and the
+underlying object is altered through that pointer, the change will be
+reflected in the component even if the callout makes no call to setArgument.
+This can be avoided by passing a pointer to a "const" object.
+
+ at subsection hooksComponentSkipFlag The Skip Flag
+
+Although information is passed back to the component from callouts through
+CalloutHandle arguments, a common action for callouts is to inform the component
+that its flow of control should be altered. For example:
+
+- In the DHCP servers, there is a hook at the point at which a lease is
+ about to be assigned. Callouts attached to this hooks may handle the
+ lease assignment in special cases, in which case they set the skip flag
+ to indicate that the server should not perform lease assignment in this
+ case.
+- A server may define a hook just after a packet is received. A callout
+ attached to the hook might inspect the source address and compare it
+ against a blacklist. If the address is on the list, the callout could set
+ the skip flag to indicate to the server that the packet should be dropped.
+
+For ease of processing, the CalloutHandle contains
+two methods, isc::hooks::CalloutHandle::getSkip() and
+isc::hooks::CalloutHandle::setSkip(). It is only meaningful for the
+component to use the "get" method. The skip flag is cleared by the hooks
+framework when the component requests that callouts be executed, so any
+value set by the component is lost. Callouts can both inspect the flag (it
+might have been set by callouts earlier in the callout list for the hook)
+and set it. Note that the setting of the flag by a callout does not
+prevent callouts later in the list from being called: the skip flag is
+just a boolean flag - the only significance comes from its interpretation
+by the component.
+
+An example of use could be:
+ at code
+// Set up arguments for DHCP lease assignment.
+handle->setArgument("query", query);
+handle->setArgument("response", response);
+HooksManager::callCallouts(lease_hook_index, *handle_ptr);
+if (! handle_ptr->getSkip()) {
+ // Skip flag not set, do the address allocation
+ :
+}
+ at endcode
+
+
+ at subsection hooksComponentGettingHandle Getting the Callout Handle
+
+The CalloutHandle object is linked to the loaded libraries
+for lifetime reasons (described below). Components
+should retrieve a isc::hooks::CalloutHandle using
+isc::hooks::HooksManager::createCalloutHandle():
+ at code
+ CalloutHandlePtr handle_ptr = HooksManager::createCalloutHandle();
+ at endcode
+(isc::hooks::CalloutHandlePtr is a typedef for a Boost shared pointer to a
+CalloutHandle.) The CalloutHandle so retrieved may be used for as
+long as the libraries are loaded.
+
+The handle is deleted by resetting the pointer:
+ at code
+ handle_ptr.reset();
+ at endcode
+... or by letting the handle pointer go out of scope. The actual deletion
+occurs when the CallHandle's reference count goes to zero. (The
+current version of the hooks framework does not maintain any other
+pointer to the returned CalloutHandle, so it gets destroyed when the
+shared pointer to it is cleared or destroyed. However, this may change
+in a future version.)
+
+ at subsection hooksComponentCallingCallout Calling the Callout
+
+Calling the callout is a simple matter of executing the
+isc::hooks::HooksManager::callCallouts() method for the hook index in
+question. For example, with the hook index pkt_sent defined as above,
+the hook can be executed by:
+ at code
+ HooksManager::callCallouts(pkt_sent, *handle_ptr);
+ at endcode
+... where "*handle_ptr" is a reference (note: not a pointer) to the
+isc::hooks::CalloutHandle object holding the arguments. No status code
+is returned. If a component needs to get data returned (other than that
+provided by the "skip" flag), it should define an argument through which
+the callout can do so.
+
+ at subsubsection hooksComponentConditionalCallout Conditionally Calling Hook Callouts
+
+Most hooks in a component will not have callouts attached to them. To
+avoid the overhead of setting up arguments in the CalloutHandle, a
+component can check for callouts before doing that processing using
+isc::hooks::HooksManager::calloutsPresent(). Taking the index of a
+hook as its sole argument, the function returns true if there are any
+callouts attached to the hook and false otherwise.
+
+With this check, the code in the component for calling a hook would look
+something like:
+ at code
+if (HooksManager::calloutsPresent(lease_hook_index)) {
+ // Set up arguments for lease assignment
+ handle->setArgument("query", query);
+ handle->setArgument("response", response);
+ HooksManager::callCallouts(lease_hook_index, *handle);
+ if (! handle->getSkip()) {
+ // Skip flag not set, do the address allocation
+ :
+ }
+}
+ at endcode
+
+ at section hooksComponentLoadLibraries Loading the User Libraries
+
+Once hooks are defined, all the hooks code described above will
+work, even if no libraries are loaded (and even if the library
+loading method is not called). The CalloutHandle returned by
+isc::hooks::HooksManager::createCalloutHandle() will be valid,
+isc::hooks::HooksManager::calloutsPresent() will return false for every
+index, and isc::hooks::HooksManager::callCallouts() will be a no-op.
+
+However, if user libraries are specified in the BIND 10 configuration,
+the component should load them. (Note the term "libraries": the hooks
+framework allows multiple user libraries to be loaded.) This should take
+place after the component's configuration has been read, and is achieved
+by the isc::hooks::HooksManager::loadLibraries() method. The method is
+passed a vector of strings, each giving the full file specification of
+a user library:
+ at code
+ std::vector<std::string> libraries = ... // Get array of libraries
+ bool success = HooksManager::loadLibraries(libraries);
+ at endcode
+loadLibraries() returns a boolean status which is true if all libraries
+loaded successfully or false if one or more failed to load. Appropriate
+error messages will have been logged in the latter case, the status
+being more to allow the developer to decide whether the execution
+should proceed in such circumstances.
+
+If loadLibraries() is called a second or subsequent time (as a result
+of a reconfiguration), all existing libraries are unloaded and a new
+set loaded. Libraries can be explicitly unloaded either by calling
+isc::hooks::HooksManager::unloadLibraries() or by calling
+loadLibraries() with an empty vector as an argument.
+
+ at subsection hooksComponentUnloadIssues Unload and Reload Issues
+
+Unloading a shared library works by unmapping the part of the process's
+virtual address space in which the library lies. This may lead to
+problems if there are still references to that address space elsewhere
+in the process.
+
+In many operating systems, heap storage allowed by a shared library will
+lie in the virtual address allocated to the library. This has implications
+in the hooks framework because:
+
+- Argument information stored in a CalloutHandle by a callout in a library
+may lie in the library's address space.
+- Data modified in objects passed as arguments may lie in the address
+space. For example, it is common for a DHCP callout to add "options"
+to a packet: the memory allocated for those options will most likely
+lie in library address space.
+
+The problem really arises because of the extensive use by BIND 10 of boost
+smart pointers. When the pointer is destroyed, the pointed-to memory is
+deallocated. If the pointer points to address space that is unmapped because
+a library has been unloaded, the deletion causes a segmentation fault.
+
+The hooks framework addresses the issue for CalloutHandles by keeping in
+that object a shared pointer to the object controlling library unloading.
+Although a library can be unloaded at any time, it is only when all
+CalloutHandles that could possibly reference address space in the library
+have been deleted that the library will actually be unloaded and the
+address space unmapped.
+
+The hooks framework cannot solve the second issue as the objects in
+question are under control of the component. It is up to the component
+developer to ensure that all such objects have been destroyed before
+libraries are reloaded. In extreme cases this may mean the component
+suspending all processing of incoming requests until all currently
+executing requests have completed and data object destroyed, reloading
+the libraries, then resuming processing.
+
+ at section hooksComponentCallouts Component-Defined Callouts
+
+Previous sections have discussed callout registration by user libraries.
+It is possible for a component to register its own functions (i.e. within
+its own address space) as hook callouts. These functions are called
+in eactly the same way as user callouts, being passed their arguments
+though a CalloutHandle object. (Guidelines for writing callouts can be
+found in @ref hooksDevelopersGuide.)
+
+A component can associate with a hook callouts that run either before
+user-registered callouts or after them. Registration is done via a
+isc::hooks::LibraryHandle object, a reference to one being obtained
+through the methods isc::hooks::HooksManager::preCalloutLibraryHandle()
+(for a handle to register callouts to run before the user library
+callouts) or isc::hooks::HooksManager::postCalloutLibraryHandle() (for
+a handle to register callouts to run after the user callouts). Use of
+the LibraryHandle to register and deregister callouts is described in
+ at ref hooksLibraryHandle.
+
+Finally, it should be noted that callouts registered in this way only
+remain registered until the next call to isc::hooks::loadLibraries().
+It is up to the component to re-register the callouts after this
+method has been called.
+
+*/
diff --git a/src/lib/hooks/hooks_log.cc b/src/lib/hooks/hooks_log.cc
new file mode 100644
index 0000000..360394c
--- /dev/null
+++ b/src/lib/hooks/hooks_log.cc
@@ -0,0 +1,26 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/// Defines the logger used by the NSAS
+
+#include "hooks/hooks_log.h"
+
+namespace isc {
+namespace hooks {
+
+isc::log::Logger hooks_logger("hooks");
+
+} // namespace hooks
+} // namespace isc
+
diff --git a/src/lib/hooks/hooks_log.h b/src/lib/hooks/hooks_log.h
new file mode 100644
index 0000000..92d429a
--- /dev/null
+++ b/src/lib/hooks/hooks_log.h
@@ -0,0 +1,50 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef HOOKS_LOG_H
+#define HOOKS_LOG_H
+
+#include <log/macros.h>
+#include <hooks/hooks_messages.h>
+
+namespace isc {
+namespace hooks {
+
+/// @brief Hooks debug Logging levels
+///
+/// Defines the levels used to output debug messages in the Hooks framework.
+/// Note that higher numbers equate to more verbose (and detailed) output.
+
+// The first level traces normal operations,
+const int HOOKS_DBG_TRACE = DBGLVL_TRACE_BASIC;
+
+// The next level traces each call to hook code.
+const int HOOKS_DBG_CALLS = DBGLVL_TRACE_BASIC_DATA;
+
+// Additional information on the calls. Report each call to a callout (even
+// if there are multiple callouts on a hook) and each status return.
+const int HOOKS_DBG_EXTENDED_CALLS = DBGLVL_TRACE_DETAIL_DATA;
+
+
+/// @brief Hooks Logger
+///
+/// Define the logger used to log messages. We could define it in multiple
+/// modules, but defining in a single module and linking to it saves time and
+/// space.
+extern isc::log::Logger hooks_logger;
+
+} // namespace hooks
+} // namespace isc
+
+#endif // HOOKS_LOG_H
diff --git a/src/lib/hooks/hooks_manager.cc b/src/lib/hooks/hooks_manager.cc
new file mode 100644
index 0000000..39e1fc5
--- /dev/null
+++ b/src/lib/hooks/hooks_manager.cc
@@ -0,0 +1,173 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <hooks/callout_handle.h>
+#include <hooks/callout_manager.h>
+#include <hooks/callout_manager.h>
+#include <hooks/library_handle.h>
+#include <hooks/library_manager_collection.h>
+#include <hooks/hooks_manager.h>
+#include <hooks/server_hooks.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <string>
+#include <vector>
+
+using namespace std;
+
+namespace isc {
+namespace hooks {
+
+// Constructor
+
+HooksManager::HooksManager() {
+}
+
+// Return reference to singleton hooks manager.
+
+HooksManager&
+HooksManager::getHooksManager() {
+ static HooksManager manager;
+ return (manager);
+}
+
+// Are callouts present?
+
+bool
+HooksManager::calloutsPresentInternal(int index) {
+ conditionallyInitialize();
+ return (callout_manager_->calloutsPresent(index));
+}
+
+bool
+HooksManager::calloutsPresent(int index) {
+ return (getHooksManager().calloutsPresentInternal(index));
+}
+
+// Call the callouts
+
+void
+HooksManager::callCalloutsInternal(int index, CalloutHandle& handle) {
+ conditionallyInitialize();
+ return (callout_manager_->callCallouts(index, handle));
+}
+
+void
+HooksManager::callCallouts(int index, CalloutHandle& handle) {
+ return (getHooksManager().callCalloutsInternal(index, handle));
+}
+
+// Load the libraries. This will delete the previously-loaded libraries
+// (if present) and load new ones.
+
+bool
+HooksManager::loadLibrariesInternal(const std::vector<std::string>& libraries) {
+ // Unload current set of libraries (if any are loaded).
+ unloadLibrariesInternal();
+
+ // Create the library manager and load the libraries.
+ lm_collection_.reset(new LibraryManagerCollection(libraries));
+ bool status = lm_collection_->loadLibraries();
+
+ // ... and obtain the callout manager for them.
+ callout_manager_ = lm_collection_->getCalloutManager();
+
+ return (status);
+}
+
+bool
+HooksManager::loadLibraries(const std::vector<std::string>& libraries) {
+ return (getHooksManager().loadLibrariesInternal(libraries));
+}
+
+// Unload the libraries. This just deletes all internal objects which will
+// cause the libraries to be unloaded.
+
+void
+HooksManager::unloadLibrariesInternal() {
+ // The order of deletion does not matter here, as each library manager
+ // holds its own pointer to the callout manager. However, we may as
+ // well delete the library managers first: if there are no other references
+ // to the callout manager, the second statement will delete it, which may
+ // ease debugging.
+ lm_collection_.reset();
+ callout_manager_.reset();
+}
+
+void HooksManager::unloadLibraries() {
+ getHooksManager().unloadLibrariesInternal();
+}
+
+// Create a callout handle
+
+boost::shared_ptr<CalloutHandle>
+HooksManager::createCalloutHandleInternal() {
+ conditionallyInitialize();
+ return (boost::shared_ptr<CalloutHandle>(
+ new CalloutHandle(callout_manager_, lm_collection_)));
+}
+
+boost::shared_ptr<CalloutHandle>
+HooksManager::createCalloutHandle() {
+ return (getHooksManager().createCalloutHandleInternal());
+}
+
+// Perform conditional initialization if nothing is loaded.
+
+void
+HooksManager::performConditionalInitialization() {
+
+ // Nothing present, so create the collection with any empty set of
+ // libraries, and get the CalloutManager.
+ vector<string> libraries;
+ lm_collection_.reset(new LibraryManagerCollection(libraries));
+ lm_collection_->loadLibraries();
+
+ callout_manager_ = lm_collection_->getCalloutManager();
+}
+
+// Shell around ServerHooks::registerHook()
+
+int
+HooksManager::registerHook(const std::string& name) {
+ return (ServerHooks::getServerHooks().registerHook(name));
+}
+
+// Return pre- and post- library handles.
+
+isc::hooks::LibraryHandle&
+HooksManager::preCalloutsLibraryHandleInternal() {
+ conditionallyInitialize();
+ return (callout_manager_->getPreLibraryHandle());
+}
+
+isc::hooks::LibraryHandle&
+HooksManager::preCalloutsLibraryHandle() {
+ return (getHooksManager().preCalloutsLibraryHandleInternal());
+}
+
+isc::hooks::LibraryHandle&
+HooksManager::postCalloutsLibraryHandleInternal() {
+ conditionallyInitialize();
+ return (callout_manager_->getPostLibraryHandle());
+}
+
+isc::hooks::LibraryHandle&
+HooksManager::postCalloutsLibraryHandle() {
+ return (getHooksManager().postCalloutsLibraryHandleInternal());
+}
+
+} // namespace util
+} // namespace isc
diff --git a/src/lib/hooks/hooks_manager.h b/src/lib/hooks/hooks_manager.h
new file mode 100644
index 0000000..03aa521
--- /dev/null
+++ b/src/lib/hooks/hooks_manager.h
@@ -0,0 +1,276 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef HOOKS_MANAGER_H
+#define HOOKS_MANAGER_H
+
+#include <hooks/server_hooks.h>
+
+#include <boost/noncopyable.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace hooks {
+
+// Forward declarations
+class CalloutHandle;
+class CalloutManager;
+class LibraryHandle;
+class LibraryManagerCollection;
+
+/// @brief Hooks Manager
+///
+/// This is the overall manager of the hooks framework and is the main class
+/// used by a BIND 10 module when handling hooks. It is responsible for the
+/// loading and unloading of user libraries, and for calling the callouts on
+/// each hook point.
+///
+/// The class is a singleton, the single instance of the object being accessed
+/// through the static getHooksManager() method.
+
+class HooksManager : boost::noncopyable {
+public:
+ /// @brief Get singleton hooks manager
+ ///
+ /// @return Reference to the singleton hooks manager.
+ static HooksManager& getHooksManager();
+
+ /// @brief Load and reload libraries
+ ///
+ /// Loads the list of libraries into the server address space. For each
+ /// library, the "standard" functions (ones with the same names as the
+ /// hook points) are configured and the libraries' "load" function
+ /// called.
+ ///
+ /// If libraries are already loaded, they are unloaded and the new
+ /// libraries loaded.
+ ///
+ /// If any library fails to load, an error message will be logged. The
+ /// remaining libraries will be loaded if possible.
+ ///
+ /// @param libraries List of libraries to be loaded. The order is
+ /// important, as it determines the order that callouts on the same
+ /// hook will be called.
+ ///
+ /// @return true if all libraries loaded without a problem, false if one or
+ /// more libraries failed to load. In the latter case, message will
+ /// be logged that give the reason.
+ static bool loadLibraries(const std::vector<std::string>& libraries);
+
+ /// @brief Unload libraries
+ ///
+ /// Unloads the loaded libraries and leaves the hooks subsystem in the
+ /// state it was after construction but before loadLibraries() is called.
+ ///
+ /// @note: This method should be used with caution - see the notes for
+ /// the class LibraryManager for pitfalls. In general, a server
+ /// should not call this method: library unloading will automatically
+ /// take place when new libraries are loaded, and when appropriate
+ /// objects are destroyed.
+ ///
+ /// @return true if all libraries unloaded successfully, false on an error.
+ /// In the latter case, an error message will have been output.
+ static void unloadLibraries();
+
+ /// @brief Are callouts present?
+ ///
+ /// Checks loaded libraries and returns true if at lease one callout
+ /// has been registered by them for the given hook.
+ ///
+ /// @param index Hooks index for which callouts are checked.
+ ///
+ /// @return true if callouts are present, false if not.
+ /// @throw NoSuchHook Given index does not correspond to a valid hook.
+ static bool calloutsPresent(int index);
+
+ /// @brief Calls the callouts for a given hook
+ ///
+ /// Iterates through the libray handles and calls the callouts associated
+ /// with the given hook index.
+ ///
+ /// @note This method invalidates the current library index set with
+ /// setLibraryIndex().
+ ///
+ /// @param index Index of the hook to call.
+ /// @param handle Reference to the CalloutHandle object for the current
+ /// object being processed.
+ static void callCallouts(int index, CalloutHandle& handle);
+
+ /// @brief Return pre-callouts library handle
+ ///
+ /// Returns a library handle that can be used by the server to register
+ /// callouts on a hook that are called _before_ any callouts belonging
+ /// to a library.
+ ///
+ /// @note Both the reference returned and the callouts registered with
+ /// this handle only remain valid until the next loadLibraries() or
+ /// unloadLibraries() call. If the callouts are to remain registered
+ /// after this time, a new handle will need to be obtained and the
+ /// callouts re-registered.
+ ///
+ /// @return Reference to library handle associated with pre-library callout
+ /// registration.
+ static LibraryHandle& preCalloutsLibraryHandle();
+
+ /// @brief Return post-callouts library handle
+ ///
+ /// Returns a library handle that can be used by the server to register
+ /// callouts on a hook that are called _after any callouts belonging
+ /// to a library.
+ ///
+ /// @note Both the reference returned and the callouts registered with
+ /// this handle only remain valid until the next loadLibraries() or
+ /// unloadLibraries() call. If the callouts are to remain registered
+ /// after this time, a new handle will need to be obtained and the
+ /// callouts re-registered.
+ ///
+ /// @return Reference to library handle associated with post-library callout
+ /// registration.
+ static LibraryHandle& postCalloutsLibraryHandle();
+
+ /// @brief Return callout handle
+ ///
+ /// Returns a callout handle to be associated with a request passed round
+ /// the system.
+ ///
+ /// @note This handle is valid only after a loadLibraries() call and then
+ /// only up to the next loadLibraries() call.
+ ///
+ /// @return Shared pointer to a CalloutHandle object.
+ static boost::shared_ptr<CalloutHandle> createCalloutHandle();
+
+ /// @brief Register Hook
+ ///
+ /// This is just a convenience shell around the ServerHooks::registerHook()
+ /// method. It - along with the definitions of the two hook indexes for
+ /// the context_create and context_destroy methods - means that server
+ /// authors only need to deal with HooksManager and CalloutHandle, and not
+ /// include any other hooks framework classes.
+ ///
+ /// @param name Name of the hook
+ ///
+ /// @return Index of the hook, to be used in subsequent hook-related calls.
+ /// This will be greater than or equal to zero (so allowing a
+ /// negative value to indicate an invalid index).
+ ///
+ /// @throws DuplicateHook A hook with the same name has already been
+ /// registered.
+ static int registerHook(const std::string& name);
+
+ /// Index numbers for pre-defined hooks.
+ static const int CONTEXT_CREATE = ServerHooks::CONTEXT_CREATE;
+ static const int CONTEXT_DESTROY = ServerHooks::CONTEXT_DESTROY;
+
+private:
+
+ /// @brief Constructor
+ ///
+ /// This is private as the object is a singleton and can only be addessed
+ /// through the getHooksManager() static method.
+ HooksManager();
+
+ //@{
+ /// The following methods correspond to similarly-named static methods,
+ /// but actually do the work on the singleton instance of the HooksManager.
+ /// See the descriptions of the static methods for more details.
+
+ /// @brief Load and reload libraries
+ ///
+ /// @param libraries List of libraries to be loaded. The order is
+ /// important, as it determines the order that callouts on the same
+ /// hook will be called.
+ ///
+ /// @return true if all libraries loaded without a problem, false if one or
+ /// more libraries failed to load. In the latter case, message will
+ /// be logged that give the reason.
+ bool loadLibrariesInternal(const std::vector<std::string>& libraries);
+
+ /// @brief Unload libraries
+ void unloadLibrariesInternal();
+
+ /// @brief Are callouts present?
+ ///
+ /// @param index Hooks index for which callouts are checked.
+ ///
+ /// @return true if callouts are present, false if not.
+ /// @throw NoSuchHook Given index does not correspond to a valid hook.
+ bool calloutsPresentInternal(int index);
+
+ /// @brief Calls the callouts for a given hook
+ ///
+ /// @param index Index of the hook to call.
+ /// @param handle Reference to the CalloutHandle object for the current
+ /// object being processed.
+ void callCalloutsInternal(int index, CalloutHandle& handle);
+
+ /// @brief Return callout handle
+ ///
+ /// @return Shared pointer to a CalloutHandle object.
+ boost::shared_ptr<CalloutHandle> createCalloutHandleInternal();
+
+ /// @brief Return pre-callouts library handle
+ ///
+ /// @return Reference to library handle associated with pre-library callout
+ /// registration.
+ LibraryHandle& preCalloutsLibraryHandleInternal();
+
+ /// @brief Return post-callouts library handle
+ ///
+ /// @return Reference to library handle associated with post-library callout
+ /// registration.
+ LibraryHandle& postCalloutsLibraryHandleInternal();
+
+ //@}
+
+ /// @brief Initialization to No Libraries
+ ///
+ /// Initializes the hooks manager with an "empty set" of libraries. This
+ /// method is called if conditionallyInitialize() determines that such
+ /// initialization is needed.
+ void performConditionalInitialization();
+
+ /// @brief Conditional initialization of the hooks manager
+ ///
+ /// loadLibraries() performs the initialization of the HooksManager,
+ /// setting up the internal structures and loading libraries. However,
+ /// in some cases, server authors may not do that. This method is called
+ /// whenever any hooks execution function is invoked (checking callouts,
+ /// calling callouts or returning a callout handle). If the HooksManager
+ /// is unitialised, it will initialize it with an "empty set" of libraries.
+ ///
+ /// For speed, the test of whether initialization is required is done
+ /// in-line here. The actual initialization is performed in
+ /// performConditionalInitialization().
+ void conditionallyInitialize() {
+ if (!lm_collection_) {
+ performConditionalInitialization();
+ }
+ }
+
+ // Members
+
+ /// Set of library managers.
+ boost::shared_ptr<LibraryManagerCollection> lm_collection_;
+
+ /// Callout manager for the set of library managers.
+ boost::shared_ptr<CalloutManager> callout_manager_;
+};
+
+} // namespace util
+} // namespace hooks
+
+#endif // HOOKS_MANAGER_H
diff --git a/src/lib/hooks/hooks_messages.mes b/src/lib/hooks/hooks_messages.mes
new file mode 100644
index 0000000..33c1282
--- /dev/null
+++ b/src/lib/hooks/hooks_messages.mes
@@ -0,0 +1,167 @@
+# Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+$NAMESPACE isc::hooks
+
+% HOOKS_CALLOUT_CALLED hooks library with index %1 has called a callout on hook %2 that has address %3
+Only output at a high debugging level, this message indicates that
+a callout on the named hook registered by the library with the given
+index (in the list of loaded libraries) has been called and returned a
+success state. The address of the callout is given in the message
+
+% HOOKS_CALLOUT_ERROR error returned by callout on hook %1 registered by library with index %2 (callout address %3)
+If a callout returns an error status when called, this error message
+is issued. It identifies the hook to which the callout is attached, the
+index of the library (in the list of loaded libraries) that registered
+it and the address of the callout. The error is otherwise ignored.
+
+% HOOKS_CALLOUTS_REMOVED callouts removed from hook %1 for library %2
+This is a debug message issued during library unloading. It notes that
+one of more callouts registered by that library have been removed from
+the specified hook. This is similar to the HOOKS_DEREGISTER_ALL_CALLOUTS
+message (and the two are likely to be seen together), but is issued at a
+higher-level in the hook framework.
+
+% HOOKS_CLOSE_ERROR failed to close hook library %1: %2
+BIND 10 has failed to close the named hook library for the stated reason.
+Although this is an error, this should not affect the running system
+other than as a loss of resources. If this error persists, you should
+restart BIND 10.
+
+% HOOKS_CALLOUT_EXCEPTION exception thrown by callout on hook %1 registered by library with index %2 (callout address %3)
+If a callout throws an exception when called, this error message is
+issued. It identifies the hook to which the callout is attached, the
+index of the library (in the list of loaded libraries) that registered
+it and the address of the callout. The error is otherwise ignored.
+
+% HOOKS_ALL_CALLOUTS_DEREGISTERED hook library at index %1 removed all callouts on hook %2
+A debug message issued when all callouts on the specified hook registered
+by the library with the given index were removed. This is similar to
+the HOOKS_CALLOUTS_REMOVED message (and the two are likely to be seen
+together), but is issued at a lower-level in the hook framework.
+
+% HOOKS_CALLOUT_DEREGISTERED hook library at index %1 deregistered a callout on hook %2
+A debug message issued when all instances of a particular callouts on
+the hook identified in the message that were registered by the library
+with the given index have been removed.
+
+% HOOKS_INCORRECT_VERSION hook library %1 is at version %2, require version %3
+BIND 10 has detected that the named hook library has been built against
+a version of BIND 10 that is incompatible with the version of BIND 10
+running on your system. It has not loaded the library.
+
+This is most likely due to the installation of a new version of BIND 10
+without rebuilding the hook library. A rebuild and re-install of the
+library should fix the problem in most cases.
+
+% HOOKS_LIBRARY_LOADED hooks library %1 successfully loaded
+This information message is issued when a user-supplied hooks library
+has been successfully loaded.
+
+% HOOKS_LIBRARY_UNLOADED hooks library %1 successfully unloaded
+This information message is issued when a user-supplied hooks library
+has been successfully unloaded.
+
+% HOOKS_LIBRARY_VERSION hooks library %1 reports its version as %2
+A debug message issued when the version check on the hooks library
+has succeeded.
+
+% HOOKS_LOAD_SUCCESS 'load' function in hook library %1 returned success
+This is a debug message issued when the "load" function has been found
+in a hook library and has been successfully called.
+
+% HOOKS_LOAD_ERROR 'load' function in hook library %1 returned error %2
+A "load" function was found in the library named in the message and
+was called. The function returned a non-zero status (also given in
+the message) which was interpreted as an error. The library has been
+unloaded and no callouts from it will be installed.
+
+% HOOKS_LOAD_EXCEPTION 'load' function in hook library %1 threw an exception
+A "load" function was found in the library named in the message and
+was called. The function threw an exception (an error indication)
+during execution, which is an error condition. The library has been
+unloaded and no callouts from it will be installed.
+
+% HOOKS_LIBRARY_LOADING loading hooks library %1
+This is a debug message output just before the specified library is loaded.
+If the action is successfully, it will be followed by the
+HOOKS_LIBRARY_LOADED informational message.
+
+% HOOKS_NO_LOAD no 'load' function found in hook library %1
+This is a debug message saying that the specified library was loaded
+but no function called "load" was found in it. Providing the library
+contained some "standard" functions (i.e. functions with the names of
+the hooks for the given server), this is not an issue.
+
+% HOOKS_NO_UNLOAD no 'unload' function found in hook library %1
+This is a debug message issued when the library is being unloaded.
+It merely states that the library did not contain an "unload" function.
+
+% HOOKS_NO_VERSION no 'version' function found in hook library %1
+The shared library named in the message was found and successfully loaded,
+but BIND 10 did not find a function named "version" in it. This function
+is required and should return the version of BIND 10 against which the
+library was built. The value is used to check that the library was built
+against a compatible version of BIND 10. The library has not been loaded.
+
+% HOOKS_OPEN_ERROR failed to open hook library %1: %2
+BIND 10 failed to open the specified hook library for the stated
+reason. The library has not been loaded. BIND 10 will continue to
+function, but without the services offered by the library.
+
+% HOOKS_CALLOUT_REGISTRATION hooks library with index %1 registering callout for hook '%2'
+This is a debug message, output when a library (whose index in the list
+of libraries (being) loaded is given) registers a callout.
+
+% HOOKS_HOOK_REGISTERED hook %1 was registered
+This is a debug message indicating that a hook of the specified name
+was registered by a BIND 10 server. The server doing the logging is
+indicated by the full name of the logger used to log this message.
+
+% HOOKS_STD_CALLOUT_REGISTERED hooks library %1 registered standard callout for hook %2 at address %3
+This is a debug message, output when the library loading function has
+located a standard callout (a callout with the same name as a hook point)
+and registered it. The address of the callout is indicated.
+
+% HOOKS_HOOK_LIST_RESET the list of hooks has been reset
+This is a message indicating that the list of hooks has been reset.
+While this is usual when running the BIND 10 test suite, it should not be
+seen when running BIND 10 in a producion environment. If this appears,
+please report a bug through the usual channels.
+
+% HOOKS_UNLOAD_SUCCESS 'unload' function in hook library %1 returned success
+This is a debug message issued when an "unload" function has been found
+in a hook library during the unload process, called, and returned success.
+
+% HOOKS_UNLOAD_ERROR 'unload' function in hook library %1 returned error %2
+During the unloading of a library, an "unload" function was found.
+It was called, but returned an error (non-zero) status, resulting in
+the issuing of this message. The unload process continued after this
+message and the library has been unloaded.
+
+% HOOKS_UNLOAD_EXCEPTION 'unload' function in hook library %1 threw an exception
+During the unloading of a library, an "unload" function was found. It was
+called, but in the process generated an exception (an error indication).
+The unload process continued after this message and the library has
+been unloaded.
+
+% HOOKS_LIBRARY_UNLOADING unloading library %1
+This is a debug message called when the specified library is
+being unloaded. If all is successful, it will be followed by the
+HOOKS_LIBRARY_UNLOADED informational message.
+
+% HOOKS_VERSION_EXCEPTION 'version' function in hook library %1 threw an exception
+This error message is issued if the version() function in the specified
+hooks library was called and generated an exception. The library is
+considered unusable and will not be loaded.
diff --git a/src/lib/hooks/library_handle.cc b/src/lib/hooks/library_handle.cc
new file mode 100644
index 0000000..7f43116
--- /dev/null
+++ b/src/lib/hooks/library_handle.cc
@@ -0,0 +1,76 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <hooks/callout_manager.h>
+#include <hooks/library_handle.h>
+
+namespace isc {
+namespace hooks {
+
+// Callout manipulation - all deferred to the CalloutManager.
+
+void
+LibraryHandle::registerCallout(const std::string& name, CalloutPtr callout) {
+ // Reset library index if required, saving the current value.
+ int saved_index = callout_manager_->getLibraryIndex();
+ if (index_ >= 0) {
+ callout_manager_->setLibraryIndex(index_);
+ }
+
+ // Register the callout.
+ callout_manager_->registerCallout(name, callout);
+
+ // Restore the library index if required. We know that the saved index
+ // is valid for the number of libraries (or is -1, which is an internal
+ // state indicating there is no current library index) as we obtained it
+ // from the callout manager.
+ if (index_ >= 0) {
+ callout_manager_->setLibraryIndex(saved_index);
+ }
+}
+
+bool
+LibraryHandle::deregisterCallout(const std::string& name, CalloutPtr callout) {
+ int saved_index = callout_manager_->getLibraryIndex();
+ if (index_ >= 0) {
+ callout_manager_->setLibraryIndex(index_);
+ }
+
+ bool status = callout_manager_->deregisterCallout(name, callout);
+
+ if (index_ >= 0) {
+ callout_manager_->setLibraryIndex(saved_index);
+ }
+
+ return (status);
+}
+
+bool
+LibraryHandle::deregisterAllCallouts(const std::string& name) {
+ int saved_index = callout_manager_->getLibraryIndex();
+ if (index_ >= 0) {
+ callout_manager_->setLibraryIndex(index_);
+ }
+
+ bool status = callout_manager_->deregisterAllCallouts(name);
+
+ if (index_ >= 0) {
+ callout_manager_->setLibraryIndex(saved_index);
+ }
+
+ return (status);
+}
+
+} // namespace util
+} // namespace isc
diff --git a/src/lib/hooks/library_handle.h b/src/lib/hooks/library_handle.h
new file mode 100644
index 0000000..4fe47cd
--- /dev/null
+++ b/src/lib/hooks/library_handle.h
@@ -0,0 +1,149 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef LIBRARY_HANDLE_H
+#define LIBRARY_HANDLE_H
+
+#include <string>
+
+namespace isc {
+namespace hooks {
+
+// Forward declarations
+class CalloutHandle;
+class CalloutManager;
+
+/// Typedef for a callout pointer. (Callouts must have "C" linkage.)
+extern "C" {
+ typedef int (*CalloutPtr)(CalloutHandle&);
+};
+
+/// @brief Library handle
+///
+/// This class is accessed by the user library when registering callouts,
+/// either by the library's load() function, or by one of the callouts
+/// themselves.
+///
+/// It is really little more than a shell around the CalloutManager. By
+/// presenting this object to the user-library callouts, callouts can manage
+/// the callout list for their own library, but cannot affect the callouts
+/// registered by other libraries.
+///
+/// (This restriction is achieved by the CalloutManager maintaining the concept
+/// of the "current library". When a callout is registered - either by the
+/// library's load() function, or by a callout in the library - the registration
+/// information includes the library active at the time. When that callout is
+/// called, the CalloutManager uses that information to set the "current
+/// library": the registration functions only operator on data whose
+/// associated library is equal to the "current library".)
+
+class LibraryHandle {
+public:
+
+ /// @brief Constructor
+ ///
+ /// @param manager Back pointer to the containing CalloutManager.
+ /// This pointer is used to access appropriate methods in that
+ /// object. Note that the "raw" pointer is safe - the only
+ /// instance of the LibraryHandle in the system is as a member of
+ /// the CalloutManager to which it points.
+ ///
+ /// @param index Index of the library to which the LibraryHandle applies.
+ /// If negative, the library index as set in the CalloutManager is
+ /// used. Otherwise the current library index is saved, this value
+ /// is set as the current index, the registration call is made, and
+ /// the CalloutManager's library index is reset. Note: although -1
+ /// is a valid argument value for
+ /// isc::hooks::CalloutManager::setLibraryIndex(), in this class is
+ /// is used as a sentinel to indicate that the library index in
+ /// isc::util::CalloutManager should not be set or reset.
+ LibraryHandle(CalloutManager* manager, int index = -1)
+ : callout_manager_(manager), index_(index)
+ {}
+
+ /// @brief Register a callout on a hook
+ ///
+ /// Registers a callout function with a given hook. The callout is added
+ /// to the end of the callouts for the current library that are associated
+ /// with that hook.
+ ///
+ /// @param name Name of the hook to which the callout is added.
+ /// @param callout Pointer to the callout function to be registered.
+ ///
+ /// @throw NoSuchHook The hook name is unrecognised.
+ /// @throw Unexpected The hook name is valid but an internal data structure
+ /// is of the wrong size.
+ void registerCallout(const std::string& name, CalloutPtr callout);
+
+ /// @brief De-Register a callout on a hook
+ ///
+ /// Searches through the functions registered by the current library with
+ /// the named hook and removes all entries matching the callout. It does
+ /// not affect callouts registered by other libraries.
+ ///
+ /// @param name Name of the hook from which the callout is removed.
+ /// @param callout Pointer to the callout function to be removed.
+ ///
+ /// @return true if a one or more callouts were deregistered.
+ ///
+ /// @throw NoSuchHook The hook name is unrecognised.
+ /// @throw Unexpected The hook name is valid but an internal data structure
+ /// is of the wrong size.
+ bool deregisterCallout(const std::string& name, CalloutPtr callout);
+
+ /// @brief Removes all callouts on a hook
+ ///
+ /// Removes all callouts associated with a given hook that were registered.
+ /// by the current library. It does not affect callouts that were
+ /// registered by other libraries.
+ ///
+ /// @param name Name of the hook from which the callouts are removed.
+ ///
+ /// @return true if one or more callouts were deregistered.
+ ///
+ /// @throw NoSuchHook Thrown if the hook name is unrecognised.
+ bool deregisterAllCallouts(const std::string& name);
+
+private:
+ /// @brief Copy constructor
+ ///
+ /// Private (with no implementation) as it makes no sense to copy an object
+ /// of this type. All code receives a reference to an existing handle which
+ /// is tied to a particular CalloutManager. Creating a copy of that handle
+ /// runs the risk of a "dangling pointer" to the original handle's callout
+ /// manager.
+ ///
+ /// @param Unused - should be the object to copy.
+ LibraryHandle(const LibraryHandle&);
+
+ /// @brief Assignment operator
+ ///
+ /// Declared private like the copy constructor for the same reasons. It too
+ /// has no implementation.
+ ///
+ /// @param Unused - should be the object to copy.
+ LibraryHandle& operator=(const LibraryHandle&);
+
+ /// Back pointer to the collection object for the library
+ CalloutManager* callout_manager_;
+
+ /// Library index to which this handle applies. -1 indicates that it
+ /// applies to whatever index is current in the CalloutManager.
+ int index_;
+};
+
+} // namespace util
+} // namespace isc
+
+#endif // LIBRARY_HANDLE_H
diff --git a/src/lib/hooks/library_manager.cc b/src/lib/hooks/library_manager.cc
new file mode 100644
index 0000000..f425508
--- /dev/null
+++ b/src/lib/hooks/library_manager.cc
@@ -0,0 +1,292 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <hooks/hooks.h>
+#include <hooks/hooks_log.h>
+#include <hooks/callout_manager.h>
+#include <hooks/library_handle.h>
+#include <hooks/library_manager.h>
+#include <hooks/pointer_converter.h>
+#include <hooks/server_hooks.h>
+
+#include <string>
+#include <vector>
+
+#include <dlfcn.h>
+
+using namespace std;
+
+namespace isc {
+namespace hooks {
+
+
+// Open the library
+
+bool
+LibraryManager::openLibrary() {
+
+ // Open the library. We'll resolve names now, so that if there are any
+ // issues we don't bugcheck in the middle of apparently unrelated code.
+ dl_handle_ = dlopen(library_name_.c_str(), RTLD_NOW | RTLD_LOCAL);
+ if (dl_handle_ == NULL) {
+ LOG_ERROR(hooks_logger, HOOKS_OPEN_ERROR).arg(library_name_)
+ .arg(dlerror());
+ }
+
+ return (dl_handle_ != NULL);
+}
+
+// Close the library if not already open
+
+bool
+LibraryManager::closeLibrary() {
+
+ // Close the library if it is open. (If not, this is a no-op.)
+ int status = 0;
+ if (dl_handle_ != NULL) {
+ status = dlclose(dl_handle_);
+ dl_handle_ = NULL;
+ if (status != 0) {
+ LOG_ERROR(hooks_logger, HOOKS_CLOSE_ERROR).arg(library_name_)
+ .arg(dlerror());
+ }
+ }
+
+ return (status == 0);
+}
+
+// Check the version of the library
+
+bool
+LibraryManager::checkVersion() const {
+
+ // Get the pointer to the "version" function.
+ PointerConverter pc(dlsym(dl_handle_, VERSION_FUNCTION_NAME));
+ if (pc.versionPtr() != NULL) {
+ int version = BIND10_HOOKS_VERSION - 1; // This is an invalid value
+ try {
+ version = (*pc.versionPtr())();
+ } catch (...) {
+ LOG_ERROR(hooks_logger, HOOKS_VERSION_EXCEPTION).arg(library_name_);
+ return (false);
+ }
+
+ if (version == BIND10_HOOKS_VERSION) {
+ // All OK, version checks out
+ LOG_DEBUG(hooks_logger, HOOKS_DBG_CALLS, HOOKS_LIBRARY_VERSION)
+ .arg(library_name_).arg(version);
+ return (true);
+
+ } else {
+ LOG_ERROR(hooks_logger, HOOKS_INCORRECT_VERSION).arg(library_name_)
+ .arg(version).arg(BIND10_HOOKS_VERSION);
+ }
+ } else {
+ LOG_ERROR(hooks_logger, HOOKS_NO_VERSION).arg(library_name_);
+ }
+
+ return (false);
+}
+
+// Register the standard callouts
+
+void
+LibraryManager::registerStandardCallouts() {
+ // Set the library index for doing the registration. This is picked up
+ // when the library handle is created.
+ manager_->setLibraryIndex(index_);
+
+ // Iterate through the list of known hooks
+ vector<string> hook_names = ServerHooks::getServerHooks().getHookNames();
+ for (int i = 0; i < hook_names.size(); ++i) {
+
+ // Look up the symbol
+ void* dlsym_ptr = dlsym(dl_handle_, hook_names[i].c_str());
+ PointerConverter pc(dlsym_ptr);
+ if (pc.calloutPtr() != NULL) {
+ // Found a symbol, so register it.
+ manager_->getLibraryHandle().registerCallout(hook_names[i],
+ pc.calloutPtr());
+ LOG_DEBUG(hooks_logger, HOOKS_DBG_CALLS, HOOKS_STD_CALLOUT_REGISTERED)
+ .arg(library_name_).arg(hook_names[i]).arg(dlsym_ptr);
+
+ }
+ }
+}
+
+// Run the "load" function if present.
+
+bool
+LibraryManager::runLoad() {
+
+ // Get the pointer to the "load" function.
+ PointerConverter pc(dlsym(dl_handle_, LOAD_FUNCTION_NAME));
+ if (pc.loadPtr() != NULL) {
+
+ // Call the load() function with the library handle. We need to set
+ // the CalloutManager's index appropriately. We'll invalidate it
+ // afterwards.
+
+ int status = -1;
+ try {
+ manager_->setLibraryIndex(index_);
+ status = (*pc.loadPtr())(manager_->getLibraryHandle());
+ } catch (...) {
+ LOG_ERROR(hooks_logger, HOOKS_LOAD_EXCEPTION).arg(library_name_);
+ return (false);
+ }
+
+ if (status != 0) {
+ LOG_ERROR(hooks_logger, HOOKS_LOAD_ERROR).arg(library_name_)
+ .arg(status);
+ return (false);
+ } else {
+ LOG_DEBUG(hooks_logger, HOOKS_DBG_TRACE, HOOKS_LOAD_SUCCESS)
+ .arg(library_name_);
+ }
+
+ } else {
+ LOG_DEBUG(hooks_logger, HOOKS_DBG_TRACE, HOOKS_NO_LOAD)
+ .arg(library_name_);
+ }
+
+ return (true);
+}
+
+
+// Run the "unload" function if present.
+
+bool
+LibraryManager::runUnload() {
+
+ // Get the pointer to the "load" function.
+ PointerConverter pc(dlsym(dl_handle_, UNLOAD_FUNCTION_NAME));
+ if (pc.unloadPtr() != NULL) {
+
+ // Call the load() function with the library handle. We need to set
+ // the CalloutManager's index appropriately. We'll invalidate it
+ // afterwards.
+ int status = -1;
+ try {
+ status = (*pc.unloadPtr())();
+ } catch (...) {
+ // Exception generated. Note a warning as the unload will occur
+ // anyway.
+ LOG_WARN(hooks_logger, HOOKS_UNLOAD_EXCEPTION).arg(library_name_);
+ return (false);
+ }
+
+ if (status != 0) {
+ LOG_ERROR(hooks_logger, HOOKS_UNLOAD_ERROR).arg(library_name_)
+ .arg(status);
+ return (false);
+ } else {
+ LOG_DEBUG(hooks_logger, HOOKS_DBG_TRACE, HOOKS_UNLOAD_SUCCESS)
+ .arg(library_name_);
+ }
+ } else {
+ LOG_DEBUG(hooks_logger, HOOKS_DBG_TRACE, HOOKS_NO_UNLOAD)
+ .arg(library_name_);
+ }
+
+ return (true);
+}
+
+// The main library loading function.
+
+bool
+LibraryManager::loadLibrary() {
+ LOG_DEBUG(hooks_logger, HOOKS_DBG_TRACE, HOOKS_LIBRARY_LOADING)
+ .arg(library_name_);
+
+ // In the following, if a method such as openLibrary() fails, it will
+ // have issued an error message so there is no need to issue another one
+ // here.
+
+ // Open the library (which is a check that it exists and is accessible).
+ if (openLibrary()) {
+
+ // Library opened OK, see if a version function is present and if so,
+ // check what value it returns.
+ if (checkVersion()) {
+
+ // Version OK, so now register the standard callouts and call the
+ // library's load() function if present.
+ registerStandardCallouts();
+ if (runLoad()) {
+
+ // Success - the library has been successfully loaded.
+ LOG_INFO(hooks_logger, HOOKS_LIBRARY_LOADED).arg(library_name_);
+ return (true);
+
+ } else {
+
+ // The load function failed, so back out. We can't just close
+ // the library as (a) we need to call the library's "unload"
+ // function (if present) in case "load" allocated resources that
+ // need to be freed and (b) we need to remove any callouts that
+ // have been installed.
+ static_cast<void>(unloadLibrary());
+ }
+ }
+
+ // Either the version check or call to load() failed, so close the
+ // library and free up resources. Ignore the status return here - we
+ // already know there's an error and will have output a message.
+ static_cast<void>(closeLibrary());
+ }
+
+ return (false);
+}
+
+// The library unloading function. Call the unload() function (if present),
+// remove callouts from the callout manager, then close the library.
+
+bool
+LibraryManager::unloadLibrary() {
+ LOG_DEBUG(hooks_logger, HOOKS_DBG_TRACE, HOOKS_LIBRARY_UNLOADING)
+ .arg(library_name_);
+
+ // Call the unload() function if present. Note that this is done first -
+ // operations take place in the reverse order to which they were done when
+ // the library was loaded.
+ bool result = runUnload();
+
+ // Regardless of status, remove all callouts associated with this library
+ // on all hooks.
+ vector<string> hooks = ServerHooks::getServerHooks().getHookNames();
+ manager_->setLibraryIndex(index_);
+ for (int i = 0; i < hooks.size(); ++i) {
+ bool removed = manager_->deregisterAllCallouts(hooks[i]);
+ if (removed) {
+ LOG_DEBUG(hooks_logger, HOOKS_DBG_CALLS, HOOKS_CALLOUTS_REMOVED)
+ .arg(hooks[i]).arg(library_name_);
+ }
+ }
+
+ // ... and close the library.
+ result = closeLibrary() && result;
+ if (result) {
+
+ // Issue the informational message only if the library was unloaded
+ // with no problems. If there was an issue, an error message would
+ // have been issued.
+ LOG_INFO(hooks_logger, HOOKS_LIBRARY_UNLOADED).arg(library_name_);
+ }
+
+ return (result);
+}
+
+} // namespace hooks
+} // namespace isc
diff --git a/src/lib/hooks/library_manager.h b/src/lib/hooks/library_manager.h
new file mode 100644
index 0000000..56c770b
--- /dev/null
+++ b/src/lib/hooks/library_manager.h
@@ -0,0 +1,190 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef LIBRARY_MANAGER_H
+#define LIBRARY_MANAGER_H
+
+#include <boost/shared_ptr.hpp>
+
+#include <string>
+
+namespace isc {
+namespace hooks {
+
+class CalloutManager;
+class LibraryHandle;
+class LibraryManager;
+
+/// @brief Library manager
+///
+/// This class handles the loading and unloading of a specific library.
+///
+/// On loading, it opens the library using dlopen and checks the version (set
+/// with the "version" method. If all is OK, it iterates through the list of
+/// known hooks and locates their symbols, registering each callout as it does
+/// so. Finally it locates the "load" function (if present) and calls it.
+///
+/// On unload, it calls the "unload" method if present, clears the callouts on
+/// all hooks, and closes the library.
+///
+/// @note Caution needs to be exercised when using the unload method. During
+/// normal use, data will pass between the server and the library. In
+/// this process, the library may allocate memory and pass it back to the
+/// server. This could happen by the server setting arguments or context
+/// in the CalloutHandle object, or by the library modifying the content
+/// of pointed-to data. If the library is unloaded, this memory may lie
+/// in the virtual address space deleted in that process. (The word "may"
+/// is used, as this could be operating-system specific.) Should this
+/// happen, any reference to the memory will cause a segmentation fault.
+/// This can occur in a quite obscure place, for example in the middle of
+/// a destructor of an STL class when it is deleting memory allocated
+/// when the data structure was extended by a function in the library.
+///
+/// @note The only safe way to run the "unload" function is to ensure that all
+/// possible references to it are removed first. This means that all
+/// CalloutHandles must be destroyed, as must any data items that were
+/// passed to the callouts. In practice, it could mean that a server
+/// suspends processing of new requests until all existing ones have
+/// been serviced and all packet/context structures destroyed before
+/// reloading the libraries.
+
+class LibraryManager {
+public:
+ /// @brief Constructor
+ ///
+ /// Stores the library name. The actual loading is done in loadLibrary().
+ ///
+ /// @param name Name of the library to load. This should be an absolute
+ /// path name.
+ /// @param index Index of this library
+ /// @param manager CalloutManager object
+ LibraryManager(const std::string& name, int index,
+ const boost::shared_ptr<CalloutManager>& manager)
+ : dl_handle_(NULL), index_(index), manager_(manager),
+ library_name_(name)
+ {}
+
+ /// @brief Destructor
+ ///
+ /// If the library is open, closes it. This is principally a safety
+ /// feature to ensure closure in the case of an exception destroying this
+ /// object. However, see the caveat in the class header about when it is
+ /// safe to unload libraries.
+ ~LibraryManager() {
+ static_cast<void>(unloadLibrary());
+ }
+
+ /// @brief Loads a library
+ ///
+ /// Open the library and check the version. If all is OK, load all standard
+ /// symbols then call "load" if present.
+ ///
+ /// @return true if the library loaded successfully, false otherwise. In the
+ /// latter case, the library will be unloaded if possible.
+ bool loadLibrary();
+
+ /// @brief Unloads a library
+ ///
+ /// Calls the libraries "unload" function if present, the closes the
+ /// library.
+ ///
+ /// However, see the caveat in the class header about when it is safe to
+ /// unload libraries.
+ ///
+ /// @return true if the library unloaded successfully, false if an error
+ /// occurred in the process (most likely the unload() function
+ /// (if present) returned an error). Even if an error did occur,
+ /// the library is closed if possible.
+ bool unloadLibrary();
+
+ /// @brief Return library name
+ ///
+ /// @return Name of this library
+ std::string getName() const {
+ return (library_name_);
+ }
+
+protected:
+ // The following methods are protected as they are accessed in testing.
+
+ /// @brief Open library
+ ///
+ /// Opens the library associated with this LibraryManager. A message is
+ /// logged on an error.
+ ///
+ /// @return true if the library opened successfully, false otherwise.
+ bool openLibrary();
+
+ /// @brief Close library
+ ///
+ /// Closes the library associated with this LibraryManager. A message is
+ /// logged on an error.
+ ///
+ /// @return true if the library closed successfully, false otherwise. "true"
+ /// is also returned if the library were already closed when this
+ /// method was called.
+ bool closeLibrary();
+
+ /// @brief Check library version
+ ///
+ /// With the library open, accesses the "version()" function and, if
+ /// present, checks the returned value against the hooks version symbol
+ /// for the currently running BIND 10. The "version()" function is
+ /// mandatory and must be present (and return the correct value) for the
+ /// library to load.
+ ///
+ /// If there is no version() function, or if there is a mismatch in
+ /// version number, a message logged.
+ ///
+ /// @return bool true if the check succeeded
+ bool checkVersion() const;
+
+ /// @brief Register standard callouts
+ ///
+ /// Loops through the list of hook names and searches the library for
+ /// functions with those names. Any that are found are registered as
+ /// callouts for that hook.
+ void registerStandardCallouts();
+
+ /// @brief Run the load function if present
+ ///
+ /// Searches for the "load" framework function and, if present, runs it.
+ ///
+ /// @return bool true if not found or found and run successfully,
+ /// false on an error. In this case, an error message will
+ /// have been output.
+ bool runLoad();
+
+ /// @brief Run the unload function if present
+ ///
+ /// Searches for the "unload" framework function and, if present, runs it.
+ ///
+ /// @return bool true if not found or found and run successfully,
+ /// false on an error. In this case, an error message will
+ /// have been output.
+ bool runUnload();
+
+private:
+ void* dl_handle_; ///< Handle returned by dlopen
+ int index_; ///< Index associated with this library
+ boost::shared_ptr<CalloutManager> manager_;
+ ///< Callout manager for registration
+ std::string library_name_; ///< Name of the library
+
+};
+
+} // namespace hooks
+} // namespace isc
+
+#endif // LIBRARY_MANAGER_H
diff --git a/src/lib/hooks/library_manager_collection.cc b/src/lib/hooks/library_manager_collection.cc
new file mode 100644
index 0000000..d3f7f34
--- /dev/null
+++ b/src/lib/hooks/library_manager_collection.cc
@@ -0,0 +1,114 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <hooks/callout_manager.h>
+#include <hooks/library_manager.h>
+#include <hooks/library_manager_collection.h>
+
+namespace isc {
+namespace hooks {
+
+// Return callout manager for the loaded libraries. This call is only valid
+// after one has been created for the loaded libraries (which includes the
+// case of no loaded libraries).
+//
+// Note that there is no real connection between the callout manager and the
+// libraries, other than it knows the number of libraries so can do sanity
+// checks on values passed to it. However, this may change in the future,
+// so the hooks framework is written such that a callout manager is used only
+// with the LibraryManagerCollection that created it. It is also the reason
+// why each LibraryManager contains a pointer to this CalloutManager.
+
+boost::shared_ptr<CalloutManager>
+LibraryManagerCollection::getCalloutManager() const {
+
+ // Only return a pointer if we have a CalloutManager created.
+ if (! callout_manager_) {
+ isc_throw(LoadLibrariesNotCalled, "must load hooks libraries before "
+ "attempting to retrieve a CalloutManager for them");
+ }
+
+ return (callout_manager_);
+}
+
+// Load a set of libraries
+
+bool
+LibraryManagerCollection::loadLibraries() {
+
+ // Unload libraries if any are loaded.
+ static_cast<void>(unloadLibraries());
+
+ // Create the callout manager. A pointer to this is maintained by
+ // each library. Note that the callout manager does not hold any memory
+ // allocated by a library: although a library registers a callout (and so
+ // causes the creation of an entry in the CalloutManager's callout list),
+ // that creation is done by the CalloutManager itself. The CalloutManager
+ // is created within the server.
+ //
+ // The upshot of this is that it is therefore safe for the CalloutManager
+ // to be deleted after all associated libraries are deleted, hence this
+ // link (LibraryManager -> CalloutManager) is safe.
+ callout_manager_.reset(new CalloutManager(library_names_.size()));
+
+ // Now iterate through the libraries are load them one by one. We'll
+ for (int i = 0; i < library_names_.size(); ++i) {
+ // Create a pointer to the new library manager. The index of this
+ // library is determined by the number of library managers currently
+ // loaded: note that the library indexes run from 1 to (number of loaded
+ // libraries).
+ boost::shared_ptr<LibraryManager> manager(
+ new LibraryManager(library_names_[i], lib_managers_.size() + 1,
+ callout_manager_));
+
+ // Load the library. On success, add it to the list of loaded
+ // libraries. On failure, an error will have been logged and the
+ // library closed.
+ if (manager->loadLibrary()) {
+ lib_managers_.push_back(manager);
+ }
+ }
+
+ // Update the CalloutManager's idea of the number of libraries it is
+ // handling.
+ callout_manager_->setNumLibraries(lib_managers_.size());
+
+ // Get an indication of whether all libraries loaded successfully.
+ bool status = (library_names_.size() == lib_managers_.size());
+
+ // Don't need the library names any more, so free up the space.
+ library_names_.clear();
+
+ return (status);
+}
+
+// Unload the libraries.
+
+void
+LibraryManagerCollection::unloadLibraries() {
+
+ // Delete the library managers in the reverse order to which they were
+ // created, then clear the library manager vector.
+ for (int i = lib_managers_.size() - 1; i >= 0; --i) {
+ lib_managers_[i].reset();
+ }
+ lib_managers_.clear();
+
+ // Get rid of the callout manager. (The other member, the list of library
+ // names, was cleared when the libraries were loaded.)
+ callout_manager_.reset();
+}
+
+} // namespace hooks
+} // namespace isc
diff --git a/src/lib/hooks/library_manager_collection.h b/src/lib/hooks/library_manager_collection.h
new file mode 100644
index 0000000..c0f6def
--- /dev/null
+++ b/src/lib/hooks/library_manager_collection.h
@@ -0,0 +1,133 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef LIBRARY_MANAGER_COLLECTION_H
+#define LIBRARY_MANAGER_COLLECTION_H
+
+#include <exceptions/exceptions.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <vector>
+
+namespace isc {
+namespace hooks {
+
+/// @brief LoadLibraries not called
+///
+/// Thrown if an attempt is made get a CalloutManager before the libraries
+/// have been loaded.
+class LoadLibrariesNotCalled : public Exception {
+public:
+ LoadLibrariesNotCalled(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+
+// Forward declarations
+class CalloutManager;
+class LibraryManager;
+
+/// @brief Library manager collection
+///
+/// The LibraryManagerCollection class, as the name implies, is responsible for
+/// managing the collection of LibraryManager objects that describe the loaded
+/// libraries. As such, it converts a single operation (e.g load libraries)
+/// into multiple operations, one per library. However, the class does more
+/// than that - it provides a single object with which to manage lifetimes.
+///
+/// As described in the LibraryManager documentation, a CalloutHandle may end
+/// up with pointers to memory within the address space of a loaded library.
+/// If the library is unloaded before this address space is deleted, the
+/// deletion of the CalloutHandle may attempt to free memory into the newly-
+/// unmapped address space and cause a segmentation fault.
+///
+/// To prevent this, each CalloutHandle maintains a shared pointer to the
+/// LibraryManagerCollection current when it was created. In addition, the
+/// containing HooksManager object also maintains a shared pointer to it. A
+/// a LibraryManagerCollection is never explicitly deleted: when a new set
+/// of libraries is loaded, the HooksManager clears its pointer to the
+/// collection. The LibraryManagerCollection is only destroyed when all
+/// CallHandle objects referencing it are destroyed.
+///
+/// Note that this does not completely solve the problem - a hook function may
+/// have modified a packet being processed by the server and that packet may
+/// hold a pointer to memory in the library's virtual address space. To avoid
+/// a segmentation fault, that packet needs to free the memory before the
+/// LibraryManagerCollection is destroyed and this places demands on the server
+/// code. However, the link with the CalloutHandle does at least mean that
+/// authors of server code do not need to be so careful about when they destroy
+/// CalloutHandles.
+
+class LibraryManagerCollection {
+public:
+ /// @brief Constructor
+ ///
+ /// @param List of libraries that this collection will manage. The order
+ /// of the libraries is important.
+ LibraryManagerCollection(const std::vector<std::string>& libraries)
+ : library_names_(libraries)
+ {}
+
+ /// @brief Destructor
+ ///
+ /// Unloads all loaded libraries.
+ ~LibraryManagerCollection() {
+ static_cast<void>(unloadLibraries());
+ }
+
+ /// @brief Load libraries
+ ///
+ /// Loads the libraries. This creates the LibraryManager associated with
+ /// each library and calls its loadLibrary() method. If a library fails
+ /// to load, the fact is noted but attempts are made to load the remaining
+ /// libraries.
+ bool loadLibraries();
+
+ /// @brief Get callout manager
+ ///
+ /// Returns a callout manager that can be used with this set of loaded
+ /// libraries (even if the number of loaded libraries is zero). This
+ /// method may only be caslled after loadLibraries() has been called.
+ ///
+ /// @return Pointer to a callout manager for this set of libraries.
+ ///
+ /// @throw LoadLibrariesNotCalled Thrown if this method is called between
+ /// construction and the time loadLibraries() is called.
+ boost::shared_ptr<CalloutManager> getCalloutManager() const;
+
+protected:
+ /// @brief Unload libraries
+ ///
+ /// Unloads and closes all loaded libraries. They are unloaded in the
+ /// reverse order to the order in which they were loaded.
+ void unloadLibraries();
+
+private:
+
+ /// Vector of library names
+ std::vector<std::string> library_names_;
+
+ /// Vector of library managers
+ std::vector<boost::shared_ptr<LibraryManager> > lib_managers_;
+
+ /// Callout manager to be associated with the libraries
+ boost::shared_ptr<CalloutManager> callout_manager_;
+};
+
+} // namespace hooks
+} // namespace isc
+
+
+#endif // LIBRARY_MANAGER_COLLECTION_H
diff --git a/src/lib/hooks/pointer_converter.h b/src/lib/hooks/pointer_converter.h
new file mode 100644
index 0000000..1fe15ac
--- /dev/null
+++ b/src/lib/hooks/pointer_converter.h
@@ -0,0 +1,121 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef POINTER_CONVERTER_H
+#define POINTER_CONVERTER_H
+
+#include <hooks/hooks.h>
+
+namespace isc {
+namespace hooks {
+
+/// @brief Local class for conversion of void pointers to function pointers
+///
+/// Converting between void* and function pointers in C++ is fraught with
+/// difficulty and pitfalls, e.g. see
+/// https://groups.google.com/forum/?hl=en&fromgroups#!topic/comp.lang.c++/37o0l8rtEE0
+///
+/// The method given in that article - convert using a union is used here. A
+/// union is declared (and zeroed) and the appropriate member extracted when
+/// needed.
+
+class PointerConverter {
+public:
+ /// @brief Constructor
+ ///
+ /// Zeroes the union and stores the void* pointer we wish to convert (the
+ /// one returned by dlsym).
+ ///
+ /// @param dlsym_ptr void* pointer returned by call to dlsym()
+ PointerConverter(void* dlsym_ptr) {
+ memset(&pointers_, 0, sizeof(pointers_));
+ pointers_.dlsym_ptr = dlsym_ptr;
+ }
+
+ /// @brief Constructor
+ ///
+ /// Zeroes the union and stores the CalloutPtr pointer we wish to convert.
+ /// This constructor is used in debug messages; output of a pointer to
+ /// an object (including to a function) is, on some compilers, printed as
+ /// "1".
+ ///
+ /// @param callout_ptr Pointer to callout function
+ PointerConverter(CalloutPtr callout_ptr) {
+ memset(&pointers_, 0, sizeof(pointers_));
+ pointers_.callout_ptr = callout_ptr;
+ }
+
+ /// @name Pointer accessor functions
+ ///
+ /// It is up to the caller to ensure that the correct member is called so
+ /// that the correct type of pointer is returned.
+ ///
+ ///@{
+
+ /// @brief Return pointer returned by dlsym call
+ ///
+ /// @return void* pointer returned by the call to dlsym(). This can be
+ /// used in statements that print the hexadecimal value of the
+ /// symbol.
+ void* dlsymPtr() const {
+ return (pointers_.dlsym_ptr);
+ }
+
+ /// @brief Return pointer to callout function
+ ///
+ /// @return Pointer to the callout function
+ CalloutPtr calloutPtr() const {
+ return (pointers_.callout_ptr);
+ }
+
+ /// @brief Return pointer to load function
+ ///
+ /// @return Pointer to the load function
+ load_function_ptr loadPtr() const {
+ return (pointers_.load_ptr);
+ }
+
+ /// @brief Return pointer to unload function
+ ///
+ /// @return Pointer to the unload function
+ unload_function_ptr unloadPtr() const {
+ return (pointers_.unload_ptr);
+ }
+
+ /// @brief Return pointer to version function
+ ///
+ /// @return Pointer to the version function
+ version_function_ptr versionPtr() const {
+ return (pointers_.version_ptr);
+ }
+
+ ///@}
+
+private:
+
+ /// @brief Union linking void* and pointers to functions.
+ union {
+ void* dlsym_ptr; // void* returned by dlsym
+ CalloutPtr callout_ptr; // Pointer to callout
+ load_function_ptr load_ptr; // Pointer to load function
+ unload_function_ptr unload_ptr; // Pointer to unload function
+ version_function_ptr version_ptr; // Pointer to version function
+ } pointers_;
+};
+
+} // namespace hooks
+} // namespace isc
+
+
+#endif // POINTER_CONVERTER_H
diff --git a/src/lib/hooks/server_hooks.cc b/src/lib/hooks/server_hooks.cc
new file mode 100644
index 0000000..1a0b157
--- /dev/null
+++ b/src/lib/hooks/server_hooks.cc
@@ -0,0 +1,144 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <exceptions/exceptions.h>
+#include <hooks/hooks_log.h>
+#include <hooks/server_hooks.h>
+
+#include <utility>
+#include <vector>
+
+using namespace std;
+using namespace isc;
+
+namespace isc {
+namespace hooks {
+
+// Constructor - register the pre-defined hooks and check that the indexes
+// assigned to them are as expected.
+
+ServerHooks::ServerHooks() {
+ reset();
+}
+
+// Register a hook. The index assigned to the hook is the current number
+// of entries in the collection, so ensuring that hook indexes are unique
+// and non-negative.
+
+int
+ServerHooks::registerHook(const string& name) {
+
+ // Determine index for the new element and insert.
+ int index = hooks_.size();
+ pair<HookCollection::iterator, bool> result =
+ hooks_.insert(make_pair(name, index));
+
+ if (!result.second) {
+ // New element was not inserted because an element with the same name
+ // already existed.
+ isc_throw(DuplicateHook, "hook with name " << name <<
+ " is already registered");
+ }
+
+ // Element was inserted, so add to the inverse hooks collection.
+ inverse_hooks_[index] = name;
+
+ // Log it if debug is enabled
+ LOG_DEBUG(hooks_logger, HOOKS_DBG_TRACE, HOOKS_HOOK_REGISTERED).arg(name);
+
+ // ... and return numeric index.
+ return (index);
+}
+
+// Reset ServerHooks object to initial state.
+
+void
+ServerHooks::reset() {
+
+ // Clear out the name->index and index->name maps.
+ hooks_.clear();
+ inverse_hooks_.clear();
+
+ // Register the pre-defined hooks.
+ int create = registerHook("context_create");
+ int destroy = registerHook("context_destroy");
+
+ // Check registration went as expected.
+ if ((create != CONTEXT_CREATE) || (destroy != CONTEXT_DESTROY)) {
+ isc_throw(Unexpected, "pre-defined hook indexes are not as expected. "
+ "context_create: expected = " << CONTEXT_CREATE <<
+ ", actual = " << create <<
+ ". context_destroy: expected = " << CONTEXT_DESTROY <<
+ ", actual = " << destroy);
+ }
+
+ // Log a warning - although this is done during testing, it should never be
+ // seen in a production system.
+ LOG_WARN(hooks_logger, HOOKS_HOOK_LIST_RESET);
+}
+
+// Find the name associated with a hook index.
+
+std::string
+ServerHooks::getName(int index) const {
+
+ // Get iterator to matching element.
+ InverseHookCollection::const_iterator i = inverse_hooks_.find(index);
+ if (i == inverse_hooks_.end()) {
+ isc_throw(NoSuchHook, "hook index " << index << " is not recognised");
+ }
+
+ return (i->second);
+}
+
+// Find the index associated with a hook name.
+
+int
+ServerHooks::getIndex(const string& name) const {
+
+ // Get iterator to matching element.
+ HookCollection::const_iterator i = hooks_.find(name);
+ if (i == hooks_.end()) {
+ isc_throw(NoSuchHook, "hook name " << name << " is not recognised");
+ }
+
+ return (i->second);
+}
+
+// Return vector of hook names. The names are not sorted - it is up to the
+// caller to perform sorting if required.
+
+vector<string>
+ServerHooks::getHookNames() const {
+
+ vector<string> names;
+ HookCollection::const_iterator i;
+ for (i = hooks_.begin(); i != hooks_.end(); ++i) {
+ names.push_back(i->first);
+ }
+
+ return (names);
+}
+
+// Return global ServerHooks object
+
+ServerHooks&
+ServerHooks::getServerHooks() {
+ static ServerHooks hooks;
+ return (hooks);
+}
+
+
+} // namespace util
+} // namespace isc
diff --git a/src/lib/hooks/server_hooks.h b/src/lib/hooks/server_hooks.h
new file mode 100644
index 0000000..f075cb8
--- /dev/null
+++ b/src/lib/hooks/server_hooks.h
@@ -0,0 +1,173 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef SERVER_HOOKS_H
+#define SERVER_HOOKS_H
+
+#include <exceptions/exceptions.h>
+
+#include <boost/noncopyable.hpp>
+
+#include <map>
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace hooks {
+
+/// @brief Duplicate hook
+///
+/// Thrown if an attempt is made to register a hook with the same name as a
+/// previously-registered hook.
+class DuplicateHook : public Exception {
+public:
+ DuplicateHook(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// @brief Invalid hook
+///
+/// Thrown if an attempt is made to get the index for an invalid hook.
+class NoSuchHook : public Exception {
+public:
+ NoSuchHook(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+
+/// @brief Server hook collection
+///
+/// This class is used by the server-side code to register hooks - points in the
+/// server processing at which libraries can register functions (callouts) that
+/// the server will call. These functions can modify data and so affect the
+/// processing of the server.
+///
+/// The ServerHooks class is little more than a wrapper around the std::map
+/// class. It stores a hook, assigning to it a unique index number. This
+/// number is then used by the server code to identify the hook being called.
+/// (Although it would be feasible to use a name as an index, using an integer
+/// will speed up the time taken to locate the callouts, which may make a
+/// difference in a frequently-executed piece of code.)
+///
+/// ServerHooks is a singleton object and is only accessible by the static
+/// method getServerHooks().
+
+class ServerHooks : public boost::noncopyable {
+public:
+
+ /// Index numbers for pre-defined hooks.
+ static const int CONTEXT_CREATE = 0;
+ static const int CONTEXT_DESTROY = 1;
+
+ /// @brief Reset to Initial State
+ ///
+ /// Resets the collection of hooks to the initial state, with just the
+ /// context_create and context_destroy hooks set. This used during
+ /// construction. It is also used during testing to reset the global
+ /// ServerHooks object.
+ ///
+ /// @throws isc::Unexpected if the registration of the pre-defined hooks
+ /// fails in some way.
+ void reset();
+
+ /// @brief Register a hook
+ ///
+ /// Registers a hook and returns the hook index.
+ ///
+ /// @param name Name of the hook
+ ///
+ /// @return Index of the hook, to be used in subsequent hook-related calls.
+ /// This will be greater than or equal to zero (so allowing a
+ /// negative value to indicate an invalid index).
+ ///
+ /// @throws DuplicateHook A hook with the same name has already been
+ /// registered.
+ int registerHook(const std::string& name);
+
+ /// @brief Get hook name
+ ///
+ /// Returns the name of a hook given the index. This is most likely to be
+ /// used in log messages.
+ ///
+ /// @param index Index of the hoold
+ ///
+ /// @return Name of the hook.
+ ///
+ /// @throw NoSuchHook if the hook index is invalid.
+ std::string getName(int index) const;
+
+ /// @brief Get hook index
+ ///
+ /// Returns the index of a hook.
+ ///
+ /// @param name Name of the hook
+ ///
+ /// @return Index of the hook, to be used in subsequent calls.
+ ///
+ /// @throw NoSuchHook if the hook name is unknown to the caller.
+ int getIndex(const std::string& name) const;
+
+ /// @brief Return number of hooks
+ ///
+ /// Returns the total number of hooks registered.
+ ///
+ /// @return Number of hooks registered.
+ int getCount() const {
+ return (hooks_.size());
+ }
+
+ /// @brief Get hook names
+ ///
+ /// Return list of hooks registered in the object.
+ ///
+ /// @return Vector of strings holding hook names.
+ std::vector<std::string> getHookNames() const;
+
+ /// @brief Return ServerHooks object
+ ///
+ /// Returns the global ServerHooks object.
+ ///
+ /// @return Reference to the global ServerHooks object.
+ static ServerHooks& getServerHooks();
+
+private:
+ /// @brief Constructor
+ ///
+ /// This pre-registers two hooks, context_create and context_destroy, which
+ /// are called by the server before processing a packet and after processing
+ /// for the packet has completed. They allow the server code to allocate
+ /// and destroy per-packet context.
+ ///
+ /// Constructor is declared private to enforce the singleton nature of
+ /// the object. A reference to the singleton is obtainable through the
+ /// getServerHooks() static method.
+ ///
+ /// @throws isc::Unexpected if the registration of the pre-defined hooks
+ /// fails in some way.
+ ServerHooks();
+
+ /// Useful typedefs.
+ typedef std::map<std::string, int> HookCollection;
+ typedef std::map<int, std::string> InverseHookCollection;
+
+ /// Two maps, one for name->index, the other for index->name. (This is
+ /// simpler than using a multi-indexed container.)
+ HookCollection hooks_; ///< Hook name/index collection
+ InverseHookCollection inverse_hooks_; ///< Hook index/name collection
+};
+
+} // namespace util
+} // namespace isc
+
+#endif // SERVER_HOOKS_H
diff --git a/src/lib/hooks/tests/Makefile.am b/src/lib/hooks/tests/Makefile.am
new file mode 100644
index 0000000..37fe238
--- /dev/null
+++ b/src/lib/hooks/tests/Makefile.am
@@ -0,0 +1,104 @@
+SUBDIRS = .
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES) $(MULTITHREADING_FLAG)
+
+# Some versions of GCC warn about some versions of Boost regarding
+# missing initializer for members in its posix_time.
+# https://svn.boost.org/trac/boost/ticket/3477
+# But older GCC compilers don't have the flag.
+AM_CXXFLAGS = $(WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG)
+
+if USE_CLANGPP
+# see ../Makefile.am
+AM_CXXFLAGS += -Wno-unused-parameter
+endif
+
+# Files to clean include the file created by testing.
+CLEANFILES = *.gcno *.gcda $(builddir)/marker_file.dat
+
+TESTS_ENVIRONMENT = \
+ $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+
+TESTS =
+if HAVE_GTEST
+# Build shared libraries for testing.
+lib_LTLIBRARIES = libnvl.la libivl.la libfxl.la libbcl.la liblcl.la liblecl.la \
+ libucl.la libfcl.la
+
+# No version function
+libnvl_la_SOURCES = no_version_library.cc
+libnvl_la_CXXFLAGS = $(AM_CXXFLAGS)
+libnvl_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
+
+# Incorrect version function
+libivl_la_SOURCES = incorrect_version_library.cc
+libivl_la_CXXFLAGS = $(AM_CXXFLAGS)
+libivl_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
+
+# All framework functions throw an exception
+libfxl_la_SOURCES = framework_exception_library.cc
+libfxl_la_CXXFLAGS = $(AM_CXXFLAGS)
+libfxl_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
+
+# The basic callout library - contains standard callouts
+libbcl_la_SOURCES = basic_callout_library.cc
+libbcl_la_CXXFLAGS = $(AM_CXXFLAGS)
+libbcl_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
+
+# The load callout library - contains a load function
+liblcl_la_SOURCES = load_callout_library.cc
+liblcl_la_CXXFLAGS = $(AM_CXXFLAGS)
+liblcl_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
+
+# The load error callout library - contains a load function that returns
+# an error.
+liblecl_la_SOURCES = load_error_callout_library.cc
+liblecl_la_CXXFLAGS = $(AM_CXXFLAGS)
+liblecl_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
+
+# The unload callout library - contains an unload function that
+# creates a marker file.
+libucl_la_SOURCES = unload_callout_library.cc
+libucl_la_CXXFLAGS = $(AM_CXXFLAGS)
+libucl_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
+
+# The full callout library - contains all three framework functions.
+libfcl_la_SOURCES = full_callout_library.cc
+libfcl_la_CXXFLAGS = $(AM_CXXFLAGS)
+libfcl_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
+
+TESTS += run_unittests
+run_unittests_SOURCES = run_unittests.cc
+run_unittests_SOURCES += callout_handle_unittest.cc
+run_unittests_SOURCES += callout_manager_unittest.cc
+run_unittests_SOURCES += common_test_class.h
+run_unittests_SOURCES += handles_unittest.cc
+run_unittests_SOURCES += hooks_manager_unittest.cc
+run_unittests_SOURCES += library_manager_collection_unittest.cc
+run_unittests_SOURCES += library_manager_unittest.cc
+run_unittests_SOURCES += server_hooks_unittest.cc
+
+nodist_run_unittests_SOURCES = marker_file.h
+nodist_run_unittests_SOURCES += test_libraries.h
+
+run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
+if USE_STATIC_LINK
+# If specified, only link unit tests static - the test libraries must be
+# build as shared libraries.
+run_unittests_LDFLAGS += -static
+endif
+
+run_unittests_LDADD = $(AM_LDADD) $(GTEST_LDADD)
+
+run_unittests_LDADD += $(top_builddir)/src/lib/hooks/libb10-hooks.la
+run_unittests_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
+run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
+run_unittests_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
+run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
+endif
+
+noinst_PROGRAMS = $(TESTS)
+
+EXTRA_DIST = marker_file.h.in test_libraries.h.in
diff --git a/src/lib/hooks/tests/basic_callout_library.cc b/src/lib/hooks/tests/basic_callout_library.cc
new file mode 100644
index 0000000..253de80
--- /dev/null
+++ b/src/lib/hooks/tests/basic_callout_library.cc
@@ -0,0 +1,115 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/// @file
+/// @brief Basic callout library
+///
+/// This is source of a test library for various test (LibraryManager and
+/// HooksManager). The characteristics of the library produced from this
+/// file are:
+///
+/// - Only the "version" framework function is supplied.
+///
+/// - A context_create callout is supplied.
+///
+/// - Three "standard" callouts are supplied corresponding to the hooks
+/// "hookpt_one", "hookpt_two", "hookpt_three". All do some trivial calculations
+/// on the arguments supplied to it and the context variables, returning
+/// intermediate results through the "result" argument. The result of
+/// executing all four callouts in order is:
+///
+/// @f[ (10 + data_1) * data_2 - data_3 @f]
+///
+/// ...where data_1, data_2 and data_3 are the values passed in arguments of
+/// the same name to the three callouts (data_1 passed to hookpt_one, data_2 to
+/// hookpt_two etc.) and the result is returned in the argument "result".
+
+#include <hooks/hooks.h>
+#include <fstream>
+
+using namespace isc::hooks;
+using namespace std;
+
+extern "C" {
+
+// Callouts. All return their result through the "result" argument.
+
+int
+context_create(CalloutHandle& handle) {
+ handle.setContext("result", static_cast<int>(10));
+ handle.setArgument("result", static_cast<int>(10));
+ return (0);
+}
+
+// First callout adds the passed "data_1" argument to the initialized context
+// value of 10. (Note that the value set by context_create is accessed through
+// context and not the argument, so checking that context is correctly passed
+// between callouts in the same library.)
+
+int
+hookpt_one(CalloutHandle& handle) {
+ int data;
+ handle.getArgument("data_1", data);
+
+ int result;
+ handle.getArgument("result", result);
+
+ result += data;
+ handle.setArgument("result", result);
+
+ return (0);
+}
+
+// Second callout multiplies the current context value by the "data_2"
+// argument.
+
+int
+hookpt_two(CalloutHandle& handle) {
+ int data;
+ handle.getArgument("data_2", data);
+
+ int result;
+ handle.getArgument("result", result);
+
+ result *= data;
+ handle.setArgument("result", result);
+
+ return (0);
+}
+
+// Final callout subtracts the result in "data_3".
+
+int
+hookpt_three(CalloutHandle& handle) {
+ int data;
+ handle.getArgument("data_3", data);
+
+ int result;
+ handle.getArgument("result", result);
+
+ result -= data;
+ handle.setArgument("result", result);
+
+ return (0);
+}
+
+// Framework functions. Only version() is supplied here.
+
+int
+version() {
+ return (BIND10_HOOKS_VERSION);
+}
+
+};
+
diff --git a/src/lib/hooks/tests/callout_handle_unittest.cc b/src/lib/hooks/tests/callout_handle_unittest.cc
new file mode 100644
index 0000000..b24a4cf
--- /dev/null
+++ b/src/lib/hooks/tests/callout_handle_unittest.cc
@@ -0,0 +1,329 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <hooks/callout_handle.h>
+#include <hooks/callout_manager.h>
+#include <hooks/library_handle.h>
+#include <hooks/server_hooks.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <gtest/gtest.h>
+
+using namespace isc::hooks;
+using namespace std;
+
+namespace {
+
+/// @file
+/// @brief Holds the CalloutHandle argument tests
+///
+/// Additional testing of the CalloutHandle - together with the interaction
+/// of the LibraryHandle - is done in the handles_unittests set of tests.
+
+class CalloutHandleTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor
+ ///
+ /// Sets up a callout manager to be referenced by the CalloutHandle in
+ /// these tests. (The "4" for the number of libraries in the
+ /// CalloutManager is arbitrary - it is not used in these tests.)
+ CalloutHandleTest() : manager_(new CalloutManager(4))
+ {}
+
+ /// Obtain hook manager
+ boost::shared_ptr<CalloutManager>& getCalloutManager() {
+ return (manager_);
+ }
+
+private:
+ /// Callout manager accessed by this CalloutHandle.
+ boost::shared_ptr<CalloutManager> manager_;
+};
+
+// *** Argument Tests ***
+//
+// The first set of tests check that the CalloutHandle can store and retrieve
+// arguments. These are very similar to the LibraryHandle context tests.
+
+// Test that we can store multiple values of the same type and that they
+// are distinct.
+
+TEST_F(CalloutHandleTest, ArgumentDistinctSimpleType) {
+ CalloutHandle handle(getCalloutManager());
+
+ // Store and retrieve an int (random value).
+ int a = 42;
+ handle.setArgument("integer1", a);
+ EXPECT_EQ(42, a);
+
+ int b = 0;
+ handle.getArgument("integer1", b);
+ EXPECT_EQ(42, b);
+
+ // Add another integer (another random value).
+ int c = 142;
+ handle.setArgument("integer2", c);
+ EXPECT_EQ(142, c);
+
+ int d = 0;
+ handle.getArgument("integer2", d);
+ EXPECT_EQ(142, d);
+
+ // Add a short (random value).
+ short e = -81;
+ handle.setArgument("short", e);
+ EXPECT_EQ(-81, e);
+
+ short f = 0;
+ handle.getArgument("short", f);
+ EXPECT_EQ(-81, f);
+}
+
+// Test that trying to get an unknown argument throws an exception.
+
+TEST_F(CalloutHandleTest, ArgumentUnknownName) {
+ CalloutHandle handle(getCalloutManager());
+
+ // Set an integer
+ int a = 42;
+ handle.setArgument("integer1", a);
+ EXPECT_EQ(42, a);
+
+ // Check we can retrieve it
+ int b = 0;
+ handle.getArgument("integer1", b);
+ EXPECT_EQ(42, b);
+
+ // Check that getting an unknown name throws an exception.
+ int c = 0;
+ EXPECT_THROW(handle.getArgument("unknown", c), NoSuchArgument);
+}
+
+// Test that trying to get an argument with an incorrect type throws an
+// exception.
+
+TEST_F(CalloutHandleTest, ArgumentIncorrectType) {
+ CalloutHandle handle(getCalloutManager());
+
+ // Set an integer
+ int a = 42;
+ handle.setArgument("integer1", a);
+ EXPECT_EQ(42, a);
+
+ // Check we can retrieve it
+ long b = 0;
+ EXPECT_THROW(handle.getArgument("integer1", b), boost::bad_any_cast);
+}
+
+// Now try with some very complex types. The types cannot be defined within
+// the function and they should contain a copy constructor. For this reason,
+// a simple "struct" is used.
+
+struct Alpha {
+ int a;
+ int b;
+ Alpha(int first = 0, int second = 0) : a(first), b(second) {}
+};
+
+struct Beta {
+ int c;
+ int d;
+ Beta(int first = 0, int second = 0) : c(first), d(second) {}
+};
+
+TEST_F(CalloutHandleTest, ComplexTypes) {
+ CalloutHandle handle(getCalloutManager());
+
+ // Declare two variables of different (complex) types. (Note as to the
+ // variable names: aleph and beth are the first two letters of the Hebrew
+ // alphabet.)
+ Alpha aleph(1, 2);
+ EXPECT_EQ(1, aleph.a);
+ EXPECT_EQ(2, aleph.b);
+ handle.setArgument("aleph", aleph);
+
+ Beta beth(11, 22);
+ EXPECT_EQ(11, beth.c);
+ EXPECT_EQ(22, beth.d);
+ handle.setArgument("beth", beth);
+
+ // Ensure we can extract the data correctly.
+ Alpha aleph2;
+ EXPECT_EQ(0, aleph2.a);
+ EXPECT_EQ(0, aleph2.b);
+ handle.getArgument("aleph", aleph2);
+ EXPECT_EQ(1, aleph2.a);
+ EXPECT_EQ(2, aleph2.b);
+
+ Beta beth2;
+ EXPECT_EQ(0, beth2.c);
+ EXPECT_EQ(0, beth2.d);
+ handle.getArgument("beth", beth2);
+ EXPECT_EQ(11, beth2.c);
+ EXPECT_EQ(22, beth2.d);
+
+ // Ensure that complex types also thrown an exception if we attempt to
+ // get a context element of the wrong type.
+ EXPECT_THROW(handle.getArgument("aleph", beth), boost::bad_any_cast);
+}
+
+// Check that the context can store pointers. And also check that it respects
+// that a "pointer to X" is not the same as a "pointer to const X".
+
+TEST_F(CalloutHandleTest, PointerTypes) {
+ CalloutHandle handle(getCalloutManager());
+
+ // Declare a couple of variables, const and non-const.
+ Alpha aleph(5, 10);
+ const Beta beth(15, 20);
+
+ Alpha* pa = ℵ
+ const Beta* pcb = ℶ
+
+ // Check pointers can be set and retrieved OK.
+ handle.setArgument("non_const_pointer", pa);
+ handle.setArgument("const_pointer", pcb);
+
+ Alpha* pa2 = 0;
+ handle.getArgument("non_const_pointer", pa2);
+ EXPECT_TRUE(pa == pa2);
+
+ const Beta* pcb2 = 0;
+ handle.getArgument("const_pointer", pcb2);
+ EXPECT_TRUE(pcb == pcb2);
+
+ // Check that the "const" is protected in the context.
+ const Alpha* pca3;
+ EXPECT_THROW(handle.getArgument("non_const_pointer", pca3),
+ boost::bad_any_cast);
+
+ Beta* pb3;
+ EXPECT_THROW(handle.getArgument("const_pointer", pb3),
+ boost::bad_any_cast);
+}
+
+// Check that we can get the names of the arguments.
+
+TEST_F(CalloutHandleTest, ContextItemNames) {
+ CalloutHandle handle(getCalloutManager());
+
+ vector<string> expected_names;
+
+ expected_names.push_back("faith");
+ handle.setArgument("faith", 42);
+ expected_names.push_back("hope");
+ handle.setArgument("hope", 43);
+ expected_names.push_back("charity");
+ handle.setArgument("charity", 44);
+
+ // Get the names and check against the expected names. We'll sort
+ // both arrays to simplify the checking.
+ vector<string> actual_names = handle.getArgumentNames();
+
+ sort(actual_names.begin(), actual_names.end());
+ sort(expected_names.begin(), expected_names.end());
+ EXPECT_TRUE(expected_names == actual_names);
+}
+
+// Test that we can delete an argument.
+
+TEST_F(CalloutHandleTest, DeleteArgument) {
+ CalloutHandle handle(getCalloutManager());
+
+ int one = 1;
+ int two = 2;
+ int three = 3;
+ int four = 4;
+ int value; // Return value
+
+ handle.setArgument("one", one);
+ handle.setArgument("two", two);
+ handle.setArgument("three", three);
+ handle.setArgument("four", four);
+
+ // Delete "one".
+ handle.getArgument("one", value);
+ EXPECT_EQ(1, value);
+ handle.deleteArgument("one");
+
+ EXPECT_THROW(handle.getArgument("one", value), NoSuchArgument);
+ handle.getArgument("two", value);
+ EXPECT_EQ(2, value);
+ handle.getArgument("three", value);
+ EXPECT_EQ(3, value);
+ handle.getArgument("four", value);
+ EXPECT_EQ(4, value);
+
+ // Delete "three".
+ handle.getArgument("three", value);
+ EXPECT_EQ(3, value);
+ handle.deleteArgument("three");
+
+ EXPECT_THROW(handle.getArgument("one", value), NoSuchArgument);
+ handle.getArgument("two", value);
+ EXPECT_EQ(2, value);
+ EXPECT_THROW(handle.getArgument("three", value), NoSuchArgument);
+ handle.getArgument("four", value);
+ EXPECT_EQ(4, value);
+}
+
+// Test that we can delete all arguments.
+
+TEST_F(CalloutHandleTest, DeleteAllArguments) {
+ CalloutHandle handle(getCalloutManager());
+
+ int one = 1;
+ int two = 2;
+ int three = 3;
+ int four = 4;
+ int value; // Return value
+
+ // Set the arguments. The previous test verifies that this works.
+ handle.setArgument("one", one);
+ handle.setArgument("two", two);
+ handle.setArgument("three", three);
+ handle.setArgument("four", four);
+
+ // Delete all arguments...
+ handle.deleteAllArguments();
+
+ // ... and check that none are left.
+ EXPECT_THROW(handle.getArgument("one", value), NoSuchArgument);
+ EXPECT_THROW(handle.getArgument("two", value), NoSuchArgument);
+ EXPECT_THROW(handle.getArgument("three", value), NoSuchArgument);
+ EXPECT_THROW(handle.getArgument("four", value), NoSuchArgument);
+}
+
+// Test the "skip" flag.
+
+TEST_F(CalloutHandleTest, SkipFlag) {
+ CalloutHandle handle(getCalloutManager());
+
+ // Should be false on construction.
+ EXPECT_FALSE(handle.getSkip());
+
+ handle.setSkip(true);
+ EXPECT_TRUE(handle.getSkip());
+
+ handle.setSkip(false);
+ EXPECT_FALSE(handle.getSkip());
+}
+
+// Further tests of the "skip" flag and tests of getting the name of the
+// hook to which the current callout is attached is in the "handles_unittest"
+// module.
+
+} // Anonymous namespace
diff --git a/src/lib/hooks/tests/callout_manager_unittest.cc b/src/lib/hooks/tests/callout_manager_unittest.cc
new file mode 100644
index 0000000..935987a
--- /dev/null
+++ b/src/lib/hooks/tests/callout_manager_unittest.cc
@@ -0,0 +1,886 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <exceptions/exceptions.h>
+#include <hooks/callout_handle.h>
+#include <hooks/callout_manager.h>
+#include <hooks/library_handle.h>
+#include <hooks/server_hooks.h>
+
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+
+#include <algorithm>
+#include <climits>
+#include <string>
+#include <vector>
+
+/// @file
+/// @brief CalloutManager and LibraryHandle tests
+///
+/// These set of tests check the CalloutManager and LibraryHandle. They are
+/// together in the same file because the LibraryHandle is little more than a
+/// restricted interface to the CalloutManager, and a lot of the support
+/// structure for the tests is common.
+
+using namespace isc;
+using namespace isc::hooks;
+using namespace std;
+
+namespace {
+
+class CalloutManagerTest : public ::testing::Test {
+public:
+ /// @brief Constructor
+ ///
+ /// Sets up a collection of three LibraryHandle objects to use in the test.
+ CalloutManagerTest() {
+
+ // Set up the server hooks. There is sone singleton for all tests,
+ // so reset it and explicitly set up the hooks for the test.
+ ServerHooks& hooks = ServerHooks::getServerHooks();
+ hooks.reset();
+ alpha_index_ = hooks.registerHook("alpha");
+ beta_index_ = hooks.registerHook("beta");
+ gamma_index_ = hooks.registerHook("gamma");
+ delta_index_ = hooks.registerHook("delta");
+
+ // Set up the callout manager with these hooks. Assume a maximum of
+ // four libraries.
+ callout_manager_.reset(new CalloutManager(10));
+
+ // Set up the callout handle.
+ callout_handle_.reset(new CalloutHandle(callout_manager_));
+
+ // Initialize the static variable.
+ callout_value_ = 0;
+ }
+
+ /// @brief Return the callout handle
+ CalloutHandle& getCalloutHandle() {
+ return (*callout_handle_);
+ }
+
+ /// @brief Return the callout manager
+ boost::shared_ptr<CalloutManager> getCalloutManager() {
+ return (callout_manager_);
+ }
+
+ /// Static variable used for accumulating information
+ static int callout_value_;
+
+ /// Hook indexes. These are somewhat ubiquitous, so are made public for
+ /// ease of reference instead of being accessible by a function.
+ int alpha_index_;
+ int beta_index_;
+ int gamma_index_;
+ int delta_index_;
+
+private:
+ /// Callout handle used in calls
+ boost::shared_ptr<CalloutHandle> callout_handle_;
+
+ /// Callout manager used for the test
+ boost::shared_ptr<CalloutManager> callout_manager_;
+};
+
+// Definition of the static variable.
+int CalloutManagerTest::callout_value_ = 0;
+
+// Callout definitions
+//
+// The callouts defined here are structured in such a way that it is possible
+// to determine the order in which they are called and whether they are called
+// at all. The method used is simple - after a sequence of callouts, the digits
+// in the value, reading left to right, determines the order of the callouts
+// called. For example, callout one followed by two followed by three followed
+// by two followed by one results in a value of 12321.
+//
+// Functions return a zero to indicate success.
+
+extern "C" {
+int callout_general(int number) {
+ CalloutManagerTest::callout_value_ =
+ 10 * CalloutManagerTest::callout_value_ + number;
+ return (0);
+}
+
+int callout_one(CalloutHandle&) {
+ return (callout_general(1));
+}
+
+int callout_two(CalloutHandle&) {
+ return (callout_general(2));
+}
+
+int callout_three(CalloutHandle&) {
+ return (callout_general(3));
+}
+
+int callout_four(CalloutHandle&) {
+ return (callout_general(4));
+}
+
+int callout_five(CalloutHandle&) {
+ return (callout_general(5));
+}
+
+int callout_six(CalloutHandle&) {
+ return (callout_general(6));
+}
+
+int callout_seven(CalloutHandle&) {
+ return (callout_general(7));
+}
+
+// The next functions are duplicates of some of the above, but return an error.
+
+int callout_one_error(CalloutHandle& handle) {
+ (void) callout_one(handle);
+ return (1);
+}
+
+int callout_two_error(CalloutHandle& handle) {
+ (void) callout_two(handle);
+ return (1);
+}
+
+int callout_three_error(CalloutHandle& handle) {
+ (void) callout_three(handle);
+ return (1);
+}
+
+int callout_four_error(CalloutHandle& handle) {
+ (void) callout_four(handle);
+ return (1);
+}
+
+}; // extern "C"
+
+// *** Callout Tests ***
+//
+// The next set of tests check that callouts can be called.
+
+// Constructor - check that we trap bad parameters.
+
+TEST_F(CalloutManagerTest, BadConstructorParameters) {
+ boost::scoped_ptr<CalloutManager> cm;
+
+ // Invalid number of libraries
+ EXPECT_THROW(cm.reset(new CalloutManager(-1)), BadValue);
+}
+
+// Check the number of libraries is reported successfully.
+
+TEST_F(CalloutManagerTest, NumberOfLibraries) {
+ boost::scoped_ptr<CalloutManager> cm;
+
+ // Check two valid values of number of libraries to ensure that the
+ // GetNumLibraries() returns the value set.
+ EXPECT_NO_THROW(cm.reset(new CalloutManager()));
+ EXPECT_EQ(0, cm->getNumLibraries());
+
+ EXPECT_NO_THROW(cm.reset(new CalloutManager(0)));
+ EXPECT_EQ(0, cm->getNumLibraries());
+
+ EXPECT_NO_THROW(cm.reset(new CalloutManager(4)));
+ EXPECT_EQ(4, cm->getNumLibraries());
+
+ EXPECT_NO_THROW(cm.reset(new CalloutManager(42)));
+ EXPECT_EQ(42, cm->getNumLibraries());
+
+ // Check that setting the number of libraries alterns the number reported.
+ EXPECT_NO_THROW(cm->setNumLibraries(27));
+ EXPECT_EQ(27, cm->getNumLibraries());
+}
+
+// Check that we can only set the current library index to the correct values.
+
+TEST_F(CalloutManagerTest, CheckLibraryIndex) {
+ // Check valid indexes. As the callout manager is sized for 10 libraries,
+ // we expect:
+ //
+ // -1 to be valid as it is the standard "invalid" value.
+ // 0 to be valid for the pre-user library callouts
+ // 1-10 to be valid for the user-library callouts
+ // INT_MAX to be valid for the post-user library callouts
+ //
+ // All other values to be invalid.
+ for (int i = -1; i < 11; ++i) {
+ EXPECT_NO_THROW(getCalloutManager()->setLibraryIndex(i));
+ EXPECT_EQ(i, getCalloutManager()->getLibraryIndex());
+ }
+ EXPECT_NO_THROW(getCalloutManager()->setLibraryIndex(INT_MAX));
+ EXPECT_EQ(INT_MAX, getCalloutManager()->getLibraryIndex());
+
+ // Check invalid ones
+ EXPECT_THROW(getCalloutManager()->setLibraryIndex(-2), NoSuchLibrary);
+ EXPECT_THROW(getCalloutManager()->setLibraryIndex(11), NoSuchLibrary);
+}
+
+// Check that we can only register callouts on valid hook names.
+
+TEST_F(CalloutManagerTest, ValidHookNames) {
+ getCalloutManager()->setLibraryIndex(0);
+ EXPECT_NO_THROW(getCalloutManager()->registerCallout("alpha", callout_one));
+ EXPECT_THROW(getCalloutManager()->registerCallout("unknown", callout_one),
+ NoSuchHook);
+}
+
+
+// Check we can register callouts appropriately.
+
+TEST_F(CalloutManagerTest, RegisterCallout) {
+ // Ensure that no callouts are attached to any of the hooks.
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
+
+ // Set up so that hooks "alpha" and "beta" have callouts attached from a
+ // different libraries.
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("alpha", callout_one);
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->registerCallout("beta", callout_two);
+
+ // Check all is as expected.
+ EXPECT_TRUE(getCalloutManager()->calloutsPresent(alpha_index_));
+ EXPECT_TRUE(getCalloutManager()->calloutsPresent(beta_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
+
+ // Check that calling the callouts returns as expected. (This is also a
+ // test of the callCallouts method.)
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(1, callout_value_);
+
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(beta_index_, getCalloutHandle());
+ EXPECT_EQ(2, callout_value_);
+
+ // Register some more callouts from different libraries on hook "alpha".
+ getCalloutManager()->setLibraryIndex(2);
+ getCalloutManager()->registerCallout("alpha", callout_three);
+ getCalloutManager()->registerCallout("alpha", callout_four);
+ getCalloutManager()->setLibraryIndex(3);
+ getCalloutManager()->registerCallout("alpha", callout_five);
+
+ // Check it is as expected.
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(1345, callout_value_);
+
+ // ... and check the additional callouts were not registered on the "beta"
+ // hook.
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(beta_index_, getCalloutHandle());
+ EXPECT_EQ(2, callout_value_);
+
+ // Add another callout to hook "alpha" from library index 2 - this should
+ // appear at the end of the callout list for that library.
+ getCalloutManager()->setLibraryIndex(2);
+ getCalloutManager()->registerCallout("alpha", callout_six);
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(13465, callout_value_);
+
+ // Add a callout from library index 1 - this should appear between the
+ // callouts from library index 0 and linrary index 2.
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->registerCallout("alpha", callout_seven);
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(173465, callout_value_);
+}
+
+// Check the "calloutsPresent()" method.
+
+TEST_F(CalloutManagerTest, CalloutsPresent) {
+ // Ensure that no callouts are attached to any of the hooks.
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
+
+ // Set up so that hooks "alpha", "beta" and "delta" have callouts attached
+ // to them, and callout "gamma" does not. (In the statements below, the
+ // exact callouts attached to a hook are not relevant - only the fact
+ // that some callouts are). Chose the libraries for which the callouts
+ // are registered randomly.
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("alpha", callout_one);
+
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->registerCallout("alpha", callout_two);
+ getCalloutManager()->registerCallout("beta", callout_two);
+
+ getCalloutManager()->setLibraryIndex(3);
+ getCalloutManager()->registerCallout("alpha", callout_three);
+ getCalloutManager()->registerCallout("delta", callout_four);
+
+ // Check all is as expected.
+ EXPECT_TRUE(getCalloutManager()->calloutsPresent(alpha_index_));
+ EXPECT_TRUE(getCalloutManager()->calloutsPresent(beta_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
+ EXPECT_TRUE(getCalloutManager()->calloutsPresent(delta_index_));
+
+ // Check we fail on an invalid hook index.
+ EXPECT_THROW(getCalloutManager()->calloutsPresent(42), NoSuchHook);
+ EXPECT_THROW(getCalloutManager()->calloutsPresent(-1), NoSuchHook);
+}
+
+// Test that calling a hook with no callouts on it returns success.
+
+TEST_F(CalloutManagerTest, CallNoCallouts) {
+ // Ensure that no callouts are attached to any of the hooks.
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
+
+ // Call the callouts on an arbitrary hook and ensure that nothing happens.
+ callout_value_ = 475;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(475, callout_value_); // Unchanged
+}
+
+// Test that the callouts are called in the correct order (i.e. the callouts
+// from the first library in the order they were registered, then the callouts
+// from the second library in the order they were registered etc.)
+
+TEST_F(CalloutManagerTest, CallCalloutsSuccess) {
+ // Ensure that no callouts are attached to any of the hooks.
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
+
+ // Each library contributes one callout on hook "alpha".
+ callout_value_ = 0;
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->registerCallout("alpha", callout_one);
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->registerCallout("alpha", callout_two);
+ getCalloutManager()->setLibraryIndex(2);
+ getCalloutManager()->registerCallout("alpha", callout_three);
+ getCalloutManager()->setLibraryIndex(3);
+ getCalloutManager()->registerCallout("alpha", callout_four);
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(1234, callout_value_);
+
+ // Do a random selection of callouts on hook "beta".
+ callout_value_ = 0;
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("beta", callout_one);
+ getCalloutManager()->registerCallout("beta", callout_three);
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->registerCallout("beta", callout_two);
+ getCalloutManager()->setLibraryIndex(3);
+ getCalloutManager()->registerCallout("beta", callout_four);
+ getCalloutManager()->callCallouts(beta_index_, getCalloutHandle());
+ EXPECT_EQ(1324, callout_value_);
+
+ // Ensure that calling the callouts on a hook with no callouts works.
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(gamma_index_, getCalloutHandle());
+ EXPECT_EQ(0, callout_value_);
+}
+
+// Test that the callouts are called in order, but that callouts occurring
+// after a callout that returns an error are not called.
+//
+// (Note: in this test, the callouts that return an error set the value of
+// callout_value_ before they return the error code.)
+
+TEST_F(CalloutManagerTest, CallCalloutsError) {
+ // Ensure that no callouts are attached to any of the hooks.
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
+
+ // Each library contributing one callout on hook "alpha". The first callout
+ // returns an error (after adding its value to the result).
+ callout_value_ = 0;
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("alpha", callout_one_error);
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->registerCallout("alpha", callout_two);
+ getCalloutManager()->setLibraryIndex(2);
+ getCalloutManager()->registerCallout("alpha", callout_three);
+ getCalloutManager()->setLibraryIndex(3);
+ getCalloutManager()->registerCallout("alpha", callout_four);
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(1234, callout_value_);
+
+ // Each library contributing multiple callouts on hook "beta". The last
+ // callout on the first library returns an error.
+ callout_value_ = 0;
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("beta", callout_one);
+ getCalloutManager()->registerCallout("beta", callout_one_error);
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->registerCallout("beta", callout_two);
+ getCalloutManager()->registerCallout("beta", callout_two);
+ getCalloutManager()->registerCallout("beta", callout_three);
+ getCalloutManager()->registerCallout("beta", callout_three);
+ getCalloutManager()->setLibraryIndex(3);
+ getCalloutManager()->registerCallout("beta", callout_four);
+ getCalloutManager()->registerCallout("beta", callout_four);
+ getCalloutManager()->callCallouts(beta_index_, getCalloutHandle());
+ EXPECT_EQ(11223344, callout_value_);
+
+ // A callout in a random position in the callout list returns an error.
+ callout_value_ = 0;
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("gamma", callout_one);
+ getCalloutManager()->registerCallout("gamma", callout_one);
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->registerCallout("gamma", callout_two);
+ getCalloutManager()->registerCallout("gamma", callout_two);
+ getCalloutManager()->setLibraryIndex(3);
+ getCalloutManager()->registerCallout("gamma", callout_four_error);
+ getCalloutManager()->registerCallout("gamma", callout_four);
+ getCalloutManager()->callCallouts(gamma_index_, getCalloutHandle());
+ EXPECT_EQ(112244, callout_value_);
+
+ // The last callout on a hook returns an error.
+ callout_value_ = 0;
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("delta", callout_one);
+ getCalloutManager()->registerCallout("delta", callout_one);
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->registerCallout("delta", callout_two);
+ getCalloutManager()->registerCallout("delta", callout_two);
+ getCalloutManager()->setLibraryIndex(2);
+ getCalloutManager()->registerCallout("delta", callout_three);
+ getCalloutManager()->registerCallout("delta", callout_three);
+ getCalloutManager()->setLibraryIndex(3);
+ getCalloutManager()->registerCallout("delta", callout_four);
+ getCalloutManager()->registerCallout("delta", callout_four_error);
+ getCalloutManager()->callCallouts(delta_index_, getCalloutHandle());
+ EXPECT_EQ(11223344, callout_value_);
+}
+
+// Now test that we can deregister a single callout on a hook.
+
+TEST_F(CalloutManagerTest, DeregisterSingleCallout) {
+ // Ensure that no callouts are attached to any of the hooks.
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
+
+ // Add a callout to hook "alpha" and check it is added correctly.
+ callout_value_ = 0;
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("alpha", callout_two);
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(2, callout_value_);
+
+ // Remove it and check that the no callouts are present. We have to reset
+ // the current library index here as it was invalidated by the call
+ // to callCallouts().
+ getCalloutManager()->setLibraryIndex(0);
+ EXPECT_TRUE(getCalloutManager()->calloutsPresent(alpha_index_));
+ EXPECT_TRUE(getCalloutManager()->deregisterCallout("alpha", callout_two));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
+}
+
+// Now test that we can deregister a single callout on a hook that has multiple
+// callouts from the same library.
+
+TEST_F(CalloutManagerTest, DeregisterSingleCalloutSameLibrary) {
+ // Ensure that no callouts are attached to any of the hooks.
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
+
+ // Add multiple callouts to hook "alpha".
+ callout_value_ = 0;
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("alpha", callout_one);
+ getCalloutManager()->registerCallout("alpha", callout_two);
+ getCalloutManager()->registerCallout("alpha", callout_three);
+ getCalloutManager()->registerCallout("alpha", callout_four);
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(1234, callout_value_);
+
+ // Remove the callout_two callout. We have to reset the current library
+ // index here as it was invalidated by the call to callCallouts().
+ getCalloutManager()->setLibraryIndex(0);
+ EXPECT_TRUE(getCalloutManager()->deregisterCallout("alpha", callout_two));
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(134, callout_value_);
+
+ // Try removing it again.
+ getCalloutManager()->setLibraryIndex(0);
+ EXPECT_FALSE(getCalloutManager()->deregisterCallout("alpha", callout_two));
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(134, callout_value_);
+
+}
+
+// Check we can deregister multiple callouts from the same library.
+
+TEST_F(CalloutManagerTest, DeregisterMultipleCalloutsSameLibrary) {
+ // Ensure that no callouts are attached to any of the hooks.
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
+
+ // Each library contributes one callout on hook "alpha".
+ callout_value_ = 0;
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("alpha", callout_one);
+ getCalloutManager()->registerCallout("alpha", callout_two);
+ getCalloutManager()->registerCallout("alpha", callout_one);
+ getCalloutManager()->registerCallout("alpha", callout_two);
+ getCalloutManager()->registerCallout("alpha", callout_three);
+ getCalloutManager()->registerCallout("alpha", callout_four);
+ getCalloutManager()->registerCallout("alpha", callout_three);
+ getCalloutManager()->registerCallout("alpha", callout_four);
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(12123434, callout_value_);
+
+ // Remove the callout_two callouts. We have to reset the current library
+ // index here as it was invalidated by the call to callCallouts().
+ getCalloutManager()->setLibraryIndex(0);
+ EXPECT_TRUE(getCalloutManager()->deregisterCallout("alpha", callout_two));
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(113434, callout_value_);
+
+ // Try removing multiple callouts that includes one at the end of the
+ // list of callouts.
+ getCalloutManager()->setLibraryIndex(0);
+ EXPECT_TRUE(getCalloutManager()->deregisterCallout("alpha", callout_four));
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(1133, callout_value_);
+
+ // ... and from the start.
+ getCalloutManager()->setLibraryIndex(0);
+ EXPECT_TRUE(getCalloutManager()->deregisterCallout("alpha", callout_one));
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(33, callout_value_);
+
+ // ... and the remaining callouts.
+ getCalloutManager()->setLibraryIndex(0);
+ EXPECT_TRUE(getCalloutManager()->deregisterCallout("alpha", callout_three));
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(0, callout_value_);
+
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
+}
+
+// Check we can deregister multiple callouts from multiple libraries.
+
+TEST_F(CalloutManagerTest, DeregisterMultipleCalloutsMultipleLibraries) {
+ // Ensure that no callouts are attached to any of the hooks.
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
+
+ // Each library contributes two callouts to hook "alpha".
+ callout_value_ = 0;
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("alpha", callout_one);
+ getCalloutManager()->registerCallout("alpha", callout_two);
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->registerCallout("alpha", callout_three);
+ getCalloutManager()->registerCallout("alpha", callout_four);
+ getCalloutManager()->setLibraryIndex(2);
+ getCalloutManager()->registerCallout("alpha", callout_five);
+ getCalloutManager()->registerCallout("alpha", callout_two);
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(123452, callout_value_);
+
+ // Remove the callout_two callout from library 0. It should not affect
+ // the second callout_two callout registered by library 2.
+ getCalloutManager()->setLibraryIndex(0);
+ EXPECT_TRUE(getCalloutManager()->deregisterCallout("alpha", callout_two));
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(13452, callout_value_);
+}
+
+// Check we can deregister all callouts from a single library.
+
+TEST_F(CalloutManagerTest, DeregisterAllCallouts) {
+ // Ensure that no callouts are attached to hook one.
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
+
+ // Each library contributes two callouts to hook "alpha".
+ callout_value_ = 0;
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("alpha", callout_one);
+ getCalloutManager()->registerCallout("alpha", callout_two);
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->registerCallout("alpha", callout_three);
+ getCalloutManager()->registerCallout("alpha", callout_four);
+ getCalloutManager()->setLibraryIndex(2);
+ getCalloutManager()->registerCallout("alpha", callout_five);
+ getCalloutManager()->registerCallout("alpha", callout_six);
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(123456, callout_value_);
+
+ // Remove all callouts from library index 1.
+ getCalloutManager()->setLibraryIndex(1);
+ EXPECT_TRUE(getCalloutManager()->deregisterAllCallouts("alpha"));
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(1256, callout_value_);
+
+ // Remove all callouts from library index 2.
+ getCalloutManager()->setLibraryIndex(2);
+ EXPECT_TRUE(getCalloutManager()->deregisterAllCallouts("alpha"));
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(12, callout_value_);
+}
+
+// Check that we can register/deregister callouts on different libraries
+// and different hooks, and that the callout instances are regarded as
+// unique and do not affect one another.
+
+TEST_F(CalloutManagerTest, MultipleCalloutsLibrariesHooks) {
+ // Ensure that no callouts are attached to any of the hooks.
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
+
+ // Register callouts on the alpha hook.
+ callout_value_ = 0;
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("alpha", callout_one);
+ getCalloutManager()->registerCallout("alpha", callout_two);
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->registerCallout("alpha", callout_three);
+ getCalloutManager()->registerCallout("alpha", callout_four);
+ getCalloutManager()->setLibraryIndex(2);
+ getCalloutManager()->registerCallout("alpha", callout_five);
+ getCalloutManager()->registerCallout("alpha", callout_two);
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(123452, callout_value_);
+
+ // Register the same callouts on the beta hook, and check that those
+ // on the alpha hook are not affected.
+ callout_value_ = 0;
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("beta", callout_five);
+ getCalloutManager()->registerCallout("beta", callout_one);
+ getCalloutManager()->setLibraryIndex(2);
+ getCalloutManager()->registerCallout("beta", callout_four);
+ getCalloutManager()->registerCallout("beta", callout_three);
+ getCalloutManager()->callCallouts(beta_index_, getCalloutHandle());
+ EXPECT_EQ(5143, callout_value_);
+
+ // Check that the order of callouts on the alpha hook has not been affected.
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(123452, callout_value_);
+
+ // Remove callout four from beta and check that alpha is not affected.
+ getCalloutManager()->setLibraryIndex(2);
+ EXPECT_TRUE(getCalloutManager()->deregisterCallout("beta", callout_four));
+
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(beta_index_, getCalloutHandle());
+ EXPECT_EQ(513, callout_value_);
+
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(123452, callout_value_);
+}
+
+// Library handle tests. As by inspection the LibraryHandle can be seen to be
+// little more than shell around CalloutManager, only a basic set of tests
+// is done concerning registration and deregistration of functions.
+//
+// More extensive tests (i.e. checking that when a callout is called it can
+// only register and deregister callouts within its library) require that
+// the CalloutHandle object pass the appropriate LibraryHandle to the
+// callout. These tests are done in the handles_unittest tests.
+
+TEST_F(CalloutManagerTest, LibraryHandleRegistration) {
+ // Ensure that no callouts are attached to any of the hooks.
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
+
+ // Set up so that hooks "alpha" and "beta" have callouts attached from a
+ // different libraries.
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->getLibraryHandle().registerCallout("alpha",
+ callout_one);
+ getCalloutManager()->getLibraryHandle().registerCallout("alpha",
+ callout_two);
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->getLibraryHandle().registerCallout("alpha",
+ callout_three);
+ getCalloutManager()->getLibraryHandle().registerCallout("alpha",
+ callout_four);
+
+ // Check all is as expected.
+ EXPECT_TRUE(getCalloutManager()->calloutsPresent(alpha_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
+
+ // Check that calling the callouts returns as expected. (This is also a
+ // test of the callCallouts method.)
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(1234, callout_value_);
+
+ // Deregister a callout on library index 0 (after we check we can't
+ // deregister it through library index 1).
+ getCalloutManager()->setLibraryIndex(1);
+ EXPECT_FALSE(getCalloutManager()->deregisterCallout("alpha", callout_two));
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(1234, callout_value_);
+
+ getCalloutManager()->setLibraryIndex(0);
+ EXPECT_TRUE(getCalloutManager()->deregisterCallout("alpha", callout_two));
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(134, callout_value_);
+
+ // Deregister all callouts on library index 1.
+ getCalloutManager()->setLibraryIndex(1);
+ EXPECT_TRUE(getCalloutManager()->deregisterAllCallouts("alpha"));
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(1, callout_value_);
+}
+
+// A repeat of the test above, but using the alternate constructor for the
+// LibraryHandle.
+TEST_F(CalloutManagerTest, LibraryHandleAlternateConstructor) {
+ // Ensure that no callouts are attached to any of the hooks.
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
+
+ // Set up so that hooks "alpha" and "beta" have callouts attached from a
+ // different libraries.
+ LibraryHandle lh0(getCalloutManager().get(), 0);
+ lh0.registerCallout("alpha", callout_one);
+ lh0.registerCallout("alpha", callout_two);
+
+ LibraryHandle lh1(getCalloutManager().get(), 1);
+ lh1.registerCallout("alpha", callout_three);
+ lh1.registerCallout("alpha", callout_four);
+
+ // Check all is as expected.
+ EXPECT_TRUE(getCalloutManager()->calloutsPresent(alpha_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
+
+ // Check that calling the callouts returns as expected. (This is also a
+ // test of the callCallouts method.)
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(1234, callout_value_);
+
+ // Deregister a callout on library index 0 (after we check we can't
+ // deregister it through library index 1).
+ EXPECT_FALSE(lh1.deregisterCallout("alpha", callout_two));
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(1234, callout_value_);
+
+ EXPECT_TRUE(lh0.deregisterCallout("alpha", callout_two));
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(134, callout_value_);
+
+ // Deregister all callouts on library index 1.
+ EXPECT_TRUE(lh1.deregisterAllCallouts("alpha"));
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(1, callout_value_);
+}
+
+// Check that the pre- and post- user callout library handles work
+// appropriately with no user libraries.
+
+TEST_F(CalloutManagerTest, LibraryHandlePrePostNoLibraries) {
+ // Create a local callout manager and callout handle to reflect no libraries
+ // being loaded.
+ boost::shared_ptr<CalloutManager> manager(new CalloutManager(0));
+ CalloutHandle handle(manager);
+
+ // Ensure that no callouts are attached to any of the hooks.
+ EXPECT_FALSE(manager->calloutsPresent(alpha_index_));
+
+ // Setup the pre-and post callouts.
+ manager->getPostLibraryHandle().registerCallout("alpha", callout_four);
+ manager->getPreLibraryHandle().registerCallout("alpha", callout_one);
+ // Check all is as expected.
+ EXPECT_TRUE(manager->calloutsPresent(alpha_index_));
+ EXPECT_FALSE(manager->calloutsPresent(beta_index_));
+ EXPECT_FALSE(manager->calloutsPresent(gamma_index_));
+ EXPECT_FALSE(manager->calloutsPresent(delta_index_));
+
+ // Check that calling the callouts returns as expected.
+ callout_value_ = 0;
+ manager->callCallouts(alpha_index_, handle);
+ EXPECT_EQ(14, callout_value_);
+
+ // Deregister the pre- library callout.
+ EXPECT_TRUE(manager->getPreLibraryHandle().deregisterAllCallouts("alpha"));
+ callout_value_ = 0;
+ manager->callCallouts(alpha_index_, handle);
+ EXPECT_EQ(4, callout_value_);
+}
+
+// Repeat the tests with one user library.
+
+TEST_F(CalloutManagerTest, LibraryHandlePrePostUserLibrary) {
+
+ // Setup the pre-, library and post callouts.
+ getCalloutManager()->getPostLibraryHandle().registerCallout("alpha",
+ callout_four);
+ getCalloutManager()->getPreLibraryHandle().registerCallout("alpha",
+ callout_one);
+
+ // ... and set up a callout in between, on library number 2.
+ LibraryHandle lh1(getCalloutManager().get(), 2);
+ lh1.registerCallout("alpha", callout_five);
+
+ // Check all is as expected.
+ EXPECT_TRUE(getCalloutManager()->calloutsPresent(alpha_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
+
+ // Check that calling the callouts returns as expected.
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(154, callout_value_);
+}
+
+// The setting of the hook index is checked in the handles_unittest
+// set of tests, as access restrictions mean it is not easily tested
+// on its own.
+
+} // Anonymous namespace
diff --git a/src/lib/hooks/tests/common_test_class.h b/src/lib/hooks/tests/common_test_class.h
new file mode 100644
index 0000000..803e25c
--- /dev/null
+++ b/src/lib/hooks/tests/common_test_class.h
@@ -0,0 +1,142 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef COMMON_HOOKS_TEST_CLASS_H
+#define COMMON_HOOKS_TEST_CLASS_H
+
+#include <hooks/callout_handle.h>
+#include <hooks/callout_manager.h>
+#include <hooks/server_hooks.h>
+#include <hooks/tests/marker_file.h>
+
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+
+/// @brief Common hooks test class
+///
+/// This class is a shared parent of the test fixture class in the tests of the
+/// higher-level hooks classes (LibraryManager, LibraryManagerCollection and
+/// HooksManager). It
+///
+/// - sets the the ServerHooks object with three hooks and stores their
+/// indexes.
+/// - executes the callouts (which are assumed to perform a calculation)
+/// and checks the results.
+
+class HooksCommonTestClass {
+public:
+ /// @brief Constructor
+ HooksCommonTestClass() {
+
+ // Set up the server hooks. ServerHooks is a singleton, so we reset it
+ // between each test.
+ isc::hooks::ServerHooks& hooks =
+ isc::hooks::ServerHooks::getServerHooks();
+ hooks.reset();
+ hookpt_one_index_ = hooks.registerHook("hookpt_one");
+ hookpt_two_index_ = hooks.registerHook("hookpt_two");
+ hookpt_three_index_ = hooks.registerHook("hookpt_three");
+ }
+
+ /// @brief Call callouts test
+ ///
+ /// All of the loaded libraries for which callouts are called register four
+ /// callouts: a context_create callout and three callouts that are attached
+ /// to hooks hookpt_one, hookpt_two and hookpt_three. These four callouts,
+ /// executed in sequence, perform a series of calculations. Data is passed
+ /// between callouts in the argument list, in a variable named "result".
+ ///
+ /// context_create initializes the calculation by setting a seed
+ /// value, called r0 here. This value is dependent on the library being
+ /// loaded. Prior to that, the argument "result" is initialized to -1,
+ /// the purpose being to avoid exceptions when running this test with no
+ /// libraries loaded.
+ ///
+ /// Callout hookpt_one is passed a value d1 and performs a simple arithmetic
+ /// operation on it and r0 yielding a result r1. Hence we can say that
+ /// @f[ r1 = hookpt_one(r0, d1) @f]
+ ///
+ /// Callout hookpt_two is passed a value d2 and peforms another simple
+ /// arithmetic operation on it and d2, yielding r2, i.e.
+ /// @f[ r2 = hookpt_two(d1, d2) @f]
+ ///
+ /// hookpt_three does a similar operation giving
+ /// @f[ r3 = hookpt_three(r2, d3) @f].
+ ///
+ /// The details of the operations hookpt_one, hookpt_two and hookpt_three
+ /// depend on the library, so the results obtained not only depend on
+ /// the data, but also on the library loaded. This method is passed both
+ /// data and expected results. It executes the three callouts in sequence,
+ /// checking the intermediate and final results. Only if the expected
+ /// library has been loaded correctly and the callouts in it registered
+ /// correctly will be the results be as expected.
+ ///
+ /// It is assumed that callout_manager_ has been set up appropriately.
+ ///
+ /// @note The CalloutHandle used in the calls is declared locally here.
+ /// The advantage of this (apart from scope reduction) is that on
+ /// exit, it is destroyed. This removes any references to memory
+ /// allocated by loaded libraries while they are still loaded.
+ ///
+ /// @param manager CalloutManager to use for the test
+ /// @param r0...r3, d1..d3 Data (dN) and expected results (rN) - both
+ /// intermediate and final. The arguments are ordered so that they
+ /// appear in the argument list in the order they are used.
+ void executeCallCallouts(
+ const boost::shared_ptr<isc::hooks::CalloutManager>& manager,
+ int r0, int d1, int r1, int d2, int r2, int d3, int r3) {
+ static const char* COMMON_TEXT = " callout returned the wong value";
+ static const char* RESULT = "result";
+
+ int result;
+
+ // Set up a callout handle for the calls.
+ isc::hooks::CalloutHandle handle(manager);
+
+ // Initialize the argument RESULT. This simplifies testing by
+ // eliminating the generation of an exception when we try the unload
+ // test. In that case, RESULT is unchanged.
+ handle.setArgument(RESULT, -1);
+
+ // Seed the calculation.
+ manager->callCallouts(isc::hooks::ServerHooks::CONTEXT_CREATE, handle);
+ handle.getArgument(RESULT, result);
+ EXPECT_EQ(r0, result) << "context_create" << COMMON_TEXT;
+
+ // Perform the first calculation.
+ handle.setArgument("data_1", d1);
+ manager->callCallouts(hookpt_one_index_, handle);
+ handle.getArgument(RESULT, result);
+ EXPECT_EQ(r1, result) << "hookpt_one" << COMMON_TEXT;
+
+ // ... the second ...
+ handle.setArgument("data_2", d2);
+ manager->callCallouts(hookpt_two_index_, handle);
+ handle.getArgument(RESULT, result);
+ EXPECT_EQ(r2, result) << "hookpt_two" << COMMON_TEXT;
+
+ // ... and the third.
+ handle.setArgument("data_3", d3);
+ manager->callCallouts(hookpt_three_index_, handle);
+ handle.getArgument(RESULT, result);
+ EXPECT_EQ(r3, result) << "hookpt_three" << COMMON_TEXT;
+ }
+
+ /// Hook indexes. These are are made public for ease of reference.
+ int hookpt_one_index_;
+ int hookpt_two_index_;
+ int hookpt_three_index_;
+};
+
+#endif // COMMON_HOOKS_TEST_CLASS_H
diff --git a/src/lib/hooks/tests/framework_exception_library.cc b/src/lib/hooks/tests/framework_exception_library.cc
new file mode 100644
index 0000000..e90fd36
--- /dev/null
+++ b/src/lib/hooks/tests/framework_exception_library.cc
@@ -0,0 +1,47 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/// @file
+/// @brief Framework exception library
+///
+/// This is source of a test library for various test (LibraryManager and
+/// HooksManager). The characteristics of the library produced from this
+/// file are:
+///
+/// - All three framework functions are supplied (version(), load() and
+/// unload()) and all generate an exception.
+
+#include <hooks/hooks.h>
+
+#include <exception>
+
+extern "C" {
+
+int
+version() {
+ throw std::exception();
+}
+
+int
+load(isc::hooks::LibraryHandle& handle) {
+ throw std::exception();
+}
+
+int
+unload() {
+ throw std::exception();
+}
+
+};
+
diff --git a/src/lib/hooks/tests/full_callout_library.cc b/src/lib/hooks/tests/full_callout_library.cc
new file mode 100644
index 0000000..33d5660
--- /dev/null
+++ b/src/lib/hooks/tests/full_callout_library.cc
@@ -0,0 +1,137 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/// @file
+/// @brief Full callout library
+///
+/// This is source of a test library for various test (LibraryManager and
+/// HooksManager). The characteristics of the library produced from this
+/// file are:
+///
+/// The characteristics of this library are:
+///
+/// - All three framework functions are supplied (version(), load() and
+/// unload()), with unload() creating a marker file. The test code checks
+/// for the presence of this file, so verifying that unload() has been run.
+///
+/// - One standard and two non-standard callouts are supplied, with the latter
+/// being registered by the load() function.
+///
+/// All callouts do trivial calculations, the result of all being called in
+/// sequence being
+///
+/// @f[ ((7 * data_1) - data_2) * data_3 @f]
+///
+/// ...where data_1, data_2 and data_3 are the values passed in arguments of
+/// the same name to the three callouts (data_1 passed to hookpt_one, data_2 to
+/// hookpt_two etc.) and the result is returned in the argument "result".
+
+#include <hooks/hooks.h>
+#include <hooks/tests/marker_file.h>
+
+#include <fstream>
+
+using namespace isc::hooks;
+
+extern "C" {
+
+// Callouts
+
+int
+context_create(CalloutHandle& handle) {
+ handle.setContext("result", static_cast<int>(7));
+ handle.setArgument("result", static_cast<int>(7));
+ return (0);
+}
+
+// First callout adds the passed "data_1" argument to the initialized context
+// value of 7. (Note that the value set by context_create is accessed through
+// context and not the argument, so checking that context is correctly passed
+// between callouts in the same library.)
+
+int
+hookpt_one(CalloutHandle& handle) {
+ int data;
+ handle.getArgument("data_1", data);
+
+ int result;
+ handle.getArgument("result", result);
+
+ result *= data;
+ handle.setArgument("result", result);
+
+ return (0);
+}
+
+// Second callout subtracts the passed value of data_2 from the current
+// running total.
+
+static int
+hook_nonstandard_two(CalloutHandle& handle) {
+ int data;
+ handle.getArgument("data_2", data);
+
+ int result;
+ handle.getArgument("result", result);
+
+ result -= data;
+ handle.setArgument("result", result);
+
+ return (0);
+}
+
+// Final callout multplies the current running total by data_3.
+
+static int
+hook_nonstandard_three(CalloutHandle& handle) {
+ int data;
+ handle.getArgument("data_3", data);
+
+ int result;
+ handle.getArgument("result", result);
+
+ result *= data;
+ handle.setArgument("result", result);
+
+ return (0);
+}
+
+// Framework functions
+
+int
+version() {
+ return (BIND10_HOOKS_VERSION);
+}
+
+int
+load(LibraryHandle& handle) {
+ // Register the non-standard functions
+ handle.registerCallout("hookpt_two", hook_nonstandard_two);
+ handle.registerCallout("hookpt_three", hook_nonstandard_three);
+
+ return (0);
+}
+
+int
+unload() {
+ // Create the marker file.
+ std::fstream marker;
+ marker.open(MARKER_FILE, std::fstream::out);
+ marker.close();
+
+ return (0);
+}
+
+};
+
diff --git a/src/lib/hooks/tests/handles_unittest.cc b/src/lib/hooks/tests/handles_unittest.cc
new file mode 100644
index 0000000..e5364bc
--- /dev/null
+++ b/src/lib/hooks/tests/handles_unittest.cc
@@ -0,0 +1,956 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <hooks/callout_handle.h>
+#include <hooks/callout_manager.h>
+#include <hooks/library_handle.h>
+#include <hooks/server_hooks.h>
+
+#include <boost/lexical_cast.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+
+#include <algorithm>
+#include <string>
+
+/// @file
+/// CalloutHandle/LibraryHandle interaction tests
+///
+/// This file holds unit tests checking the interaction between the
+/// CalloutHandle/LibraryHandle and CalloutManager classes. In particular,
+/// they check that:
+///
+/// - A CalloutHandle's context is shared between callouts from the same
+/// library, but there is a separate context for each library.
+///
+/// - The various methods manipulating the items in the CalloutHandle's context
+/// work correctly.
+///
+/// - An active callout can only modify the registration of callouts registered
+/// by its own library.
+
+using namespace isc::hooks;
+using namespace std;
+
+namespace {
+
+class HandlesTest : public ::testing::Test {
+public:
+ /// @brief Constructor
+ ///
+ /// Sets up the various elements used in each test.
+ HandlesTest() {
+ // Set up four hooks, although through gamma
+ ServerHooks& hooks = ServerHooks::getServerHooks();
+ hooks.reset();
+ alpha_index_ = hooks.registerHook("alpha");
+ beta_index_ = hooks.registerHook("beta");
+ gamma_index_ = hooks.registerHook("gamma");
+ delta_index_ = hooks.registerHook("delta");
+
+ // Set up for three libraries.
+ manager_.reset(new CalloutManager(3));
+
+ // Initialize remaining variables.
+ common_string_ = "";
+ }
+
+ /// @brief Return callout manager
+ boost::shared_ptr<CalloutManager> getCalloutManager() {
+ return (manager_);
+ }
+
+ /// Hook indexes - these are frequently accessed, so are accessed directly.
+ int alpha_index_;
+ int beta_index_;
+ int gamma_index_;
+ int delta_index_;
+
+ /// String accessible by all callouts whatever the library
+ static std::string common_string_;
+
+private:
+ /// Callout manager. Declared static so that the callout functions can
+ /// access it.
+ boost::shared_ptr<CalloutManager> manager_;
+};
+
+/// Define the common string
+std::string HandlesTest::common_string_;
+
+
+// The next set of functions define the callouts used by the tests. They
+// manipulate the data in such a way that callouts called - and the order in
+// which they were called - can be determined. The functions also check that
+// the "callout context" data areas are separate.
+//
+// Three libraries are assumed, and each supplies four callouts. All callouts
+// manipulate two context elements the CalloutHandle, the elements being called
+// "string" and "int" (which describe the type of data manipulated).
+//
+// For the string item, each callout shifts data to the left and inserts its own
+// data. The data is a string of the form "nmc", where "n" is the number of
+// the library, "m" is the callout number and "y" is the indication of what
+// callout handle was passed as an argument ("1" or "2": "0" is used when no
+// identification has been set in the callout handle).
+//
+// For simplicity, and to cut down the number of functions actually written,
+// the callout indicator ("1" or "2") ) used in the in the CalloutHandle
+// functions is passed via a CalloutArgument. The argument is named "string":
+// use of a name the same as that of one of the context elements serves as a
+// check that the argument name space and argument context space are separate.
+//
+// For integer data, the value starts at zero and an increment is added on each
+// call. This increment is equal to:
+//
+// 100 * library number + 10 * callout number + callout handle
+//
+// Although this gives less information than the string value, the reasons for
+// using it are:
+//
+// - It is a separate item in the context, so checks that the context can
+// handle multiple items.
+// - It provides an item that can be deleted by the context deletion
+// methods.
+
+
+// Values set in the CalloutHandle context. There are three libraries, so
+// there are three contexts for the callout, one for each library.
+
+std::string& resultCalloutString(int index) {
+ static std::string result_callout_string[3];
+ return (result_callout_string[index]);
+}
+
+int& resultCalloutInt(int index) {
+ static int result_callout_int[3];
+ return (result_callout_int[index]);
+}
+
+// A simple function to zero the results.
+
+static void zero_results() {
+ for (int i = 0; i < 3; ++i) {
+ resultCalloutString(i) = "";
+ resultCalloutInt(i) = 0;
+ }
+}
+
+
+// Library callouts.
+
+// Common code for setting the callout context values.
+
+int
+execute(CalloutHandle& callout_handle, int library_num, int callout_num) {
+
+ // Obtain the callout handle number
+ int handle_num = 0;
+ try {
+ callout_handle.getArgument("handle_num", handle_num);
+ } catch (const NoSuchArgument&) {
+ // handle_num argument not set: this is the case in the tests where
+ // the context_create hook check is tested.
+ handle_num = 0;
+ }
+
+ // Create the basic data to be appended to the context value.
+ int idata = 100 * library_num + 10 * callout_num + handle_num;
+ string sdata = boost::lexical_cast<string>(idata);
+
+ // Get the context data. As before, this will not exist for the first
+ // callout called. (In real life, the library should create it when the
+ // "context_create" hook gets called before any packet processing takes
+ // place.)
+ int int_value = 0;
+ try {
+ callout_handle.getContext("int", int_value);
+ } catch (const NoSuchCalloutContext&) {
+ int_value = 0;
+ }
+
+ string string_value = "";
+ try {
+ callout_handle.getContext("string", string_value);
+ } catch (const NoSuchCalloutContext&) {
+ string_value = "";
+ }
+
+ // Update the values and set them back in the callout context.
+ int_value += idata;
+ callout_handle.setContext("int", int_value);
+
+ string_value += sdata;
+ callout_handle.setContext("string", string_value);
+
+ return (0);
+}
+
+// The following functions are the actual callouts - the name is of the
+// form "callout_<library number>_<callout number>"
+
+int
+callout11(CalloutHandle& callout_handle) {
+ return (execute(callout_handle, 1, 1));
+}
+
+int
+callout12(CalloutHandle& callout_handle) {
+ return (execute(callout_handle, 1, 2));
+}
+
+int
+callout13(CalloutHandle& callout_handle) {
+ return (execute(callout_handle, 1, 3));
+}
+
+int
+callout21(CalloutHandle& callout_handle) {
+ return (execute(callout_handle, 2, 1));
+}
+
+int
+callout22(CalloutHandle& callout_handle) {
+ return (execute(callout_handle, 2, 2));
+}
+
+int
+callout23(CalloutHandle& callout_handle) {
+ return (execute(callout_handle, 2, 3));
+}
+
+int
+callout31(CalloutHandle& callout_handle) {
+ return (execute(callout_handle, 3, 1));
+}
+
+int
+callout32(CalloutHandle& callout_handle) {
+ return (execute(callout_handle, 3, 2));
+}
+
+int
+callout33(CalloutHandle& callout_handle) {
+ return (execute(callout_handle, 3, 3));
+}
+
+// Common callout code for the fourth hook (which makes the data available for
+// checking). It copies the library and callout context data to the global
+// variables.
+
+int printExecute(CalloutHandle& callout_handle, int library_num) {
+ callout_handle.getContext("string", resultCalloutString(library_num - 1));
+ callout_handle.getContext("int", resultCalloutInt(library_num - 1));
+
+ return (0);
+}
+
+// These are the actual callouts.
+
+int
+print1(CalloutHandle& callout_handle) {
+ return (printExecute(callout_handle, 1));
+}
+
+int
+print2(CalloutHandle& callout_handle) {
+ return (printExecute(callout_handle, 2));
+}
+
+int
+print3(CalloutHandle& callout_handle) {
+ return (printExecute(callout_handle, 3));
+}
+
+// This test checks the many-faced nature of the context for the CalloutContext.
+
+TEST_F(HandlesTest, ContextAccessCheck) {
+ // Register callouts for the different libraries.
+ CalloutHandle handle(getCalloutManager());
+
+ // Library 0.
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("alpha", callout11);
+ getCalloutManager()->registerCallout("beta", callout12);
+ getCalloutManager()->registerCallout("gamma", callout13);
+ getCalloutManager()->registerCallout("delta", print1);
+
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->registerCallout("alpha", callout21);
+ getCalloutManager()->registerCallout("beta", callout22);
+ getCalloutManager()->registerCallout("gamma", callout23);
+ getCalloutManager()->registerCallout("delta", print2);
+
+ getCalloutManager()->setLibraryIndex(2);
+ getCalloutManager()->registerCallout("alpha", callout31);
+ getCalloutManager()->registerCallout("beta", callout32);
+ getCalloutManager()->registerCallout("gamma", callout33);
+ getCalloutManager()->registerCallout("delta", print3);
+
+ // Create the callout handles and distinguish them by setting the
+ // "handle_num" argument.
+ CalloutHandle callout_handle_1(getCalloutManager());
+ callout_handle_1.setArgument("handle_num", static_cast<int>(1));
+
+ CalloutHandle callout_handle_2(getCalloutManager());
+ callout_handle_2.setArgument("handle_num", static_cast<int>(2));
+
+ // Now call the callouts attached to the first three hooks. Each hook is
+ // called twice (once for each callout handle) before the next hook is
+ // called.
+ getCalloutManager()->callCallouts(alpha_index_, callout_handle_1);
+ getCalloutManager()->callCallouts(alpha_index_, callout_handle_2);
+ getCalloutManager()->callCallouts(beta_index_, callout_handle_1);
+ getCalloutManager()->callCallouts(beta_index_, callout_handle_2);
+ getCalloutManager()->callCallouts(gamma_index_, callout_handle_1);
+ getCalloutManager()->callCallouts(gamma_index_, callout_handle_2);
+
+ // Get the results for each callout (the callout on hook "delta" copies
+ // the context values into a location the test can access). Explicitly
+ // zero the variables before getting the results so we are certain that
+ // the values are the results of the callouts.
+
+ zero_results();
+
+ // To explain the expected callout context results.
+ //
+ // Each callout handle maintains a separate context for each library. When
+ // the first call to callCallouts() is made, "111" gets appended to
+ // the context for library 1 maintained by the first callout handle, "211"
+ // gets appended to the context maintained for library 2, and "311" to
+ // the context maintained for library 3. In each case, the first digit
+ // corresponds to the library number, the second to the callout number and
+ // the third to the "handle_num" of the callout handle. For the first call
+ // to callCallouts, handle 1 is used, so the last digit is always 1.
+ //
+ // The next call to callCallouts() calls the same callouts but for the
+ // second callout handle. It also maintains three contexts (one for
+ // each library) and they will get "112", "212", "312" appended to
+ // them. The explanation for the digits is the same as before, except that
+ // in this case, the callout handle is number 2, so the third digit is
+ // always 2. These additions don't affect the contexts maintained by
+ // callout handle 1.
+ //
+ // The process is then repeated for hooks "beta" and "gamma" which, for
+ // callout handle 1, append "121", "221" and "321" for hook "beta" and
+ // "311", "321" and "331" for hook "gamma".
+ //
+ // The expected integer values can be found by summing up the values
+ // corresponding to the elements of the strings.
+
+ // At this point, we have only called the "print" function for callout
+ // handle "1", so the following results are checking the context values
+ // maintained in that callout handle.
+
+ getCalloutManager()->callCallouts(delta_index_, callout_handle_1);
+ EXPECT_EQ("111121131", resultCalloutString(0));
+ EXPECT_EQ("211221231", resultCalloutString(1));
+ EXPECT_EQ("311321331", resultCalloutString(2));
+
+ EXPECT_EQ((111 + 121 + 131), resultCalloutInt(0));
+ EXPECT_EQ((211 + 221 + 231), resultCalloutInt(1));
+ EXPECT_EQ((311 + 321 + 331), resultCalloutInt(2));
+
+ // Repeat the checks for callout 2.
+
+ zero_results();
+ getCalloutManager()->callCallouts(delta_index_, callout_handle_2);
+
+ EXPECT_EQ((112 + 122 + 132), resultCalloutInt(0));
+ EXPECT_EQ((212 + 222 + 232), resultCalloutInt(1));
+ EXPECT_EQ((312 + 322 + 332), resultCalloutInt(2));
+
+ EXPECT_EQ("112122132", resultCalloutString(0));
+ EXPECT_EQ("212222232", resultCalloutString(1));
+ EXPECT_EQ("312322332", resultCalloutString(2));
+}
+
+// Now repeat the test, but add a deletion callout to the list. The "beta"
+// hook of library 2 will have an additional callout to delete the "int"
+// element: the same hook for library 3 will delete both elements. In
+// addition, the names of context elements for the libraries at this point
+// will be printed.
+
+// List of context item names.
+
+vector<string>&
+getItemNames(int index) {
+ static vector<string> context_items[3];
+ return (context_items[index]);
+}
+
+// Context item deletion functions.
+
+int
+deleteIntContextItem(CalloutHandle& handle) {
+ handle.deleteContext("int");
+ return (0);
+}
+
+int
+deleteAllContextItems(CalloutHandle& handle) {
+ handle.deleteAllContext();
+ return (0);
+}
+
+// Generic print function - copy names in sorted order.
+
+int
+printContextNamesExecute(CalloutHandle& handle, int library_num) {
+ const int index = library_num - 1;
+ getItemNames(index) = handle.getContextNames();
+ sort(getItemNames(index).begin(), getItemNames(index).end());
+ return (0);
+}
+
+int
+printContextNames1(CalloutHandle& handle) {
+ return (printContextNamesExecute(handle, 1));
+}
+
+int
+printContextNames2(CalloutHandle& handle) {
+ return (printContextNamesExecute(handle, 2));
+}
+
+int
+printContextNames3(CalloutHandle& handle) {
+ return (printContextNamesExecute(handle, 3));
+}
+
+// Perform the test including deletion of context items.
+
+TEST_F(HandlesTest, ContextDeletionCheck) {
+
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("alpha", callout11);
+ getCalloutManager()->registerCallout("beta", callout12);
+ getCalloutManager()->registerCallout("beta", printContextNames1);
+ getCalloutManager()->registerCallout("gamma", callout13);
+ getCalloutManager()->registerCallout("delta", print1);
+
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->registerCallout("alpha", callout21);
+ getCalloutManager()->registerCallout("beta", callout22);
+ getCalloutManager()->registerCallout("beta", deleteIntContextItem);
+ getCalloutManager()->registerCallout("beta", printContextNames2);
+ getCalloutManager()->registerCallout("gamma", callout23);
+ getCalloutManager()->registerCallout("delta", print2);
+
+ getCalloutManager()->setLibraryIndex(2);
+ getCalloutManager()->registerCallout("alpha", callout31);
+ getCalloutManager()->registerCallout("beta", callout32);
+ getCalloutManager()->registerCallout("beta", deleteAllContextItems);
+ getCalloutManager()->registerCallout("beta", printContextNames3);
+ getCalloutManager()->registerCallout("gamma", callout33);
+ getCalloutManager()->registerCallout("delta", print3);
+
+ // Create the callout handles and distinguish them by setting the "long"
+ // argument.
+ CalloutHandle callout_handle_1(getCalloutManager());
+ callout_handle_1.setArgument("handle_num", static_cast<int>(1));
+
+ CalloutHandle callout_handle_2(getCalloutManager());
+ callout_handle_2.setArgument("handle_num", static_cast<int>(2));
+
+ // Now call the callouts attached to the first three hooks. Each hook is
+ // called twice (once for each callout handle) before the next hook is
+ // called.
+ getCalloutManager()->callCallouts(alpha_index_, callout_handle_1);
+ getCalloutManager()->callCallouts(alpha_index_, callout_handle_2);
+ getCalloutManager()->callCallouts(beta_index_, callout_handle_1);
+ getCalloutManager()->callCallouts(beta_index_, callout_handle_2);
+ getCalloutManager()->callCallouts(gamma_index_, callout_handle_1);
+ getCalloutManager()->callCallouts(gamma_index_, callout_handle_2);
+
+ // Get the results for each callout. Explicitly zero the variables before
+ // getting the results so we are certain that the values are the results
+ // of the callouts.
+
+ zero_results();
+ getCalloutManager()->callCallouts(delta_index_, callout_handle_1);
+
+ // The logic by which the expected results are arrived at is described
+ // in the ContextAccessCheck test. The results here are different
+ // because context items have been modified along the way.
+
+ EXPECT_EQ((111 + 121 + 131), resultCalloutInt(0));
+ EXPECT_EQ(( 231), resultCalloutInt(1));
+ EXPECT_EQ(( 331), resultCalloutInt(2));
+
+ EXPECT_EQ("111121131", resultCalloutString(0));
+ EXPECT_EQ("211221231", resultCalloutString(1));
+ EXPECT_EQ( "331", resultCalloutString(2));
+
+ // Repeat the checks for callout handle 2.
+
+ zero_results();
+ getCalloutManager()->callCallouts(delta_index_, callout_handle_2);
+
+ EXPECT_EQ((112 + 122 + 132), resultCalloutInt(0));
+ EXPECT_EQ(( 232), resultCalloutInt(1));
+ EXPECT_EQ(( 332), resultCalloutInt(2));
+
+ EXPECT_EQ("112122132", resultCalloutString(0));
+ EXPECT_EQ("212222232", resultCalloutString(1));
+ EXPECT_EQ( "332", resultCalloutString(2));
+
+ // ... and check what the names of the context items are after the callouts
+ // for hook "beta". We know they are in sorted order.
+
+ EXPECT_EQ(2, getItemNames(0).size());
+ EXPECT_EQ(string("int"), getItemNames(0)[0]);
+ EXPECT_EQ(string("string"), getItemNames(0)[1]);
+
+ EXPECT_EQ(1, getItemNames(1).size());
+ EXPECT_EQ(string("string"), getItemNames(1)[0]);
+
+ EXPECT_EQ(0, getItemNames(2).size());
+}
+
+// Tests that the CalloutHandle's constructor and destructor call the
+// context_create and context_destroy callbacks (if registered). For
+// simplicity, we'll use the same callout functions as used above.
+
+TEST_F(HandlesTest, ConstructionDestructionCallouts) {
+
+ // Register context callouts.
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("context_create", callout11);
+ getCalloutManager()->registerCallout("context_create", print1);
+ getCalloutManager()->registerCallout("context_destroy", callout12);
+ getCalloutManager()->registerCallout("context_destroy", print1);
+
+ // Create the CalloutHandle and check that the constructor callout
+ // has run.
+ zero_results();
+ boost::scoped_ptr<CalloutHandle>
+ callout_handle(new CalloutHandle(getCalloutManager()));
+ EXPECT_EQ("110", resultCalloutString(0));
+ EXPECT_EQ(110, resultCalloutInt(0));
+
+ // Check that the destructor callout runs. Note that the "print1" callout
+ // didn't destroy the library context - it only copied it to where it
+ // could be examined. As a result, the destructor callout appends its
+ // elements to the constructor's values and the result is printed.
+ zero_results();
+ callout_handle.reset();
+
+ EXPECT_EQ("110120", resultCalloutString(0));
+ EXPECT_EQ((110 + 120), resultCalloutInt(0));
+}
+
+// Dynamic callout registration and deregistration.
+// The following are the dynamic registration/deregistration callouts.
+
+
+// Add callout_78_alpha - adds a callout to hook alpha that appends "78x"
+// (where "x" is the callout handle) to the current output.
+
+int
+callout78(CalloutHandle& callout_handle) {
+ return (execute(callout_handle, 7, 8));
+}
+
+int
+add_callout78_alpha(CalloutHandle& callout_handle) {
+ callout_handle.getLibraryHandle().registerCallout("alpha", callout78);
+ return (0);
+}
+
+int
+delete_callout78_alpha(CalloutHandle& callout_handle) {
+ static_cast<void>(
+ callout_handle.getLibraryHandle().deregisterCallout("alpha",
+ callout78));
+ return (0);
+}
+
+// Check that a callout can register another callout on a different hook.
+
+TEST_F(HandlesTest, DynamicRegistrationAnotherHook) {
+ // Register callouts for the different libraries.
+ CalloutHandle handle(getCalloutManager());
+
+ // Set up callouts on "alpha".
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("alpha", callout11);
+ getCalloutManager()->registerCallout("delta", print1);
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->registerCallout("alpha", callout21);
+ getCalloutManager()->registerCallout("delta", print2);
+ getCalloutManager()->setLibraryIndex(2);
+ getCalloutManager()->registerCallout("alpha", callout31);
+ getCalloutManager()->registerCallout("delta", print3);
+
+ // ... and on "beta", set up the function to add a hook to alpha (but only
+ // for library 1).
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->registerCallout("beta", add_callout78_alpha);
+
+ // See what we get for calling the callouts on alpha first.
+ CalloutHandle callout_handle_1(getCalloutManager());
+ callout_handle_1.setArgument("handle_num", static_cast<int>(1));
+ getCalloutManager()->callCallouts(alpha_index_, callout_handle_1);
+
+ zero_results();
+ getCalloutManager()->callCallouts(delta_index_, callout_handle_1);
+ EXPECT_EQ("111", resultCalloutString(0));
+ EXPECT_EQ("211", resultCalloutString(1));
+ EXPECT_EQ("311", resultCalloutString(2));
+
+ // All as expected, now call the callouts on beta. This should add a
+ // callout to the list of callouts for alpha, which we should see when
+ // we run the test again.
+ getCalloutManager()->callCallouts(beta_index_, callout_handle_1);
+
+ // Use a new callout handle so as to get fresh callout context.
+ CalloutHandle callout_handle_2(getCalloutManager());
+ callout_handle_2.setArgument("handle_num", static_cast<int>(2));
+ getCalloutManager()->callCallouts(alpha_index_, callout_handle_2);
+
+ zero_results();
+ getCalloutManager()->callCallouts(delta_index_, callout_handle_2);
+ EXPECT_EQ("112", resultCalloutString(0));
+ EXPECT_EQ("212782", resultCalloutString(1));
+ EXPECT_EQ("312", resultCalloutString(2));
+}
+
+// Check that a callout can register another callout on the same hook.
+// Note that the registration only applies to a subsequent invocation of
+// callCallouts, not to the current one. In other words, if
+//
+// * the callout list for a library is "A then B then C"
+// * when callCallouts is executed "B" adds "D" to that list,
+//
+// ... the current execution of callCallouts only executes A, B and C. A
+// subsequent invocation will execute A, B, C then D.
+
+TEST_F(HandlesTest, DynamicRegistrationSameHook) {
+ // Register callouts for the different libraries.
+ CalloutHandle handle(getCalloutManager());
+
+ // Set up callouts on "alpha".
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("alpha", callout11);
+ getCalloutManager()->registerCallout("alpha", add_callout78_alpha);
+ getCalloutManager()->registerCallout("delta", print1);
+
+ // See what we get for calling the callouts on alpha first.
+ CalloutHandle callout_handle_1(getCalloutManager());
+ callout_handle_1.setArgument("handle_num", static_cast<int>(1));
+ getCalloutManager()->callCallouts(alpha_index_, callout_handle_1);
+ zero_results();
+ getCalloutManager()->callCallouts(delta_index_, callout_handle_1);
+ EXPECT_EQ("111", resultCalloutString(0));
+
+ // Run it again - we should have added something to this hook.
+ CalloutHandle callout_handle_2(getCalloutManager());
+ callout_handle_2.setArgument("handle_num", static_cast<int>(2));
+ getCalloutManager()->callCallouts(alpha_index_, callout_handle_2);
+ zero_results();
+ getCalloutManager()->callCallouts(delta_index_, callout_handle_2);
+ EXPECT_EQ("112782", resultCalloutString(0));
+
+ // And a third time...
+ CalloutHandle callout_handle_3(getCalloutManager());
+ callout_handle_3.setArgument("handle_num", static_cast<int>(3));
+ getCalloutManager()->callCallouts(alpha_index_, callout_handle_3);
+ zero_results();
+ getCalloutManager()->callCallouts(delta_index_, callout_handle_3);
+ EXPECT_EQ("113783783", resultCalloutString(0));
+}
+
+// Deregistration of a callout from a different hook
+
+TEST_F(HandlesTest, DynamicDeregistrationDifferentHook) {
+ // Register callouts for the different libraries.
+ CalloutHandle handle(getCalloutManager());
+
+ // Set up callouts on "alpha".
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("alpha", callout11);
+ getCalloutManager()->registerCallout("alpha", callout78);
+ getCalloutManager()->registerCallout("alpha", callout11);
+ getCalloutManager()->registerCallout("delta", print1);
+
+ getCalloutManager()->registerCallout("beta", delete_callout78_alpha);
+
+ // Call the callouts on alpha
+ CalloutHandle callout_handle_1(getCalloutManager());
+ callout_handle_1.setArgument("handle_num", static_cast<int>(1));
+ getCalloutManager()->callCallouts(alpha_index_, callout_handle_1);
+
+ zero_results();
+ getCalloutManager()->callCallouts(delta_index_, callout_handle_1);
+ EXPECT_EQ("111781111", resultCalloutString(0));
+
+ // Run the callouts on hook beta to remove the callout on alpha.
+ getCalloutManager()->callCallouts(beta_index_, callout_handle_1);
+
+ // The run of the callouts should have altered the callout list on the
+ // first library for hook alpha, so call again to make sure.
+ CalloutHandle callout_handle_2(getCalloutManager());
+ callout_handle_2.setArgument("handle_num", static_cast<int>(2));
+ getCalloutManager()->callCallouts(alpha_index_, callout_handle_2);
+
+ zero_results();
+ getCalloutManager()->callCallouts(delta_index_, callout_handle_2);
+ EXPECT_EQ("112112", resultCalloutString(0));
+}
+
+// Deregistration of a callout from the same hook
+
+TEST_F(HandlesTest, DynamicDeregistrationSameHook) {
+ // Register callouts for the different libraries.
+ CalloutHandle handle(getCalloutManager());
+
+ // Set up callouts on "alpha".
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("alpha", callout11);
+ getCalloutManager()->registerCallout("alpha", delete_callout78_alpha);
+ getCalloutManager()->registerCallout("alpha", callout78);
+ getCalloutManager()->registerCallout("delta", print1);
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->registerCallout("alpha", callout21);
+ getCalloutManager()->registerCallout("alpha", callout78);
+ getCalloutManager()->registerCallout("delta", print2);
+
+ // Call the callouts on alpha
+ CalloutHandle callout_handle_1(getCalloutManager());
+ callout_handle_1.setArgument("handle_num", static_cast<int>(1));
+ getCalloutManager()->callCallouts(alpha_index_, callout_handle_1);
+
+ zero_results();
+ getCalloutManager()->callCallouts(delta_index_, callout_handle_1);
+ EXPECT_EQ("111781", resultCalloutString(0));
+ EXPECT_EQ("211781", resultCalloutString(1));
+
+ // The run of the callouts should have altered the callout list on the
+ // first library for hook alpha, so call again to make sure.
+ CalloutHandle callout_handle_2(getCalloutManager());
+ callout_handle_2.setArgument("handle_num", static_cast<int>(2));
+ getCalloutManager()->callCallouts(alpha_index_, callout_handle_2);
+
+ zero_results();
+ getCalloutManager()->callCallouts(delta_index_, callout_handle_2);
+ EXPECT_EQ("112", resultCalloutString(0));
+ EXPECT_EQ("212782", resultCalloutString(1));
+}
+
+// Testing the operation of the "skip" flag. Callouts print the value
+// they see in the flag and either leave it unchanged, set it or clear it.
+
+int
+calloutPrintSkip(CalloutHandle& handle) {
+ static const std::string YES("Y");
+ static const std::string NO("N");
+
+ HandlesTest::common_string_ = HandlesTest::common_string_ +
+ (handle.getSkip() ? YES : NO);
+ return (0);
+}
+
+int
+calloutSetSkip(CalloutHandle& handle) {
+ static_cast<void>(calloutPrintSkip(handle));
+ handle.setSkip(true);
+ return (0);
+}
+
+int
+calloutClearSkip(CalloutHandle& handle) {
+ static_cast<void>(calloutPrintSkip(handle));
+ handle.setSkip(false);
+ return (0);
+}
+
+// Do a series of tests, returning with the skip flag set "true".
+
+TEST_F(HandlesTest, ReturnSkipSet) {
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("alpha", calloutPrintSkip);
+ getCalloutManager()->registerCallout("alpha", calloutSetSkip);
+ getCalloutManager()->registerCallout("alpha", calloutSetSkip);
+ getCalloutManager()->registerCallout("alpha", calloutClearSkip);
+
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->registerCallout("alpha", calloutPrintSkip);
+ getCalloutManager()->registerCallout("alpha", calloutSetSkip);
+ getCalloutManager()->registerCallout("alpha", calloutSetSkip);
+ getCalloutManager()->registerCallout("alpha", calloutClearSkip);
+ getCalloutManager()->registerCallout("alpha", calloutClearSkip);
+
+ getCalloutManager()->setLibraryIndex(2);
+ getCalloutManager()->registerCallout("alpha", calloutPrintSkip);
+ getCalloutManager()->registerCallout("alpha", calloutSetSkip);
+ getCalloutManager()->registerCallout("alpha", calloutClearSkip);
+ getCalloutManager()->registerCallout("alpha", calloutSetSkip);
+
+ CalloutHandle callout_handle(getCalloutManager());
+ getCalloutManager()->callCallouts(alpha_index_, callout_handle);
+
+ // Check result. For each of visual checking, the expected string is
+ // divided into sections corresponding to the blocks of callouts above.
+ EXPECT_EQ(std::string("NNYY" "NNYYN" "NNYN"), common_string_);
+
+ // ... and check that the skip flag on exit from callCallouts is set.
+ EXPECT_TRUE(callout_handle.getSkip());
+}
+
+// Repeat the test, returning with the skip flag clear.
+TEST_F(HandlesTest, ReturnSkipClear) {
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("alpha", calloutSetSkip);
+ getCalloutManager()->registerCallout("alpha", calloutSetSkip);
+ getCalloutManager()->registerCallout("alpha", calloutClearSkip);
+
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->registerCallout("alpha", calloutPrintSkip);
+ getCalloutManager()->registerCallout("alpha", calloutSetSkip);
+ getCalloutManager()->registerCallout("alpha", calloutClearSkip);
+ getCalloutManager()->registerCallout("alpha", calloutSetSkip);
+ getCalloutManager()->registerCallout("alpha", calloutClearSkip);
+ getCalloutManager()->registerCallout("alpha", calloutClearSkip);
+
+ getCalloutManager()->setLibraryIndex(2);
+ getCalloutManager()->registerCallout("alpha", calloutClearSkip);
+ getCalloutManager()->registerCallout("alpha", calloutPrintSkip);
+ getCalloutManager()->registerCallout("alpha", calloutSetSkip);
+ getCalloutManager()->registerCallout("alpha", calloutClearSkip);
+
+ CalloutHandle callout_handle(getCalloutManager());
+ getCalloutManager()->callCallouts(alpha_index_, callout_handle);
+
+ // Check result. For each of visual checking, the expected string is
+ // divided into sections corresponding to the blocks of callouts above.
+ EXPECT_EQ(std::string("NYY" "NNYNYN" "NNNY"), common_string_);
+
+ // ... and check that the skip flag on exit from callCallouts is set.
+ EXPECT_FALSE(callout_handle.getSkip());
+}
+
+// The next set of callouts do a similar thing to the above "skip" tests,
+// but alter the value of a string argument. This is for testing that the
+// a callout is able to change an argument and return it to the caller.
+
+const char* MODIFIED_ARG = "modified_arg";
+
+int
+calloutSetArgumentCommon(CalloutHandle& handle, const char* what) {
+ std::string modified_arg = "";
+
+ handle.getArgument(MODIFIED_ARG, modified_arg);
+ modified_arg = modified_arg + std::string(what);
+ handle.setArgument(MODIFIED_ARG, modified_arg);
+ return (0);
+}
+
+int
+calloutSetArgumentYes(CalloutHandle& handle) {
+ return (calloutSetArgumentCommon(handle, "Y"));
+}
+
+int
+calloutSetArgumentNo(CalloutHandle& handle) {
+ return (calloutSetArgumentCommon(handle, "N"));
+}
+
+// ... and a callout to just copy the argument to the "common_string_" variable
+// but otherwise not alter it.
+
+int
+calloutPrintArgument(CalloutHandle& handle) {
+ handle.getArgument(MODIFIED_ARG, HandlesTest::common_string_);
+ return (0);
+}
+
+TEST_F(HandlesTest, CheckModifiedArgument) {
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("alpha", calloutSetArgumentYes);
+ getCalloutManager()->registerCallout("alpha", calloutSetArgumentNo);
+ getCalloutManager()->registerCallout("alpha", calloutSetArgumentNo);
+
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->registerCallout("alpha", calloutSetArgumentYes);
+ getCalloutManager()->registerCallout("alpha", calloutSetArgumentYes);
+ getCalloutManager()->registerCallout("alpha", calloutPrintArgument);
+ getCalloutManager()->registerCallout("alpha", calloutSetArgumentNo);
+ getCalloutManager()->registerCallout("alpha", calloutSetArgumentNo);
+
+ getCalloutManager()->setLibraryIndex(2);
+ getCalloutManager()->registerCallout("alpha", calloutSetArgumentYes);
+ getCalloutManager()->registerCallout("alpha", calloutSetArgumentNo);
+ getCalloutManager()->registerCallout("alpha", calloutSetArgumentYes);
+
+ // Create the argument with an initial empty string value. Then call the
+ // sequence of callouts above.
+ CalloutHandle callout_handle(getCalloutManager());
+ std::string modified_arg = "";
+ callout_handle.setArgument(MODIFIED_ARG, modified_arg);
+ getCalloutManager()->callCallouts(alpha_index_, callout_handle);
+
+ // Check the intermediate and results. For visual checking, the expected
+ // string is divided into sections corresponding to the blocks of callouts
+ // above.
+ EXPECT_EQ(std::string("YNN" "YY"), common_string_);
+
+ callout_handle.getArgument(MODIFIED_ARG, modified_arg);
+ EXPECT_EQ(std::string("YNN" "YYNN" "YNY"), modified_arg);
+}
+
+// Test that the CalloutHandle provides the name of the hook to which the
+// callout is attached.
+
+int
+callout_hook_name(CalloutHandle& callout_handle) {
+ HandlesTest::common_string_ = callout_handle.getHookName();
+ return (0);
+}
+
+int
+callout_hook_dummy(CalloutHandle&) {
+ return (0);
+}
+
+TEST_F(HandlesTest, HookName) {
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("alpha", callout_hook_name);
+ getCalloutManager()->registerCallout("beta", callout_hook_name);
+
+ // Call alpha and beta callouts and check the hook to which they belong.
+ CalloutHandle callout_handle(getCalloutManager());
+
+ EXPECT_EQ(std::string(""), HandlesTest::common_string_);
+
+ getCalloutManager()->callCallouts(alpha_index_, callout_handle);
+ EXPECT_EQ(std::string("alpha"), HandlesTest::common_string_);
+
+ getCalloutManager()->callCallouts(beta_index_, callout_handle);
+ EXPECT_EQ(std::string("beta"), HandlesTest::common_string_);
+
+ // Make sure that the callout accesses the name even if it is not the
+ // only callout in the list.
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->registerCallout("gamma", callout_hook_dummy);
+ getCalloutManager()->registerCallout("gamma", callout_hook_name);
+ getCalloutManager()->registerCallout("gamma", callout_hook_dummy);
+
+ EXPECT_EQ(std::string("beta"), HandlesTest::common_string_);
+ getCalloutManager()->callCallouts(gamma_index_, callout_handle);
+ EXPECT_EQ(std::string("gamma"), HandlesTest::common_string_);
+}
+
+} // Anonymous namespace
+
diff --git a/src/lib/hooks/tests/hooks_manager_unittest.cc b/src/lib/hooks/tests/hooks_manager_unittest.cc
new file mode 100644
index 0000000..c5dba60
--- /dev/null
+++ b/src/lib/hooks/tests/hooks_manager_unittest.cc
@@ -0,0 +1,454 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <hooks/callout_handle.h>
+#include <hooks/hooks_manager.h>
+#include <hooks/server_hooks.h>
+
+#include <hooks/tests/common_test_class.h>
+#include <hooks/tests/test_libraries.h>
+
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+
+#include <algorithm>
+#include <string>
+
+
+using namespace isc;
+using namespace isc::hooks;
+using namespace std;
+
+namespace {
+
+/// @brief Hooks manager collection test class
+
+class HooksManagerTest : public ::testing::Test,
+ public HooksCommonTestClass {
+public:
+ /// @brief Constructor
+ ///
+ /// Reset the hooks manager. The hooks manager is a singleton, so needs
+ /// to be reset for each test.
+ HooksManagerTest() {
+ HooksManager::unloadLibraries();
+ }
+
+ /// @brief Destructor
+ ///
+ /// Unload all libraries.
+ ~HooksManagerTest() {
+ HooksManager::unloadLibraries();
+ }
+
+
+ /// @brief Call callouts test
+ ///
+ /// See the header for HooksCommonTestClass::execute for details.
+ ///
+ /// @param r0...r3, d1..d3 Values and intermediate values expected. They
+ /// are ordered so that the variables appear in the argument list in
+ /// the order they are used.
+ void executeCallCallouts(int r0, int d1, int r1, int d2, int r2, int d3,
+ int r3) {
+ static const char* COMMON_TEXT = " callout returned the wong value";
+ static const char* RESULT = "result";
+
+ // Get a CalloutHandle for the calculation.
+ CalloutHandlePtr handle = HooksManager::createCalloutHandle();
+
+ // Initialize the argument RESULT. This simplifies testing by
+ // eliminating the generation of an exception when we try the unload
+ // test. In that case, RESULT is unchanged.
+ int result = -1;
+ handle->setArgument(RESULT, result);
+
+ // Seed the calculation.
+ HooksManager::callCallouts(isc::hooks::ServerHooks::CONTEXT_CREATE,
+ *handle);
+ handle->getArgument(RESULT, result);
+ EXPECT_EQ(r0, result) << "context_create" << COMMON_TEXT;
+
+ // Perform the first calculation.
+ handle->setArgument("data_1", d1);
+ HooksManager::callCallouts(hookpt_one_index_, *handle);
+ handle->getArgument(RESULT, result);
+ EXPECT_EQ(r1, result) << "hookpt_one" << COMMON_TEXT;
+
+ // ... the second ...
+ handle->setArgument("data_2", d2);
+ HooksManager::callCallouts(hookpt_two_index_, *handle);
+ handle->getArgument(RESULT, result);
+ EXPECT_EQ(r2, result) << "hookpt_two" << COMMON_TEXT;
+
+ // ... and the third.
+ handle->setArgument("data_3", d3);
+ HooksManager::callCallouts(hookpt_three_index_, *handle);
+ handle->getArgument(RESULT, result);
+ EXPECT_EQ(r3, result) << "hookpt_three" << COMMON_TEXT;
+ }
+
+};
+
+// This is effectively the same test as for LibraryManager, but using the
+// HooksManager object.
+
+TEST_F(HooksManagerTest, LoadLibraries) {
+
+ // Set up the list of libraries to be loaded.
+ std::vector<std::string> library_names;
+ library_names.push_back(std::string(FULL_CALLOUT_LIBRARY));
+ library_names.push_back(std::string(BASIC_CALLOUT_LIBRARY));
+
+ // Load the libraries.
+ EXPECT_TRUE(HooksManager::loadLibraries(library_names));
+
+ // Execute the callouts. The first library implements the calculation.
+ //
+ // r3 = (7 * d1 - d2) * d3
+ //
+ // The last-loaded library implements the calculation
+ //
+ // r3 = (10 + d1) * d2 - d3
+ //
+ // Putting the processing for each library together in the appropriate
+ // order, we get:
+ //
+ // r3 = ((10 * d1 + d1) - d2) * d2 * d3 - d3
+ {
+ SCOPED_TRACE("Calculation with libraries loaded");
+ executeCallCallouts(10, 3, 33, 2, 62, 3, 183);
+ }
+
+ // Try unloading the libraries.
+ EXPECT_NO_THROW(HooksManager::unloadLibraries());
+
+ // Re-execute the calculation - callouts can be called but as nothing
+ // happens, the result should always be -1.
+ {
+ SCOPED_TRACE("Calculation with libraries not loaded");
+ executeCallCallouts(-1, 3, -1, 22, -1, 83, -1);
+ }
+}
+
+// This is effectively the same test as above, but with a library generating
+// an error when loaded. It is expected that the failing library will not be
+// loaded, but others will be.
+
+TEST_F(HooksManagerTest, LoadLibrariesWithError) {
+
+ // Set up the list of libraries to be loaded.
+ std::vector<std::string> library_names;
+ library_names.push_back(std::string(FULL_CALLOUT_LIBRARY));
+ library_names.push_back(std::string(INCORRECT_VERSION_LIBRARY));
+ library_names.push_back(std::string(BASIC_CALLOUT_LIBRARY));
+
+ // Load the libraries. We expect a failure return because one of the
+ // libraries fails to load.
+ EXPECT_FALSE(HooksManager::loadLibraries(library_names));
+
+ // Execute the callouts. The first library implements the calculation.
+ //
+ // r3 = (7 * d1 - d2) * d3
+ //
+ // The last-loaded library implements the calculation
+ //
+ // r3 = (10 + d1) * d2 - d3
+ //
+ // Putting the processing for each library together in the appropriate
+ // order, we get:
+ //
+ // r3 = ((10 * d1 + d1) - d2) * d2 * d3 - d3
+ {
+ SCOPED_TRACE("Calculation with libraries loaded");
+ executeCallCallouts(10, 3, 33, 2, 62, 3, 183);
+ }
+
+ // Try unloading the libraries.
+ EXPECT_NO_THROW(HooksManager::unloadLibraries());
+
+ // Re-execute the calculation - callouts can be called but as nothing
+ // happens, the result should always be -1.
+ {
+ SCOPED_TRACE("Calculation with libraries not loaded");
+ executeCallCallouts(-1, 3, -1, 22, -1, 83, -1);
+ }
+}
+
+// Test that we can unload a set of libraries while we have a CalloutHandle
+// created on them in existence, and can delete the handle afterwards.
+
+TEST_F(HooksManagerTest, CalloutHandleUnloadLibrary) {
+
+ // Set up the list of libraries to be loaded.
+ std::vector<std::string> library_names;
+ library_names.push_back(std::string(FULL_CALLOUT_LIBRARY));
+
+ // Load the libraries.
+ EXPECT_TRUE(HooksManager::loadLibraries(library_names));
+
+ // Execute the callouts. Thiis library implements:
+ //
+ // r3 = (7 * d1 - d2) * d3
+ {
+ SCOPED_TRACE("Calculation with full callout library loaded");
+ executeCallCallouts(7, 4, 28, 8, 20, 2, 40);
+ }
+
+ // Get an outstanding callout handle on this library.
+ CalloutHandlePtr handle = HooksManager::createCalloutHandle();
+
+ // Execute once of the callouts again to ensure that the handle contains
+ // memory allocated by the library.
+ HooksManager::callCallouts(ServerHooks::CONTEXT_CREATE, *handle);
+
+ // Unload the libraries.
+ HooksManager::unloadLibraries();
+
+ // Deleting the callout handle should not cause a segmentation fault.
+ handle.reset();
+}
+
+// Test that we can load a new set of libraries while we have a CalloutHandle
+// created on them in existence, and can delete the handle afterwards.
+
+TEST_F(HooksManagerTest, CalloutHandleLoadLibrary) {
+
+ // Set up the list of libraries to be loaded.
+ std::vector<std::string> library_names;
+ library_names.push_back(std::string(FULL_CALLOUT_LIBRARY));
+
+ // Load the libraries.
+ EXPECT_TRUE(HooksManager::loadLibraries(library_names));
+
+ // Execute the callouts. Thiis library implements:
+ //
+ // r3 = (7 * d1 - d2) * d3
+ {
+ SCOPED_TRACE("Calculation with full callout library loaded");
+ executeCallCallouts(7, 4, 28, 8, 20, 2, 40);
+ }
+
+ // Get an outstanding callout handle on this library and execute one of
+ // the callouts again to ensure that the handle contains memory allocated
+ // by the library.
+ CalloutHandlePtr handle = HooksManager::createCalloutHandle();
+ HooksManager::callCallouts(ServerHooks::CONTEXT_CREATE, *handle);
+
+ // Load a new library that implements the calculation
+ //
+ // r3 = (10 + d1) * d2 - d3
+ std::vector<std::string> new_library_names;
+ new_library_names.push_back(std::string(BASIC_CALLOUT_LIBRARY));
+
+ // Load the libraries.
+ EXPECT_TRUE(HooksManager::loadLibraries(new_library_names));
+
+ // Execute the calculation. Note that we still have the CalloutHandle
+ // for the old library: however, this should not affect the new calculation.
+ {
+ SCOPED_TRACE("Calculation with basic callout library loaded");
+ executeCallCallouts(10, 7, 17, 3, 51, 16, 35);
+ }
+
+ // Deleting the old callout handle should not cause a segmentation fault.
+ handle.reset();
+}
+
+// This is effectively the same test as the LoadLibraries test.
+
+TEST_F(HooksManagerTest, ReloadSameLibraries) {
+
+ // Set up the list of libraries to be loaded.
+ std::vector<std::string> library_names;
+ library_names.push_back(std::string(FULL_CALLOUT_LIBRARY));
+ library_names.push_back(std::string(BASIC_CALLOUT_LIBRARY));
+
+ // Load the libraries.
+ EXPECT_TRUE(HooksManager::loadLibraries(library_names));
+
+ // Execute the callouts. See the LoadLibraries test for an explanation of
+ // the calculation.
+ {
+ SCOPED_TRACE("Calculation with libraries loaded");
+ executeCallCallouts(10, 3, 33, 2, 62, 3, 183);
+ }
+
+ // Try reloading the libraries and re-execute the calculation - we should
+ // get the same results.
+ EXPECT_NO_THROW(HooksManager::loadLibraries(library_names));
+ {
+ SCOPED_TRACE("Calculation with libraries reloaded");
+ executeCallCallouts(10, 3, 33, 2, 62, 3, 183);
+ }
+}
+
+TEST_F(HooksManagerTest, ReloadLibrariesReverseOrder) {
+
+ // Set up the list of libraries to be loaded and load them.
+ std::vector<std::string> library_names;
+ library_names.push_back(std::string(FULL_CALLOUT_LIBRARY));
+ library_names.push_back(std::string(BASIC_CALLOUT_LIBRARY));
+ EXPECT_TRUE(HooksManager::loadLibraries(library_names));
+
+ // Execute the callouts. The first library implements the calculation.
+ //
+ // r3 = (7 * d1 - d2) * d3
+ //
+ // The last-loaded library implements the calculation
+ //
+ // r3 = (10 + d1) * d2 - d3
+ //
+ // Putting the processing for each library together in the given order
+ // gives.
+ //
+ // r3 = ((10 * d1 + d1) - d2) * d2 * d3 - d3
+ {
+ SCOPED_TRACE("Calculation with libraries loaded");
+ executeCallCallouts(10, 3, 33, 2, 62, 3, 183);
+ }
+
+ // Reload the libraries in the reverse order.
+ std::reverse(library_names.begin(), library_names.end());
+ EXPECT_TRUE(HooksManager::loadLibraries(library_names));
+
+ // The calculation in the reverse order gives:
+ //
+ // r3 = ((((7 + d1) * d1) * d2 - d2) - d3) * d3
+ {
+ SCOPED_TRACE("Calculation with libraries loaded in reverse order");
+ executeCallCallouts(7, 3, 30, 3, 87, 7, 560);
+ }
+}
+
+// Local callouts for the test of server-registered callouts.
+
+namespace {
+
+ int
+testPreCallout(CalloutHandle& handle) {
+ handle.setArgument("result", static_cast<int>(1027));
+ return (0);
+}
+
+int
+testPostCallout(CalloutHandle& handle) {
+ int result;
+ handle.getArgument("result", result);
+ result *= 2;
+ handle.setArgument("result", result);
+ return (0);
+}
+
+}
+
+// The next test registers the pre and post- callouts above for hook hookpt_two,
+// and checks they are called.
+
+TEST_F(HooksManagerTest, PrePostCalloutTest) {
+
+ // Load a single library.
+ std::vector<std::string> library_names;
+ library_names.push_back(std::string(FULL_CALLOUT_LIBRARY));
+ EXPECT_TRUE(HooksManager::loadLibraries(library_names));
+
+ // Load the pre- and post- callouts.
+ HooksManager::preCalloutsLibraryHandle().registerCallout("hookpt_two",
+ testPreCallout);
+ HooksManager::postCalloutsLibraryHandle().registerCallout("hookpt_two",
+ testPostCallout);
+
+ // Execute the callouts. hookpt_two implements the calculation:
+ //
+ // "result - data_2"
+ //
+ // With the pre- and post- callouts above, the result expected is
+ //
+ // (1027 - data_2) * 2
+ CalloutHandlePtr handle = HooksManager::createCalloutHandle();
+ handle->setArgument("result", static_cast<int>(0));
+ handle->setArgument("data_2", static_cast<int>(15));
+
+ HooksManager::callCallouts(hookpt_two_index_, *handle);
+
+ int result = 0;
+ handle->getArgument("result", result);
+ EXPECT_EQ(2024, result);
+
+ // ... and check that the pre- and post- callout functions don't survive a
+ // reload.
+ EXPECT_TRUE(HooksManager::loadLibraries(library_names));
+ handle = HooksManager::createCalloutHandle();
+
+ handle->setArgument("result", static_cast<int>(0));
+ handle->setArgument("data_2", static_cast<int>(15));
+
+ HooksManager::callCallouts(hookpt_two_index_, *handle);
+
+ result = 0;
+ handle->getArgument("result", result);
+ EXPECT_EQ(-15, result);
+}
+
+// Check that everything works even with no libraries loaded. First that
+// calloutsPresent() always returns false.
+
+TEST_F(HooksManagerTest, NoLibrariesCalloutsPresent) {
+ // No callouts should be present on any hooks.
+ EXPECT_FALSE(HooksManager::calloutsPresent(hookpt_one_index_));
+ EXPECT_FALSE(HooksManager::calloutsPresent(hookpt_two_index_));
+ EXPECT_FALSE(HooksManager::calloutsPresent(hookpt_three_index_));
+}
+
+TEST_F(HooksManagerTest, NoLibrariesCallCallouts) {
+ executeCallCallouts(-1, 3, -1, 22, -1, 83, -1);
+}
+
+// Test the encapsulation of the ServerHooks::registerHook() method.
+
+TEST_F(HooksManagerTest, RegisterHooks) {
+ ServerHooks::getServerHooks().reset();
+ EXPECT_EQ(2, ServerHooks::getServerHooks().getCount());
+
+ // Check that the hook indexes are as expected. (Use temporary variables
+ // as it appears that Google test can't access the constants.)
+ int sh_cc = ServerHooks::CONTEXT_CREATE;
+ int hm_cc = HooksManager::CONTEXT_CREATE;
+ EXPECT_EQ(sh_cc, hm_cc);
+
+ int sh_cd = ServerHooks::CONTEXT_DESTROY;
+ int hm_cd = HooksManager::CONTEXT_DESTROY;
+ EXPECT_EQ(sh_cd, hm_cd);
+
+ // Register a few hooks and check we have the indexes as expected.
+ EXPECT_EQ(2, HooksManager::registerHook(string("alpha")));
+ EXPECT_EQ(3, HooksManager::registerHook(string("beta")));
+ EXPECT_EQ(4, HooksManager::registerHook(string("gamma")));
+ EXPECT_THROW(static_cast<void>(HooksManager::registerHook(string("alpha"))),
+ DuplicateHook);
+
+ // ... an check the hooks are as we expect.
+ EXPECT_EQ(5, ServerHooks::getServerHooks().getCount());
+ vector<string> names = ServerHooks::getServerHooks().getHookNames();
+ sort(names.begin(), names.end());
+
+ EXPECT_EQ(string("alpha"), names[0]);
+ EXPECT_EQ(string("beta"), names[1]);
+ EXPECT_EQ(string("context_create"), names[2]);
+ EXPECT_EQ(string("context_destroy"), names[3]);
+ EXPECT_EQ(string("gamma"), names[4]);
+}
+
+
+} // Anonymous namespace
diff --git a/src/lib/hooks/tests/incorrect_version_library.cc b/src/lib/hooks/tests/incorrect_version_library.cc
new file mode 100644
index 0000000..bb6eedf
--- /dev/null
+++ b/src/lib/hooks/tests/incorrect_version_library.cc
@@ -0,0 +1,33 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/// @file
+/// @brief Incorrect version function test
+///
+/// This is source of a test library for various test (LibraryManager and
+/// HooksManager). The characteristics of the library produced from this
+/// file are:
+///
+/// - It contains the version() framework function only, which returns an
+/// incorrect version number.
+
+#include <hooks/hooks.h>
+
+extern "C" {
+
+int version() {
+ return (BIND10_HOOKS_VERSION + 1);
+}
+
+};
diff --git a/src/lib/hooks/tests/library_manager_collection_unittest.cc b/src/lib/hooks/tests/library_manager_collection_unittest.cc
new file mode 100644
index 0000000..549142f
--- /dev/null
+++ b/src/lib/hooks/tests/library_manager_collection_unittest.cc
@@ -0,0 +1,184 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <hooks/callout_handle.h>
+#include <hooks/callout_manager.h>
+#include <hooks/library_manager.h>
+#include <hooks/library_manager_collection.h>
+
+#include <hooks/tests/common_test_class.h>
+#include <hooks/tests/test_libraries.h>
+
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+
+#include <algorithm>
+#include <string>
+
+
+using namespace isc;
+using namespace isc::hooks;
+using namespace std;
+
+namespace {
+
+/// @brief Library manager collection test class
+
+class LibraryManagerCollectionTest : public ::testing::Test,
+ public HooksCommonTestClass {
+};
+
+/// @brief Public library manager collection class
+///
+/// This is an instance of the LibraryManagerCollection class but with the
+/// protected methods made public for test purposes.
+
+class PublicLibraryManagerCollection
+ : public isc::hooks::LibraryManagerCollection {
+public:
+ /// @brief Constructor
+ ///
+ /// @param List of libraries that this collection will manage. The order
+ /// of the libraries is important.
+ PublicLibraryManagerCollection(const std::vector<std::string>& libraries)
+ : LibraryManagerCollection(libraries)
+ {}
+
+ /// Public methods that call protected methods on the superclass.
+ using LibraryManagerCollection::unloadLibraries;
+};
+
+
+// This is effectively the same test as for LibraryManager, but using the
+// LibraryManagerCollection object.
+
+TEST_F(LibraryManagerCollectionTest, LoadLibraries) {
+
+ // Set up the list of libraries to be loaded.
+ std::vector<std::string> library_names;
+ library_names.push_back(std::string(FULL_CALLOUT_LIBRARY));
+ library_names.push_back(std::string(BASIC_CALLOUT_LIBRARY));
+
+ // Set up the library manager collection and get the callout manager we'll
+ // be using.
+ PublicLibraryManagerCollection lm_collection(library_names);
+
+ // Load the libraries.
+ EXPECT_TRUE(lm_collection.loadLibraries());
+ boost::shared_ptr<CalloutManager> manager =
+ lm_collection.getCalloutManager();
+
+ // Execute the callouts. The first library implements the calculation.
+ //
+ // r3 = (7 * d1 - d2) * d3
+ //
+ // The last-loaded library implements the calculation
+ //
+ // r3 = (10 + d1) * d2 - d3
+ //
+ // Putting the processing for each library together in the appropriate
+ // order, we get:
+ //
+ // r3 = ((10 * d1 + d1) - d2) * d2 * d3 - d3
+ {
+ SCOPED_TRACE("Doing calculation with libraries loaded");
+ executeCallCallouts(manager, 10, 3, 33, 2, 62, 3, 183);
+ }
+
+ // Try unloading the libraries.
+ EXPECT_NO_THROW(lm_collection.unloadLibraries());
+
+ // Re-execute the calculation - callouts can be called but as nothing
+ // happens, the result should always be -1.
+ {
+ SCOPED_TRACE("Doing calculation with libraries not loaded");
+ executeCallCallouts(manager, -1, 3, -1, 22, -1, 83, -1);
+ }
+}
+
+// This is effectively the same test as above, but with a library generating
+// an error when loaded. It is expected that the failing library will not be
+// loaded, but others will be.
+
+TEST_F(LibraryManagerCollectionTest, LoadLibrariesWithError) {
+
+ // Set up the list of libraries to be loaded.
+ std::vector<std::string> library_names;
+ library_names.push_back(std::string(FULL_CALLOUT_LIBRARY));
+ library_names.push_back(std::string(INCORRECT_VERSION_LIBRARY));
+ library_names.push_back(std::string(BASIC_CALLOUT_LIBRARY));
+
+ // Set up the library manager collection and get the callout manager we'll
+ // be using.
+ PublicLibraryManagerCollection lm_collection(library_names);
+
+ // Load the libraries. We expect a failure status to be returned as
+ // one of the libraries failed to load.
+ EXPECT_FALSE(lm_collection.loadLibraries());
+ boost::shared_ptr<CalloutManager> manager =
+ lm_collection.getCalloutManager();
+
+ // Expect only two libraries were loaded.
+ EXPECT_EQ(2, manager->getNumLibraries());
+
+ // Execute the callouts. The first library implements the calculation.
+ //
+ // r3 = (7 * d1 - d2) * d3
+ //
+ // The last-loaded library implements the calculation
+ //
+ // r3 = (10 + d1) * d2 - d3
+ //
+ // Putting the processing for each library together in the appropriate
+ // order, we get:
+ //
+ // r3 = ((10 * d1 + d1) - d2) * d2 * d3 - d3
+ {
+ SCOPED_TRACE("Doing calculation with libraries loaded");
+ executeCallCallouts(manager, 10, 3, 33, 2, 62, 3, 183);
+ }
+
+ // Try unloading the libraries.
+ EXPECT_NO_THROW(lm_collection.unloadLibraries());
+
+ // Re-execute the calculation - callouts can be called but as nothing
+ // happens, the result should always be -1.
+ {
+ SCOPED_TRACE("Doing calculation with libraries not loaded");
+ executeCallCallouts(manager, -1, 3, -1, 22, -1, 83, -1);
+ }
+}
+
+// Check that everything works even with no libraries loaded.
+
+TEST_F(LibraryManagerCollectionTest, NoLibrariesLoaded) {
+ // Set up the list of libraries to be loaded.
+ std::vector<std::string> library_names;
+
+ // Set up the library manager collection and get the callout manager we'll
+ // be using.
+ LibraryManagerCollection lm_collection(library_names);
+ EXPECT_TRUE(lm_collection.loadLibraries());
+ boost::shared_ptr<CalloutManager> manager =
+ lm_collection.getCalloutManager();
+
+ // Load the libraries.
+ EXPECT_TRUE(lm_collection.loadLibraries());
+
+ // Eecute the calculation - callouts can be called but as nothing
+ // happens, the result should always be -1.
+ executeCallCallouts(manager, -1, 3, -1, 22, -1, 83, -1);
+}
+
+} // Anonymous namespace
diff --git a/src/lib/hooks/tests/library_manager_unittest.cc b/src/lib/hooks/tests/library_manager_unittest.cc
new file mode 100644
index 0000000..c2f8cb7
--- /dev/null
+++ b/src/lib/hooks/tests/library_manager_unittest.cc
@@ -0,0 +1,555 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <hooks/callout_handle.h>
+#include <hooks/callout_manager.h>
+#include <hooks/library_manager.h>
+#include <hooks/server_hooks.h>
+
+#include <hooks/tests/common_test_class.h>
+#include <hooks/tests/marker_file.h>
+#include <hooks/tests/test_libraries.h>
+
+#include <gtest/gtest.h>
+
+#include <algorithm>
+#include <fstream>
+#include <string>
+
+#include <unistd.h>
+
+
+using namespace isc;
+using namespace isc::hooks;
+using namespace std;
+
+namespace {
+
+/// @brief Library manager test class
+
+class LibraryManagerTest : public ::testing::Test,
+ public HooksCommonTestClass {
+public:
+ /// @brief Constructor
+ ///
+ /// Initializes the CalloutManager object used in the tests. It sets it
+ /// up with the hooks initialized in the HooksCommonTestClass object and
+ /// with four libraries.
+ LibraryManagerTest() {
+ callout_manager_.reset(new CalloutManager(4));
+
+ // Ensure the marker file is not present at the start of a test.
+ static_cast<void>(unlink(MARKER_FILE));
+ }
+
+ /// @brief Destructor
+ ///
+ /// Ensures a marker file is removed after each test.
+ ~LibraryManagerTest() {
+ static_cast<void>(unlink(MARKER_FILE));
+ }
+
+ /// @brief Marker file present
+ ///
+ /// Convenience function to check whether a marker file is present. It
+ /// does this by opening the file.
+ ///
+ /// @return true if the marker file is present.
+ bool markerFilePresent() const {
+
+ // Try to open it.
+ std::fstream marker;
+ marker.open(MARKER_FILE, std::fstream::in);
+
+ // Check if it is open and close it if so.
+ bool exists = marker.is_open();
+ if (exists) {
+ marker.close();
+ }
+
+ return (exists);
+ }
+
+ /// @brief Call callouts test
+ ///
+ /// A wrapper around the method of the same name in the HooksCommonTestClass
+ /// object, this passes this class's CalloutManager to that method.
+ ///
+ /// @param r0...r3, d1..d3 Values and intermediate values expected. They
+ /// are ordered so that the variables appear in the argument list in
+ /// the order they are used. See HooksCommonTestClass::execute for
+ /// a full description. (rN is used to indicate an expected result,
+ /// dN is data to be passed to the calculation.)
+ void executeCallCallouts(int r0, int d1, int r1, int d2, int r2, int d3,
+ int r3) {
+ HooksCommonTestClass::executeCallCallouts(callout_manager_, r0, d1,
+ r1, d2, r2, d3, r3);
+ }
+
+ /// Callout manager used for the test.
+ boost::shared_ptr<CalloutManager> callout_manager_;
+};
+
+/// @brief Library manager class
+///
+/// This is an instance of the LibraryManager class but with the protected
+/// methods made public for test purposes.
+
+class PublicLibraryManager : public isc::hooks::LibraryManager {
+public:
+ /// @brief Constructor
+ ///
+ /// Stores the library name. The actual loading is done in loadLibrary().
+ ///
+ /// @param name Name of the library to load. This should be an absolute
+ /// path name.
+ /// @param index Index of this library. For all these tests, it will be
+ /// zero, as we are only using one library.
+ /// @param manager CalloutManager object
+ PublicLibraryManager(const std::string& name, int index,
+ const boost::shared_ptr<CalloutManager>& manager)
+ : LibraryManager(name, index, manager)
+ {}
+
+ /// Public methods that call protected methods on the superclass.
+ using LibraryManager::openLibrary;
+ using LibraryManager::closeLibrary;
+ using LibraryManager::checkVersion;
+ using LibraryManager::registerStandardCallouts;
+ using LibraryManager::runLoad;
+ using LibraryManager::runUnload;
+};
+
+
+// Check that openLibrary() reports an error when it can't find the specified
+// library.
+
+TEST_F(LibraryManagerTest, NoLibrary) {
+ PublicLibraryManager lib_manager(std::string(NOT_PRESENT_LIBRARY),
+ 0, callout_manager_);
+ EXPECT_FALSE(lib_manager.openLibrary());
+}
+
+// Check that the openLibrary() and closeLibrary() methods work.
+
+TEST_F(LibraryManagerTest, OpenClose) {
+ PublicLibraryManager lib_manager(std::string(BASIC_CALLOUT_LIBRARY),
+ 0, callout_manager_);
+
+ // Open and close the library
+ EXPECT_TRUE(lib_manager.openLibrary());
+ EXPECT_TRUE(lib_manager.closeLibrary());
+
+ // Check that a second close on an already closed library does not report
+ // an error.
+ EXPECT_TRUE(lib_manager.closeLibrary());
+}
+
+// Check that the code handles the case of a library with no version function.
+
+TEST_F(LibraryManagerTest, NoVersion) {
+ PublicLibraryManager lib_manager(std::string(NO_VERSION_LIBRARY),
+ 0, callout_manager_);
+ // Open should succeed.
+ EXPECT_TRUE(lib_manager.openLibrary());
+
+ // Version check should fail.
+ EXPECT_FALSE(lib_manager.checkVersion());
+
+ // Tidy up.
+ EXPECT_TRUE(lib_manager.closeLibrary());
+}
+
+// Check that the code handles the case of a library with a version function
+// that returns an incorrect version number.
+
+TEST_F(LibraryManagerTest, WrongVersion) {
+ PublicLibraryManager lib_manager(std::string(INCORRECT_VERSION_LIBRARY),
+ 0, callout_manager_);
+ // Open should succeed.
+ EXPECT_TRUE(lib_manager.openLibrary());
+
+ // Version check should fail.
+ EXPECT_FALSE(lib_manager.checkVersion());
+
+ // Tidy up.
+ EXPECT_TRUE(lib_manager.closeLibrary());
+}
+
+// Check that the code handles the case of a library where the version function
+// throws an exception.
+
+TEST_F(LibraryManagerTest, VersionException) {
+ PublicLibraryManager lib_manager(std::string(FRAMEWORK_EXCEPTION_LIBRARY),
+ 0, callout_manager_);
+ // Open should succeed.
+ EXPECT_TRUE(lib_manager.openLibrary());
+
+ // Version check should fail.
+ EXPECT_FALSE(lib_manager.checkVersion());
+
+ // Tidy up.
+ EXPECT_TRUE(lib_manager.closeLibrary());
+}
+
+// Tests that checkVersion() function succeeds in the case of a library with a
+// version function that returns the correct version number.
+
+TEST_F(LibraryManagerTest, CorrectVersionReturned) {
+ PublicLibraryManager lib_manager(std::string(BASIC_CALLOUT_LIBRARY),
+ 0, callout_manager_);
+ // Open should succeed.
+ EXPECT_TRUE(lib_manager.openLibrary());
+
+ // Version check should succeed.
+ EXPECT_TRUE(lib_manager.checkVersion());
+
+ // Tidy up.
+ EXPECT_TRUE(lib_manager.closeLibrary());
+}
+
+// Checks the registration of standard callouts.
+
+TEST_F(LibraryManagerTest, RegisterStandardCallouts) {
+
+ // Load the only library, specifying the index of 0 as it's the only
+ // library. This should load all callouts.
+ PublicLibraryManager lib_manager(std::string(BASIC_CALLOUT_LIBRARY),
+ 0, callout_manager_);
+ EXPECT_TRUE(lib_manager.openLibrary());
+
+ // Check the version of the library.
+ EXPECT_TRUE(lib_manager.checkVersion());
+
+ // Load the standard callouts
+ EXPECT_NO_THROW(lib_manager.registerStandardCallouts());
+
+ // Now execute the callouts in the order expected. The library performs
+ // the calculation:
+ //
+ // r3 = (10 + d1) * d2 - d3
+ executeCallCallouts(10, 5, 15, 7, 105, 17, 88);
+
+ // Tidy up
+ EXPECT_TRUE(lib_manager.closeLibrary());
+}
+
+// Test that the "load" function is called correctly.
+
+TEST_F(LibraryManagerTest, CheckLoadCalled) {
+
+ // Load the only library, specifying the index of 0 as it's the only
+ // library. This should load all callouts.
+ PublicLibraryManager lib_manager(std::string(LOAD_CALLOUT_LIBRARY),
+ 0, callout_manager_);
+ EXPECT_TRUE(lib_manager.openLibrary());
+
+ // Check the version of the library.
+ EXPECT_TRUE(lib_manager.checkVersion());
+
+ // Load the standard callouts
+ EXPECT_NO_THROW(lib_manager.registerStandardCallouts());
+
+ // Check that only context_create and hookpt_one have callouts registered.
+ EXPECT_TRUE(callout_manager_->calloutsPresent(
+ ServerHooks::CONTEXT_CREATE));
+ EXPECT_TRUE(callout_manager_->calloutsPresent(hookpt_one_index_));
+ EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_two_index_));
+ EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_three_index_));
+ EXPECT_FALSE(callout_manager_->calloutsPresent(
+ ServerHooks::CONTEXT_DESTROY));
+
+ // Call the runLoad() method to run the load() function.
+ EXPECT_TRUE(lib_manager.runLoad());
+ EXPECT_TRUE(callout_manager_->calloutsPresent(
+ ServerHooks::CONTEXT_CREATE));
+ EXPECT_TRUE(callout_manager_->calloutsPresent(hookpt_one_index_));
+ EXPECT_TRUE(callout_manager_->calloutsPresent(hookpt_two_index_));
+ EXPECT_TRUE(callout_manager_->calloutsPresent(hookpt_three_index_));
+ EXPECT_FALSE(callout_manager_->calloutsPresent(
+ ServerHooks::CONTEXT_DESTROY));
+
+ // Now execute the callouts in the order expected. The library performs
+ // the calculation:
+ //
+ // r3 = (5 * d1 + d2) * d3
+ executeCallCallouts(5, 5, 25, 7, 32, 10, 320);
+
+ // Tidy up
+ EXPECT_TRUE(lib_manager.closeLibrary());
+}
+
+// Check handling of a "load" function that throws an exception
+
+TEST_F(LibraryManagerTest, CheckLoadException) {
+
+ // Load the only library, specifying the index of 0 as it's the only
+ // library. This should load all callouts.
+ PublicLibraryManager lib_manager(std::string(FRAMEWORK_EXCEPTION_LIBRARY),
+ 0, callout_manager_);
+ EXPECT_TRUE(lib_manager.openLibrary());
+
+ // Running the load function should fail.
+ EXPECT_FALSE(lib_manager.runLoad());
+
+ // Tidy up
+ EXPECT_TRUE(lib_manager.closeLibrary());
+}
+
+// Check handling of a "load" function that returns an error.
+
+TEST_F(LibraryManagerTest, CheckLoadError) {
+
+ // Load the only library, specifying the index of 0 as it's the only
+ // library. This should load all callouts.
+ PublicLibraryManager lib_manager(std::string(LOAD_ERROR_CALLOUT_LIBRARY),
+ 0, callout_manager_);
+ EXPECT_TRUE(lib_manager.openLibrary());
+
+ // Check that we catch a load error
+ EXPECT_FALSE(lib_manager.runLoad());
+
+ // Tidy up
+ EXPECT_TRUE(lib_manager.closeLibrary());
+}
+
+// No unload function
+
+TEST_F(LibraryManagerTest, CheckNoUnload) {
+
+ // Load the only library, specifying the index of 0 as it's the only
+ // library. This should load all callouts.
+ PublicLibraryManager lib_manager(std::string(BASIC_CALLOUT_LIBRARY),
+ 0, callout_manager_);
+ EXPECT_TRUE(lib_manager.openLibrary());
+
+ // Check that no unload function returns true.
+ EXPECT_TRUE(lib_manager.runUnload());
+
+ // Tidy up
+ EXPECT_TRUE(lib_manager.closeLibrary());
+}
+
+// Unload function returns an error
+
+TEST_F(LibraryManagerTest, CheckUnloadError) {
+
+ // Load the only library, specifying the index of 0 as it's the only
+ // library. This should load all callouts.
+ PublicLibraryManager lib_manager(std::string(LOAD_ERROR_CALLOUT_LIBRARY),
+ 0, callout_manager_);
+ EXPECT_TRUE(lib_manager.openLibrary());
+
+ // Check that unload function returning an error returns false.
+ EXPECT_FALSE(lib_manager.runUnload());
+
+ // Tidy up
+ EXPECT_TRUE(lib_manager.closeLibrary());
+}
+
+// Unload function throws an exception.
+
+TEST_F(LibraryManagerTest, CheckUnloadException) {
+
+ // Load the only library, specifying the index of 0 as it's the only
+ // library. This should load all callouts.
+ PublicLibraryManager lib_manager(std::string(FRAMEWORK_EXCEPTION_LIBRARY),
+ 0, callout_manager_);
+ EXPECT_TRUE(lib_manager.openLibrary());
+
+ // Check that we detect that the unload function throws an exception.
+ EXPECT_FALSE(lib_manager.runUnload());
+
+ // Tidy up
+ EXPECT_TRUE(lib_manager.closeLibrary());
+}
+
+// Check that the case of the library's unload() function returning a
+// success is handled correcty.
+
+TEST_F(LibraryManagerTest, CheckUnload) {
+
+ // Load the only library, specifying the index of 0 as it's the only
+ // library. This should load all callouts.
+ PublicLibraryManager lib_manager(std::string(UNLOAD_CALLOUT_LIBRARY),
+ 0, callout_manager_);
+ EXPECT_TRUE(lib_manager.openLibrary());
+
+
+ // Check that the marker file is not present (at least that the file
+ // open fails).
+ EXPECT_FALSE(markerFilePresent());
+
+ // Check that unload function runs and returns a success
+ EXPECT_TRUE(lib_manager.runUnload());
+
+ // Check that the marker file was created.
+ EXPECT_TRUE(markerFilePresent());
+
+ // Tidy up
+ EXPECT_TRUE(lib_manager.closeLibrary());
+}
+
+// Test the operation of unloadLibrary(). We load a library with a set
+// of callouts then unload it. We need to check that the callouts have been
+// removed. We'll also check that the library's unload() function was called
+// as well.
+
+TEST_F(LibraryManagerTest, LibUnload) {
+
+ // Load the only library, specifying the index of 0 as it's the only
+ // library. This should load all callouts.
+ PublicLibraryManager lib_manager(std::string(FULL_CALLOUT_LIBRARY),
+ 0, callout_manager_);
+ EXPECT_TRUE(lib_manager.openLibrary());
+
+ // Check the version of the library.
+ EXPECT_TRUE(lib_manager.checkVersion());
+
+ // No callouts should be registered at the moment.
+ EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_one_index_));
+ EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_two_index_));
+ EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_three_index_));
+
+ // Load the single standard callout and check it is registered correctly.
+ EXPECT_NO_THROW(lib_manager.registerStandardCallouts());
+ EXPECT_TRUE(callout_manager_->calloutsPresent(hookpt_one_index_));
+ EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_two_index_));
+ EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_three_index_));
+
+ // Call the load function to load the other callouts.
+ EXPECT_TRUE(lib_manager.runLoad());
+ EXPECT_TRUE(callout_manager_->calloutsPresent(hookpt_one_index_));
+ EXPECT_TRUE(callout_manager_->calloutsPresent(hookpt_two_index_));
+ EXPECT_TRUE(callout_manager_->calloutsPresent(hookpt_three_index_));
+
+ // Unload the library and check that the callouts have been removed from
+ // the CalloutManager.
+ lib_manager.unloadLibrary();
+ EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_one_index_));
+ EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_two_index_));
+ EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_three_index_));
+}
+
+// Now come the loadLibrary() tests that make use of all the methods tested
+// above. These tests are really to make sure that the methods have been
+// tied together correctly.
+
+// First test the basic error cases - no library, no version function, version
+// function returning an error.
+
+TEST_F(LibraryManagerTest, LoadLibraryNoLibrary) {
+ LibraryManager lib_manager(std::string(NOT_PRESENT_LIBRARY), 0,
+ callout_manager_);
+ EXPECT_FALSE(lib_manager.loadLibrary());
+}
+
+// Check that the code handles the case of a library with no version function.
+
+TEST_F(LibraryManagerTest, LoadLibraryNoVersion) {
+ LibraryManager lib_manager(std::string(NO_VERSION_LIBRARY), 0,
+ callout_manager_);
+ EXPECT_FALSE(lib_manager.loadLibrary());
+}
+
+// Check that the code handles the case of a library with a version function
+// that returns an incorrect version number.
+
+TEST_F(LibraryManagerTest, LoadLibraryWrongVersion) {
+ LibraryManager lib_manager(std::string(INCORRECT_VERSION_LIBRARY), 0,
+ callout_manager_);
+ EXPECT_FALSE(lib_manager.loadLibrary());
+}
+
+// Check that the full loadLibrary call works.
+
+TEST_F(LibraryManagerTest, LoadLibrary) {
+ LibraryManager lib_manager(std::string(FULL_CALLOUT_LIBRARY), 0,
+ callout_manager_);
+ EXPECT_TRUE(lib_manager.loadLibrary());
+
+ // Now execute the callouts in the order expected. The library performs
+ // the calculation:
+ //
+ // r3 = (7 * d1 - d2) * d3
+ executeCallCallouts(7, 5, 35, 9, 26, 3, 78);
+
+ EXPECT_TRUE(lib_manager.unloadLibrary());
+
+ // Check that the callouts have been removed from the callout manager.
+ EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_one_index_));
+ EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_two_index_));
+ EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_three_index_));
+}
+
+// Now test for multiple libraries. We'll load the full callout library
+// first, then load some of the libraries with missing framework functions.
+// This will check that when searching for framework functions, only the
+// specified library is checked, not other loaded libraries. We will
+// load a second library with suitable callouts and check that the callouts
+// are added correctly. Finally, we'll unload one of the libraries and
+// check that only the callouts belonging to that library were removed.
+
+TEST_F(LibraryManagerTest, LoadMultipleLibraries) {
+ // Load a library with all framework functions.
+ LibraryManager lib_manager_1(std::string(FULL_CALLOUT_LIBRARY), 0,
+ callout_manager_);
+ EXPECT_TRUE(lib_manager_1.loadLibrary());
+
+ // Attempt to load a library with no version() function. We should detect
+ // this and not end up calling the function from the already loaded
+ // library.
+ LibraryManager lib_manager_2(std::string(NO_VERSION_LIBRARY), 1,
+ callout_manager_);
+ EXPECT_FALSE(lib_manager_2.loadLibrary());
+
+ // Attempt to load the library with an incorrect version. This should
+ // be detected.
+ LibraryManager lib_manager_3(std::string(INCORRECT_VERSION_LIBRARY), 1,
+ callout_manager_);
+ EXPECT_FALSE(lib_manager_3.loadLibrary());
+
+ // Load the basic callout library. This only has standard callouts so,
+ // if the first library's load() function gets called, some callouts
+ // will be registered twice and lead to incorrect results.
+ LibraryManager lib_manager_4(std::string(BASIC_CALLOUT_LIBRARY), 1,
+ callout_manager_);
+ EXPECT_TRUE(lib_manager_4.loadLibrary());
+
+ // Execute the callouts. The first library implements the calculation.
+ //
+ // r3 = (7 * d1 - d2) * d3
+ //
+ // The last-loaded library implements the calculation
+ //
+ // r3 = (10 + d1) * d2 - d3
+ //
+ // Putting the processing for each library together in the appropriate
+ // order, we get:
+ //
+ // r3 = ((10 * d1 + d1) - d2) * d2 * d3 - d3
+ executeCallCallouts(10, 3, 33, 2, 62, 3, 183);
+
+ // All done, so unload the first library.
+ EXPECT_TRUE(lib_manager_1.unloadLibrary());
+
+ // Now execute the callouts again and check that the results are as
+ // expected for the new calculation.
+ executeCallCallouts(10, 5, 15, 7, 105, 17, 88);
+
+ // ... and tidy up.
+ EXPECT_TRUE(lib_manager_4.unloadLibrary());
+}
+
+} // Anonymous namespace
diff --git a/src/lib/hooks/tests/load_callout_library.cc b/src/lib/hooks/tests/load_callout_library.cc
new file mode 100644
index 0000000..ae9f470
--- /dev/null
+++ b/src/lib/hooks/tests/load_callout_library.cc
@@ -0,0 +1,119 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/// @file
+/// @brief Basic library with load() function
+///
+/// This is source of a test library for various test (LibraryManager and
+/// HooksManager). The characteristics of the library produced from this
+/// file are:
+///
+/// - The "version" and "load" framework functions are supplied. One "standard"
+/// callout is supplied ("hookpt_one") and two non-standard ones which are
+/// registered during the call to "load" on the hooks "hookpt_two" and
+/// "hookpt_three".
+///
+/// All callouts do trivial calculations, the result of all being called in
+/// sequence being
+///
+/// @f[ ((5 * data_1) + data_2) * data_3 @f]
+///
+/// ...where data_1, data_2 and data_3 are the values passed in arguments of
+/// the same name to the three callouts (data_1 passed to hookpt_one, data_2 to
+/// hookpt_two etc.) and the result is returned in the argument "result".
+
+#include <hooks/hooks.h>
+
+using namespace isc::hooks;
+
+extern "C" {
+
+// Callouts
+
+int
+context_create(CalloutHandle& handle) {
+ handle.setContext("result", static_cast<int>(5));
+ handle.setArgument("result", static_cast<int>(5));
+ return (0);
+}
+
+// First callout adds the passed "data_1" argument to the initialized context
+// value of 5. (Note that the value set by context_create is accessed through
+// context and not the argument, so checking that context is correctly passed
+// between callouts in the same library.)
+
+int
+hookpt_one(CalloutHandle& handle) {
+ int data;
+ handle.getArgument("data_1", data);
+
+ int result;
+ handle.getContext("result", result);
+
+ result *= data;
+ handle.setArgument("result", result);
+
+ return (0);
+}
+
+// Second callout multiplies the current context value by the "data_2"
+// argument.
+
+static int
+hook_nonstandard_two(CalloutHandle& handle) {
+ int data;
+ handle.getArgument("data_2", data);
+
+ int result;
+ handle.getArgument("result", result);
+
+ result += data;
+ handle.setArgument("result", result);
+
+ return (0);
+}
+
+// Final callout adds "data_3" to the result.
+
+static int
+hook_nonstandard_three(CalloutHandle& handle) {
+ int data;
+ handle.getArgument("data_3", data);
+
+ int result;
+ handle.getArgument("result", result);
+
+ result *= data;
+ handle.setArgument("result", result);
+
+ return (0);
+}
+
+// Framework functions
+
+int
+version() {
+ return (BIND10_HOOKS_VERSION);
+}
+
+int load(LibraryHandle& handle) {
+ // Register the non-standard functions
+ handle.registerCallout("hookpt_two", hook_nonstandard_two);
+ handle.registerCallout("hookpt_three", hook_nonstandard_three);
+
+ return (0);
+}
+
+};
+
diff --git a/src/lib/hooks/tests/load_error_callout_library.cc b/src/lib/hooks/tests/load_error_callout_library.cc
new file mode 100644
index 0000000..b861d7f
--- /dev/null
+++ b/src/lib/hooks/tests/load_error_callout_library.cc
@@ -0,0 +1,49 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/// @file
+/// @brief Error load library
+///
+/// This is source of a test library for various test (LibraryManager and
+/// HooksManager). The characteristics of the library produced from this
+/// file are:
+///
+/// - All framework functions are supplied. "version" returns the correct
+/// value, but "load" and unload return an error.
+
+#include <hooks/hooks.h>
+
+using namespace isc::hooks;
+
+extern "C" {
+
+// Framework functions
+
+int
+version() {
+ return (BIND10_HOOKS_VERSION);
+}
+
+int
+load(LibraryHandle&) {
+ return (1);
+}
+
+int
+unload() {
+ return (1);
+}
+
+};
+
diff --git a/src/lib/hooks/tests/marker_file.h.in b/src/lib/hooks/tests/marker_file.h.in
new file mode 100644
index 0000000..e032cdd
--- /dev/null
+++ b/src/lib/hooks/tests/marker_file.h.in
@@ -0,0 +1,27 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef MARKER_FILE_H
+#define MARKER_FILE_H
+
+/// @file
+/// Define a marker file that is used in tests to prove that an "unload"
+/// function has been called.
+
+namespace {
+const char* MARKER_FILE = "@abs_builddir@/marker_file.dat";
+}
+
+#endif // MARKER_FILE_H
+
diff --git a/src/lib/hooks/tests/no_version_library.cc b/src/lib/hooks/tests/no_version_library.cc
new file mode 100644
index 0000000..f5b5b9c
--- /dev/null
+++ b/src/lib/hooks/tests/no_version_library.cc
@@ -0,0 +1,30 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/// @file
+/// @brief No version function library
+///
+/// This is source of a test library for various test (LibraryManager and
+/// HooksManager). The characteristics of the library produced from this
+/// file are:
+///
+/// - No version() function is present.
+
+extern "C" {
+
+int no_version() {
+ return (0);
+}
+
+};
diff --git a/src/lib/hooks/tests/run_unittests.cc b/src/lib/hooks/tests/run_unittests.cc
new file mode 100644
index 0000000..f68a58d
--- /dev/null
+++ b/src/lib/hooks/tests/run_unittests.cc
@@ -0,0 +1,25 @@
+// Copyright (C) 2009 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <log/logger_support.h>
+#include <util/unittests/run_all.h>
+
+#include <gtest/gtest.h>
+
+int
+main(int argc, char* argv[]) {
+ ::testing::InitGoogleTest(&argc, argv);
+ isc::log::initLogger();
+ return (isc::util::unittests::run_all());
+}
diff --git a/src/lib/hooks/tests/server_hooks_unittest.cc b/src/lib/hooks/tests/server_hooks_unittest.cc
new file mode 100644
index 0000000..ca9b6f0
--- /dev/null
+++ b/src/lib/hooks/tests/server_hooks_unittest.cc
@@ -0,0 +1,178 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <hooks/server_hooks.h>
+
+#include <gtest/gtest.h>
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+using namespace isc;
+using namespace isc::hooks;
+using namespace std;
+
+namespace {
+
+// Checks the registration of hooks and the interrogation methods. As the
+// constructor registers two hooks, this is also a test of the constructor.
+
+TEST(ServerHooksTest, RegisterHooks) {
+ ServerHooks& hooks = ServerHooks::getServerHooks();
+ hooks.reset();
+
+ // There should be two hooks already registered, with indexes 0 and 1.
+ EXPECT_EQ(2, hooks.getCount());
+ EXPECT_EQ(0, hooks.getIndex("context_create"));
+ EXPECT_EQ(1, hooks.getIndex("context_destroy"));
+
+ // Check that the constants are as expected. (The intermediate variables
+ // are used because of problems with g++ 4.6.1/Ubuntu 11.10 when resolving
+ // the value of the ServerHooks constants when they appeared within the
+ // gtest macro.)
+ const int create_value = ServerHooks::CONTEXT_CREATE;
+ const int destroy_value = ServerHooks::CONTEXT_DESTROY;
+ EXPECT_EQ(0, create_value);
+ EXPECT_EQ(1, destroy_value);
+
+ // Register another couple of hooks. The test on returned index is based
+ // on knowledge that the hook indexes are assigned in ascending order.
+ int alpha = hooks.registerHook("alpha");
+ EXPECT_EQ(2, alpha);
+ EXPECT_EQ(2, hooks.getIndex("alpha"));
+
+ int beta = hooks.registerHook("beta");
+ EXPECT_EQ(3, beta);
+ EXPECT_EQ(3, hooks.getIndex("beta"));
+
+ // Should be four hooks now
+ EXPECT_EQ(4, hooks.getCount());
+}
+
+// Check that duplicate names cannot be registered.
+
+TEST(ServerHooksTest, DuplicateHooks) {
+ ServerHooks& hooks = ServerHooks::getServerHooks();
+ hooks.reset();
+
+ // Ensure we can't duplicate one of the existing names.
+ EXPECT_THROW(hooks.registerHook("context_create"), DuplicateHook);
+
+ // Check we can't duplicate a newly registered hook.
+ int gamma = hooks.registerHook("gamma");
+ EXPECT_EQ(2, gamma);
+ EXPECT_THROW(hooks.registerHook("gamma"), DuplicateHook);
+}
+
+// Checks that we can get the name of the hooks.
+
+TEST(ServerHooksTest, GetHookNames) {
+ ServerHooks& hooks = ServerHooks::getServerHooks();
+ hooks.reset();
+ vector<string> expected_names;
+
+ // Add names into the hooks object and to the set of expected names.
+ expected_names.push_back("alpha");
+ expected_names.push_back("beta");
+ expected_names.push_back("gamma");
+ expected_names.push_back("delta");
+ for (int i = 0; i < expected_names.size(); ++i) {
+ hooks.registerHook(expected_names[i].c_str());
+ };
+
+ // Update the expected names to include the pre-defined hook names.
+ expected_names.push_back("context_create");
+ expected_names.push_back("context_destroy");
+
+ // Get the actual hook names
+ vector<string> actual_names = hooks.getHookNames();
+
+ // For comparison, sort the names into alphabetical order and do a straight
+ // vector comparison.
+ sort(expected_names.begin(), expected_names.end());
+ sort(actual_names.begin(), actual_names.end());
+
+ EXPECT_TRUE(expected_names == actual_names);
+}
+
+// Test the inverse hooks functionality (i.e. given an index, get the name).
+
+TEST(ServerHooksTest, GetHookIndexes) {
+ ServerHooks& hooks = ServerHooks::getServerHooks();
+ hooks.reset();
+
+ int alpha = hooks.registerHook("alpha");
+ int beta = hooks.registerHook("beta");
+ int gamma = hooks.registerHook("gamma");
+
+ EXPECT_EQ(std::string("context_create"),
+ hooks.getName(ServerHooks::CONTEXT_CREATE));
+ EXPECT_EQ(std::string("context_destroy"),
+ hooks.getName(ServerHooks::CONTEXT_DESTROY));
+ EXPECT_EQ(std::string("alpha"), hooks.getName(alpha));
+ EXPECT_EQ(std::string("beta"), hooks.getName(beta));
+ EXPECT_EQ(std::string("gamma"), hooks.getName(gamma));
+
+ // Check for an invalid index
+ EXPECT_THROW(hooks.getName(-1), NoSuchHook);
+ EXPECT_THROW(hooks.getName(42), NoSuchHook);
+}
+
+// Test the reset functionality.
+
+TEST(ServerHooksTest, Reset) {
+ ServerHooks& hooks = ServerHooks::getServerHooks();
+ hooks.reset();
+
+ int alpha = hooks.registerHook("alpha");
+ int beta = hooks.registerHook("beta");
+ int gamma = hooks.registerHook("gamma");
+
+ // Check the counts before and after a reset.
+ EXPECT_EQ(5, hooks.getCount());
+ hooks.reset();
+ EXPECT_EQ(2, hooks.getCount());
+
+ // ... and check that the hooks are as expected.
+ EXPECT_EQ(0, hooks.getIndex("context_create"));
+ EXPECT_EQ(1, hooks.getIndex("context_destroy"));
+}
+
+// Check that getting an unknown name throws an exception.
+
+TEST(ServerHooksTest, UnknownHookName) {
+ ServerHooks& hooks = ServerHooks::getServerHooks();
+ hooks.reset();
+
+ EXPECT_THROW(static_cast<void>(hooks.getIndex("unknown")), NoSuchHook);
+}
+
+// Check that the count of hooks is correct.
+
+TEST(ServerHooksTest, HookCount) {
+ ServerHooks& hooks = ServerHooks::getServerHooks();
+ hooks.reset();
+
+ // Insert the names into the hooks object
+ hooks.registerHook("alpha");
+ hooks.registerHook("beta");
+ hooks.registerHook("gamma");
+ hooks.registerHook("delta");
+
+ // Should be two more hooks that the number we have registered.
+ EXPECT_EQ(6, hooks.getCount());
+}
+
+} // Anonymous namespace
diff --git a/src/lib/hooks/tests/test_libraries.h.in b/src/lib/hooks/tests/test_libraries.h.in
new file mode 100644
index 0000000..bb6a24a
--- /dev/null
+++ b/src/lib/hooks/tests/test_libraries.h.in
@@ -0,0 +1,79 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef TEST_LIBRARIES_H
+#define TEST_LIBRARIES_H
+
+#include <config.h>
+
+namespace {
+
+
+// Take care of differences in DLL naming between operating systems.
+
+#ifdef OS_OSX
+#define DLL_SUFFIX ".dylib"
+
+#else
+#define DLL_SUFFIX ".so"
+
+#endif
+
+
+// Names of the libraries used in these tests. These libraries are built using
+// libtool, so we need to look in the hidden ".libs" directory to locate the
+// .so file. Note that we access the .so file - libtool creates this as a
+// like to the real shared library.
+
+// Basic library with context_create and three "standard" callouts.
+static const char* BASIC_CALLOUT_LIBRARY = "@abs_builddir@/.libs/libbcl"
+ DLL_SUFFIX;
+
+// Library with context_create and three "standard" callouts, as well as
+// load() and unload() functions.
+static const char* FULL_CALLOUT_LIBRARY = "@abs_builddir@/.libs/libfcl"
+ DLL_SUFFIX;
+
+// Library where the all framework functions throw an exception
+static const char* FRAMEWORK_EXCEPTION_LIBRARY = "@abs_builddir@/.libs/libfxl"
+ DLL_SUFFIX;
+
+// Library where the version() function returns an incorrect result.
+static const char* INCORRECT_VERSION_LIBRARY = "@abs_builddir@/.libs/libivl"
+ DLL_SUFFIX;
+
+// Library where some of the callout registration is done with the load()
+// function.
+static const char* LOAD_CALLOUT_LIBRARY = "@abs_builddir@/.libs/liblcl"
+ DLL_SUFFIX;
+
+// Library where the load() function returns an error.
+static const char* LOAD_ERROR_CALLOUT_LIBRARY =
+ "@abs_builddir@/.libs/liblecl" DLL_SUFFIX;
+
+// Name of a library which is not present.
+static const char* NOT_PRESENT_LIBRARY = "@abs_builddir@/.libs/libnothere"
+ DLL_SUFFIX;
+
+// Library that does not include a version function.
+static const char* NO_VERSION_LIBRARY = "@abs_builddir@/.libs/libnvl"
+ DLL_SUFFIX;
+
+// Library where there is an unload() function.
+static const char* UNLOAD_CALLOUT_LIBRARY = "@abs_builddir@/.libs/libucl"
+ DLL_SUFFIX;
+} // anonymous namespace
+
+
+#endif // TEST_LIBRARIES_H
diff --git a/src/lib/hooks/tests/unload_callout_library.cc b/src/lib/hooks/tests/unload_callout_library.cc
new file mode 100644
index 0000000..9baa830
--- /dev/null
+++ b/src/lib/hooks/tests/unload_callout_library.cc
@@ -0,0 +1,52 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/// @file
+/// @brief Basic unload library
+///
+/// This is source of a test library for various test (LibraryManager and
+/// HooksManager). The characteristics of the library produced from this
+/// file are:
+///
+/// - The "version" and "unload" framework functions are supplied. "version"
+/// returns a valid value and "unload" creates a marker file and returns
+/// success.
+
+#include <hooks/hooks.h>
+#include <hooks/tests/marker_file.h>
+
+#include <fstream>
+
+using namespace isc::hooks;
+
+extern "C" {
+
+// Framework functions
+
+int
+version() {
+ return (BIND10_HOOKS_VERSION);
+}
+
+int
+unload() {
+ // Create the marker file.
+ std::fstream marker;
+ marker.open(MARKER_FILE, std::fstream::out);
+ marker.close();
+
+ return (0);
+}
+
+};
diff --git a/src/lib/python/isc/config/ccsession.py b/src/lib/python/isc/config/ccsession.py
index 206a0ee..4dd73b7 100644
--- a/src/lib/python/isc/config/ccsession.py
+++ b/src/lib/python/isc/config/ccsession.py
@@ -220,6 +220,9 @@ class ModuleCCSession(ConfigData):
self._remote_module_configs = {}
self._remote_module_callbacks = {}
+ self._notification_callbacks = {}
+ self._last_notif_id = 0
+
if handle_logging_config:
self.add_remote_config(path_search('logging.spec', bind10_config.PLUGIN_PATHS),
default_logconfig_handler)
@@ -294,8 +297,27 @@ class ModuleCCSession(ConfigData):
configuration update. Calls the corresponding handler
functions if present. Responds on the channel if the
handler returns a message."""
- # should we default to an answer? success-by-default? unhandled error?
- if msg is not None and not CC_PAYLOAD_RESULT in msg:
+ if msg is None:
+ return
+ if CC_PAYLOAD_NOTIFICATION in msg:
+ group_s = env[CC_HEADER_GROUP].split('/', 1)
+ # What to do with these bogus inputs? We just ignore them for now.
+ if len(group_s) != 2:
+ return
+ [prefix, group] = group_s
+ if prefix + '/' != CC_GROUP_NOTIFICATION_PREFIX:
+ return
+ # Now, get the callbacks and call one by one
+ callbacks = self._notification_callbacks.get(group, {})
+ event = msg[CC_PAYLOAD_NOTIFICATION][0]
+ params = None
+ if len(msg[CC_PAYLOAD_NOTIFICATION]) > 1:
+ params = msg[CC_PAYLOAD_NOTIFICATION][1]
+ for key in sorted(callbacks.keys()):
+ callbacks[key](event, params)
+ elif not CC_PAYLOAD_RESULT in msg:
+ # should we default to an answer? success-by-default? unhandled
+ # error?
answer = None
try:
module_name = env[CC_HEADER_GROUP]
@@ -575,6 +597,64 @@ class ModuleCCSession(ConfigData):
to=CC_TO_WILDCARD,
want_answer=False)
+ def subscribe_notification(self, notification_group, callback):
+ """
+ Subscribe to receive notifications in given notification group. When a
+ notification comes to the group, the callback is called with two
+ parameters, the name of the event (the value of `event_name` parameter
+ passed to `notify`) and the parameters of the event (the value
+ of `params` passed to `notify`).
+
+ This is a fast operation (there may be communication with the message
+ queue daemon, but it does not wait for any remote process).
+
+ The callback may get called multiple times (once for each notification).
+ It is possible to subscribe multiple callbacks for the same notification,
+ by multiple calls of this method, and they will be called in the order
+ of registration when the notification comes.
+
+ Throws:
+ - CCSessionError: for low-level communication errors.
+ Params:
+ - notification_group (string): Notification group to subscribe to.
+ Notification with the same value of the same parameter of `notify`
+ will be received.
+ - callback (callable): The callback to be called whenever the
+ notification comes.
+
+ The callback should not raise exceptions, such exceptions are
+ likely to propagate through the loop and terminate program.
+ Returns: Opaque id of the subscription. It can be used to cancel
+ the subscription by unsubscribe_notification.
+ """
+ self._last_notif_id += 1
+ my_id = self._last_notif_id
+ if notification_group in self._notification_callbacks:
+ self._notification_callbacks[notification_group][my_id] = callback
+ else:
+ self._session.group_subscribe(CC_GROUP_NOTIFICATION_PREFIX +
+ notification_group)
+ self._notification_callbacks[notification_group] = \
+ { my_id: callback }
+ return (notification_group, my_id)
+
+ def unsubscribe_notification(self, nid):
+ """
+ Remove previous subscription for notifications. Pass the id returned
+ from subscribe_notification.
+
+ Throws:
+ - CCSessionError: for low-level communication errors.
+ - KeyError: The id does not correspond to valid subscription.
+ """
+ (group, cid) = nid
+ del self._notification_callbacks[group][cid]
+ if not self._notification_callbacks[group]:
+ # Removed the last one
+ self._session.group_unsubscribe(CC_GROUP_NOTIFICATION_PREFIX +
+ group)
+ del self._notification_callbacks[group]
+
class UIModuleCCSession(MultiConfigData):
"""This class is used in a configuration user interface. It contains
specific functions for getting, displaying, and sending
diff --git a/src/lib/python/isc/config/tests/ccsession_test.py b/src/lib/python/isc/config/tests/ccsession_test.py
index 3585337..9c33ef6 100644
--- a/src/lib/python/isc/config/tests/ccsession_test.py
+++ b/src/lib/python/isc/config/tests/ccsession_test.py
@@ -376,6 +376,77 @@ class TestModuleCCSession(unittest.TestCase):
],
fake_session.message_queue)
+ def test_notify_receive(self):
+ """
+ Test we can subscribe to notifications, receive them, unsubscribe, etc.
+ """
+ fake_session = FakeModuleCCSession()
+ mccs = self.create_session("spec1.spec", None, None, fake_session)
+ fake_session.group_sendmsg({"notification": ["event", {
+ "param": True
+ }]}, "notifications/group")
+ # Not subscribed to notifications -> not subscribed to
+ # 'notifications/group' -> message not eaten yet
+ mccs.check_command()
+ self.assertEqual(fake_session.message_queue, [['notifications/group',
+ None, {'notification': ['event', {'param': True}]},
+ False]])
+ # Place to log called notifications
+ notifications = []
+ def notified(tag, event, params):
+ notifications.append((tag, event, params))
+ # Subscribe to the notifications. Twice.
+ id1 = mccs.subscribe_notification('group',
+ lambda event, params:
+ notified("first", event, params))
+ id2 = mccs.subscribe_notification('group',
+ lambda event, params:
+ notified("second", event,
+ params))
+ # Now the message gets eaten because we are subscribed, and both
+ # callbacks are called.
+ mccs.check_command()
+ self.assertEqual(fake_session.message_queue, [])
+ self.assertEqual(notifications, [
+ ("first", "event", {'param': True}),
+ ("second", "event", {'param': True})
+ ])
+ del notifications[:]
+ # If a notification for different group comes, it is left untouched.
+ fake_session.group_sendmsg({"notification": ["event", {
+ "param": True
+ }]}, "notifications/other")
+ mccs.check_command()
+ self.assertEqual(notifications, [])
+ self.assertEqual(fake_session.message_queue, [['notifications/other',
+ None, {'notification': ['event', {'param': True}]},
+ False]])
+ del fake_session.message_queue[:]
+ # Unsubscribe one of the notifications and see that only the other
+ # is triggered.
+ mccs.unsubscribe_notification(id2)
+ fake_session.group_sendmsg({"notification": ["event", {
+ "param": True
+ }]}, "notifications/group")
+ mccs.check_command()
+ self.assertEqual(fake_session.message_queue, [])
+ self.assertEqual(notifications, [
+ ("first", "event", {'param': True})
+ ])
+ del notifications[:]
+ # If we try to unsubscribe again, it complains.
+ self.assertRaises(KeyError, mccs.unsubscribe_notification, id2)
+ # Unsubscribe the other one too. From now on, it doesn't eat the
+ # messages again.
+ mccs.unsubscribe_notification(id1)
+ fake_session.group_sendmsg({"notification": ["event", {
+ "param": True
+ }]}, "notifications/group")
+ mccs.check_command()
+ self.assertEqual(fake_session.message_queue, [['notifications/group',
+ None, {'notification': ['event', {'param': True}]},
+ False]])
+
def my_config_handler_ok(self, new_config):
return isc.config.ccsession.create_answer(0)
diff --git a/src/lib/python/isc/notify/notify_out.py b/src/lib/python/isc/notify/notify_out.py
index a030a51..39c6708 100644
--- a/src/lib/python/isc/notify/notify_out.py
+++ b/src/lib/python/isc/notify/notify_out.py
@@ -509,10 +509,14 @@ class NotifyOut:
sock.sendto(render.get_data(), 0, addrinfo)
# count notifying by IPv4 or IPv6 for statistics
if zone_notify_info.get_socket().family == socket.AF_INET:
- self._counters.inc('zones', zone_notify_info.zone_name,
+ self._counters.inc('zones',
+ zone_notify_info.zone_class,
+ zone_notify_info.zone_name,
'notifyoutv4')
elif zone_notify_info.get_socket().family == socket.AF_INET6:
- self._counters.inc('zones', zone_notify_info.zone_name,
+ self._counters.inc('zones',
+ zone_notify_info.zone_class,
+ zone_notify_info.zone_name,
'notifyoutv6')
logger.info(NOTIFY_OUT_SENDING_NOTIFY, AddressFormatter(addrinfo))
except (socket.error, addr.InvalidAddress) as err:
diff --git a/src/lib/python/isc/notify/tests/notify_out_test.py b/src/lib/python/isc/notify/tests/notify_out_test.py
index e2b8d27..7097f01 100644
--- a/src/lib/python/isc/notify/tests/notify_out_test.py
+++ b/src/lib/python/isc/notify/tests/notify_out_test.py
@@ -304,10 +304,10 @@ class TestNotifyOut(unittest.TestCase):
self.assertRaises(isc.cc.data.DataNotFoundError,
self._notify._counters.get,
- 'zones', 'example.net.', 'notifyoutv4')
+ 'zones', 'IN', 'example.net.', 'notifyoutv4')
self.assertRaises(isc.cc.data.DataNotFoundError,
self._notify._counters.get,
- 'zones', 'example.net.', 'notifyoutv6')
+ 'zones', 'IN', 'example.net.', 'notifyoutv6')
example_com_info.prepare_notify_out()
ret = self._notify._send_notify_message_udp(example_com_info,
@@ -315,38 +315,38 @@ class TestNotifyOut(unittest.TestCase):
self.assertTrue(ret)
self.assertEqual(socket.AF_INET, example_com_info.sock_family)
self.assertEqual(self._notify._counters.get(
- 'zones', 'example.net.', 'notifyoutv4'), 1)
+ 'zones', 'IN', 'example.net.', 'notifyoutv4'), 1)
self.assertEqual(self._notify._counters.get(
- 'zones', 'example.net.', 'notifyoutv6'), 0)
+ 'zones', 'IN', 'example.net.', 'notifyoutv6'), 0)
def test_send_notify_message_udp_ipv6(self):
example_com_info = self._notify._notify_infos[('example.net.', 'IN')]
self.assertRaises(isc.cc.data.DataNotFoundError,
self._notify._counters.get,
- 'zones', 'example.net.', 'notifyoutv4')
+ 'zones', 'IN', 'example.net.', 'notifyoutv4')
self.assertRaises(isc.cc.data.DataNotFoundError,
self._notify._counters.get,
- 'zones', 'example.net.', 'notifyoutv6')
+ 'zones', 'IN', 'example.net.', 'notifyoutv6')
ret = self._notify._send_notify_message_udp(example_com_info,
('2001:db8::53', 53))
self.assertTrue(ret)
self.assertEqual(socket.AF_INET6, example_com_info.sock_family)
self.assertEqual(self._notify._counters.get(
- 'zones', 'example.net.', 'notifyoutv4'), 0)
+ 'zones', 'IN', 'example.net.', 'notifyoutv4'), 0)
self.assertEqual(self._notify._counters.get(
- 'zones', 'example.net.', 'notifyoutv6'), 1)
+ 'zones', 'IN', 'example.net.', 'notifyoutv6'), 1)
def test_send_notify_message_with_bogus_address(self):
example_com_info = self._notify._notify_infos[('example.net.', 'IN')]
self.assertRaises(isc.cc.data.DataNotFoundError,
self._notify._counters.get,
- 'zones', 'example.net.', 'notifyoutv4')
+ 'zones', 'IN', 'example.net.', 'notifyoutv4')
self.assertRaises(isc.cc.data.DataNotFoundError,
self._notify._counters.get,
- 'zones', 'example.net.', 'notifyoutv6')
+ 'zones', 'IN', 'example.net.', 'notifyoutv6')
# As long as the underlying data source validates RDATA this shouldn't
# happen, but right now it's not actually the case. Even if the
@@ -358,10 +358,10 @@ class TestNotifyOut(unittest.TestCase):
self.assertRaises(isc.cc.data.DataNotFoundError,
self._notify._counters.get,
- 'zones', 'example.net.', 'notifyoutv4')
+ 'zones', 'IN', 'example.net.', 'notifyoutv4')
self.assertRaises(isc.cc.data.DataNotFoundError,
self._notify._counters.get,
- 'zones', 'example.net.', 'notifyoutv4')
+ 'zones', 'IN', 'example.net.', 'notifyoutv4')
def test_zone_notify_handler(self):
sent_addrs = []
diff --git a/src/lib/python/isc/server_common/.gitignore b/src/lib/python/isc/server_common/.gitignore
new file mode 100644
index 0000000..63239b7
--- /dev/null
+++ b/src/lib/python/isc/server_common/.gitignore
@@ -0,0 +1 @@
+/bind10_server.py
diff --git a/src/lib/python/isc/statistics/counters.py b/src/lib/python/isc/statistics/counters.py
index 279c14b..0c2c827 100644
--- a/src/lib/python/isc/statistics/counters.py
+++ b/src/lib/python/isc/statistics/counters.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2012 Internet Systems Consortium.
+# Copyright (C) 2012-2013 Internet Systems Consortium.
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
@@ -81,19 +81,47 @@ def _add_counter(element, spec, identifier):
return isc.cc.data.find(element, identifier)
except isc.cc.data.DataNotFoundError:
pass
- # check whether spec and identifier are correct
- isc.config.find_spec_part(spec, identifier)
- # examine spec of the top-level item first
- spec_ = isc.config.find_spec_part(spec, identifier.split('/')[0])
- if spec_['item_type'] == 'named_set' and \
- spec_['named_set_item_spec']['item_type'] == 'map':
- map_spec = spec_['named_set_item_spec']['map_item_spec']
- for name in isc.config.spec_name_list(map_spec):
- spec_ = isc.config.find_spec_part(map_spec, name)
- id_str = '%s/%s/%s' % \
- tuple(identifier.split('/')[0:2] + [name])
- isc.cc.data.set(element, id_str, spec_['item_default'])
- else:
+
+ # Note: If there is a named_set type item in the statistics spec
+ # and if there are map type items under it, all of items under the
+ # map type item need to be added. For example, we're assuming that
+ # this method is now adding a counter whose identifier is like
+ # dir1/dir2/dir3/counter1. If both of dir1 and dir2 are named_set
+ # types, and if dir3 is a map type, and if counter1, counter2, and
+ # counter3 are defined as items under dir3 by the statistics spec,
+ # this method would add other two counters:
+ #
+ # dir1/dir2/dir3/counter2
+ # dir1/dir2/dir3/counter3
+ #
+ # Otherwise this method just adds the only counter
+ # dir1/dir2/dir3/counter1.
+
+ # examine spec from the top-level item and know whether
+ # has_named_set, and check whether spec and identifier are correct
+ pidr = ''
+ has_named_set = False
+ for idr in identifier.split('/'):
+ if len(pidr) > 0:
+ idr = pidr + '/' + idr
+ spec_ = isc.config.find_spec_part(spec, idr)
+ if isc.config.spec_part_is_named_set(spec_):
+ has_named_set = True
+ break
+ pidr = idr
+ # add all elements in map type if has_named_set
+ has_map = False
+ if has_named_set:
+ p_idr = identifier.rsplit('/', 1)[0]
+ p_spec = isc.config.find_spec_part(spec, p_idr)
+ if isc.config.spec_part_is_map(p_spec):
+ has_map = True
+ for name in isc.config.spec_name_list(p_spec['map_item_spec']):
+ idr_ = p_idr + '/' + name
+ spc_ = isc.config.find_spec_part(spec, idr_)
+ isc.cc.data.set(element, idr_, spc_['item_default'])
+ # otherwise add a specific element
+ if not has_map:
spec_ = isc.config.find_spec_part(spec, identifier)
isc.cc.data.set(element, identifier, spec_['item_default'])
return isc.cc.data.find(element, identifier)
@@ -161,30 +189,38 @@ class _Statistics():
"item_title": "Zone names",
"item_description": "Zone names",
"named_set_item_spec": {
- "item_name": "zonename",
- "item_type": "map",
+ "item_name": "classname",
+ "item_type": "named_set",
"item_optional": False,
"item_default": {},
- "item_title": "Zone name",
- "item_description": "Zone name",
- "map_item_spec": [
- {
- "item_name": "notifyoutv4",
- "item_type": "integer",
- "item_optional": False,
- "item_default": 0,
- "item_title": "IPv4 notifies",
- "item_description": "Number of IPv4 notifies per zone name sent out"
- },
- {
- "item_name": "notifyoutv6",
- "item_type": "integer",
- "item_optional": False,
- "item_default": 0,
- "item_title": "IPv6 notifies",
- "item_description": "Number of IPv6 notifies per zone name sent out"
- }
- ]
+ "item_title": "RR class name",
+ "item_description": "RR class name",
+ "named_set_item_spec": {
+ "item_name": "zonename",
+ "item_type": "map",
+ "item_optional": False,
+ "item_default": {},
+ "item_title": "Zone name",
+ "item_description": "Zone name",
+ "map_item_spec": [
+ {
+ "item_name": "notifyoutv4",
+ "item_type": "integer",
+ "item_optional": False,
+ "item_default": 0,
+ "item_title": "IPv4 notifies",
+ "item_description": "Number of IPv4 notifies per zone name sent out"
+ },
+ {
+ "item_name": "notifyoutv6",
+ "item_type": "integer",
+ "item_optional": False,
+ "item_default": 0,
+ "item_title": "IPv6 notifies",
+ "item_description": "Number of IPv6 notifies per zone name sent out"
+ }
+ ]
+ }
}
}
]
@@ -205,20 +241,20 @@ class Counters():
per-zone counters, a list of counters which can be handled in the
class are like the following:
- zones/example.com./notifyoutv4
- zones/example.com./notifyoutv6
- zones/example.com./xfrrej
- zones/example.com./xfrreqdone
- zones/example.com./soaoutv4
- zones/example.com./soaoutv6
- zones/example.com./axfrreqv4
- zones/example.com./axfrreqv6
- zones/example.com./ixfrreqv4
- zones/example.com./ixfrreqv6
- zones/example.com./xfrsuccess
- zones/example.com./xfrfail
- zones/example.com./last_ixfr_duration
- zones/example.com./last_axfr_duration
+ zones/IN/example.com./notifyoutv4
+ zones/IN/example.com./notifyoutv6
+ zones/IN/example.com./xfrrej
+ zones/IN/example.com./xfrreqdone
+ zones/IN/example.com./soaoutv4
+ zones/IN/example.com./soaoutv6
+ zones/IN/example.com./axfrreqv4
+ zones/IN/example.com./axfrreqv6
+ zones/IN/example.com./ixfrreqv4
+ zones/IN/example.com./ixfrreqv6
+ zones/IN/example.com./xfrsuccess
+ zones/IN/example.com./xfrfail
+ zones/IN/example.com./last_ixfr_duration
+ zones/IN/example.com./last_axfr_duration
ixfr_running
axfr_running
socket/unixdomain/open
@@ -275,8 +311,9 @@ class Counters():
isc.config.spec_name_list(self._statistics._spec):
self._zones_item_list = isc.config.spec_name_list(
isc.config.find_spec_part(
- self._statistics._spec, self._perzone_prefix)\
- ['named_set_item_spec']['map_item_spec'])
+ self._statistics._spec,
+ '%s/%s/%s' % (self._perzone_prefix,
+ '_CLASS_', self._entire_server)))
def clear_all(self):
"""clears all statistics data"""
@@ -387,15 +424,14 @@ class Counters():
# Start calculation for '_SERVER_' counts
zones_spec = isc.config.find_spec_part(self._statistics._spec,
self._perzone_prefix)
- zones_attrs = zones_spec['item_default'][self._entire_server]
zones_data = {}
- for attr in zones_attrs:
- id_str = '%s/%s' % (self._entire_server, attr)
- sum_ = 0
- for name in zones:
- if attr in zones[name]:
- sum_ += zones[name][attr]
- _set_counter(zones_data, zones_spec, id_str, sum_)
+ for cls in zones.keys():
+ for zone in zones[cls].keys():
+ for (attr, val) in zones[cls][zone].items():
+ id_str1 = '%s/%s/%s' % (cls, zone, attr)
+ id_str2 = '%s/%s/%s' % (cls, self._entire_server, attr)
+ _set_counter(zones_data, zones_spec, id_str1, val)
+ _inc_counter(zones_data, zones_spec, id_str2, val)
# insert entire-server counts
statistics_data[self._perzone_prefix] = dict(
statistics_data[self._perzone_prefix],
diff --git a/src/lib/python/isc/statistics/tests/counters_test.py b/src/lib/python/isc/statistics/tests/counters_test.py
index 5567dda..718c9da 100644
--- a/src/lib/python/isc/statistics/tests/counters_test.py
+++ b/src/lib/python/isc/statistics/tests/counters_test.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2012 Internet Systems Consortium.
+# Copyright (C) 2012-2013 Internet Systems Consortium.
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
@@ -23,6 +23,7 @@ import imp
import isc.config
TEST_ZONE_NAME_STR = "example.com."
+TEST_ZONE_CLASS_STR = "IN"
TESTDATA_SRCDIR = os.getenv("TESTDATASRCDIR")
from isc.statistics import counters
@@ -196,7 +197,8 @@ class BaseTestCounters():
def test_perzone_counters(self):
# for per-zone counters
for name in self.counters._zones_item_list:
- args = (self._perzone_prefix, TEST_ZONE_NAME_STR, name)
+ args = (self._perzone_prefix, TEST_ZONE_CLASS_STR,
+ TEST_ZONE_NAME_STR, name)
if name.find('last_') == 0 and name.endswith('_duration'):
self.counters.start_timer(*args)
self.counters.stop_timer(*args)
@@ -204,7 +206,8 @@ class BaseTestCounters():
sec = self.counters.get(*args)
for zone_str in (self._entire_server, TEST_ZONE_NAME_STR):
isc.cc.data.set(self._statistics_data,
- '%s/%s/%s' % (args[0], zone_str, name), sec)
+ '%s/%s/%s/%s' % (args[0], args[1],
+ zone_str, name), sec)
# twice exec stopper, then second is not changed
self.counters.stop_timer(*args)
self.assertEqual(self.counters.get(*args), sec)
@@ -220,7 +223,8 @@ class BaseTestCounters():
self.assertEqual(self.counters.get(*args), 2)
for zone_str in (self._entire_server, TEST_ZONE_NAME_STR):
isc.cc.data.set(self._statistics_data,
- '%s/%s/%s' % (args[0], zone_str, name), 2)
+ '%s/%s/%s/%s' % (args[0], args[1],
+ zone_str, name), 2)
self.check_get_statistics()
def test_xfrrunning_counters(self):
@@ -277,7 +281,8 @@ class BaseTestCounters():
def test_perzone_zero_counters(self):
# setting all counters to zero
for name in self.counters._zones_item_list:
- args = (self._perzone_prefix, TEST_ZONE_NAME_STR, name)
+ args = (self._perzone_prefix, TEST_ZONE_CLASS_STR,
+ TEST_ZONE_NAME_STR, name)
if name.find('last_') == 0 and name.endswith('_duration'):
zero = 0.0
else:
@@ -286,7 +291,8 @@ class BaseTestCounters():
self.counters._incdec(*args, step=zero)
for zone_str in (self._entire_server, TEST_ZONE_NAME_STR):
isc.cc.data.set(self._statistics_data,
- '%s/%s/%s' % (args[0], zone_str, name), zero)
+ '%s/%s/%s/%s' % (args[0], args[1],
+ zone_str, name), zero)
self.check_get_statistics()
def test_undefined_item(self):
@@ -352,15 +358,19 @@ class DummyNotifyOut(BaseDummyModule):
def inc_counters(self):
"""increments counters"""
- self.counters.inc('zones', TEST_ZONE_NAME_STR, 'notifyoutv4')
- self.counters.inc('zones', TEST_ZONE_NAME_STR, 'notifyoutv6')
+ self.counters.inc('zones', TEST_ZONE_CLASS_STR,
+ TEST_ZONE_NAME_STR, 'notifyoutv4')
+ self.counters.inc('zones', TEST_ZONE_CLASS_STR,
+ TEST_ZONE_NAME_STR, 'notifyoutv6')
class DummyXfroutSession(BaseDummyModule):
"""A dummy class equivalent to XfroutSession in b10-xfrout"""
def inc_counters(self):
"""increments counters"""
- self.counters.inc('zones', TEST_ZONE_NAME_STR, 'xfrreqdone')
- self.counters.inc('zones', TEST_ZONE_NAME_STR, 'xfrrej')
+ self.counters.inc('zones', TEST_ZONE_CLASS_STR,
+ TEST_ZONE_NAME_STR, 'xfrreqdone')
+ self.counters.inc('zones', TEST_ZONE_CLASS_STR,
+ TEST_ZONE_NAME_STR, 'xfrrej')
self.counters.inc('axfr_running')
self.counters.inc('ixfr_running')
self.counters.dec('axfr_running')
@@ -399,8 +409,10 @@ class TestDummyNotifyOut(unittest.TestCase):
def test_counters(self):
self.assertEqual(
- {'zones': {'_SERVER_': {'notifyoutv4': 1, 'notifyoutv6': 1},
- TEST_ZONE_NAME_STR: {'notifyoutv4': 1, 'notifyoutv6': 1}}},
+ {'zones': {TEST_ZONE_CLASS_STR: { '_SERVER_':
+ {'notifyoutv4': 1, 'notifyoutv6': 1},
+ TEST_ZONE_NAME_STR:
+ {'notifyoutv4': 1, 'notifyoutv6': 1}}}},
self.notifier.get_counters())
class TestDummyXfroutServer(unittest.TestCase):
@@ -418,13 +430,14 @@ class TestDummyXfroutServer(unittest.TestCase):
self.assertEqual(
{'axfr_running': 0, 'ixfr_running': 0,
'socket': {'unixdomain': {'open': 1, 'close': 1}},
- 'zones': {'_SERVER_': {'notifyoutv4': 1,
+ 'zones': {TEST_ZONE_CLASS_STR: {
+ '_SERVER_': {'notifyoutv4': 1,
'notifyoutv6': 1,
'xfrrej': 1, 'xfrreqdone': 1},
- TEST_ZONE_NAME_STR: {'notifyoutv4': 1,
+ TEST_ZONE_NAME_STR: {'notifyoutv4': 1,
'notifyoutv6': 1,
'xfrrej': 1,
- 'xfrreqdone': 1}}},
+ 'xfrreqdone': 1}}}},
self.xfrout_server.get_counters())
if __name__== "__main__":
diff --git a/src/lib/python/isc/statistics/tests/testdata/test_spec2.spec b/src/lib/python/isc/statistics/tests/testdata/test_spec2.spec
index 11b8706..422fc0a 100644
--- a/src/lib/python/isc/statistics/tests/testdata/test_spec2.spec
+++ b/src/lib/python/isc/statistics/tests/testdata/test_spec2.spec
@@ -9,56 +9,66 @@
"item_type": "named_set",
"item_optional": false,
"item_default": {
- "_SERVER_" : {
- "notifyoutv4" : 0,
- "notifyoutv6" : 0,
- "xfrrej" : 0,
- "xfrreqdone" : 0
+ "IN" : {
+ "_SERVER_" : {
+ "notifyoutv4" : 0,
+ "notifyoutv6" : 0,
+ "xfrrej" : 0,
+ "xfrreqdone" : 0
+ }
}
},
"item_title": "Zone names",
"item_description": "Zone names for Xfrout statistics",
"named_set_item_spec": {
- "item_name": "zonename",
- "item_type": "map",
+ "item_name": "classname",
+ "item_type": "named_set",
"item_optional": false,
"item_default": {},
- "item_title": "Zone name",
- "item_description": "Zone name for Xfrout statistics",
- "map_item_spec": [
- {
- "item_name": "notifyoutv4",
- "item_type": "integer",
- "item_optional": false,
- "item_default": 0,
- "item_title": "IPv4 notifies",
- "item_description": "Number of IPv4 notifies per zone name sent out from Xfrout"
- },
- {
- "item_name": "notifyoutv6",
- "item_type": "integer",
- "item_optional": false,
- "item_default": 0,
- "item_title": "IPv6 notifies",
- "item_description": "Number of IPv6 notifies per zone name sent out from Xfrout"
- },
- {
- "item_name": "xfrrej",
- "item_type": "integer",
- "item_optional": false,
- "item_default": 0,
- "item_title": "XFR rejected requests",
- "item_description": "Number of XFR requests per zone name rejected by Xfrout"
- },
- {
- "item_name": "xfrreqdone",
- "item_type": "integer",
- "item_optional": false,
- "item_default": 0,
- "item_title": "Requested zone transfers",
- "item_description": "Number of requested zone transfers completed per zone name"
- }
- ]
+ "item_title": "RR class name",
+ "item_description": "RR class name for Xfrout statistics",
+ "named_set_item_spec": {
+ "item_name": "zonename",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "item_title": "Zone name",
+ "item_description": "Zone name for Xfrout statistics",
+ "map_item_spec": [
+ {
+ "item_name": "notifyoutv4",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "IPv4 notifies",
+ "item_description": "Number of IPv4 notifies per zone name sent out from Xfrout"
+ },
+ {
+ "item_name": "notifyoutv6",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "IPv6 notifies",
+ "item_description": "Number of IPv6 notifies per zone name sent out from Xfrout"
+ },
+ {
+ "item_name": "xfrrej",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "XFR rejected requests",
+ "item_description": "Number of XFR requests per zone name rejected by Xfrout"
+ },
+ {
+ "item_name": "xfrreqdone",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "Requested zone transfers",
+ "item_description": "Number of requested zone transfers completed per zone name"
+ }
+ ]
+ }
}
},
{
diff --git a/src/lib/python/isc/statistics/tests/testdata/test_spec3.spec b/src/lib/python/isc/statistics/tests/testdata/test_spec3.spec
index 6c06f69..f620cad 100644
--- a/src/lib/python/isc/statistics/tests/testdata/test_spec3.spec
+++ b/src/lib/python/isc/statistics/tests/testdata/test_spec3.spec
@@ -10,110 +10,120 @@
"item_type": "named_set",
"item_optional": false,
"item_default": {
- "_SERVER_" : {
- "soaoutv4": 0,
- "soaoutv6": 0,
- "axfrreqv4": 0,
- "axfrreqv6": 0,
- "ixfrreqv4": 0,
- "ixfrreqv6": 0,
- "xfrsuccess": 0,
- "xfrfail": 0,
- "last_ixfr_duration": 0.0,
- "last_axfr_duration": 0.0
+ "IN" : {
+ "_SERVER_" : {
+ "soaoutv4": 0,
+ "soaoutv6": 0,
+ "axfrreqv4": 0,
+ "axfrreqv6": 0,
+ "ixfrreqv4": 0,
+ "ixfrreqv6": 0,
+ "xfrsuccess": 0,
+ "xfrfail": 0,
+ "last_ixfr_duration": 0.0,
+ "last_axfr_duration": 0.0
+ }
}
},
"item_title": "Zone names",
"item_description": "Zone names for Xfrout statistics",
"named_set_item_spec": {
- "item_name": "zonename",
- "item_type": "map",
+ "item_name": "classname",
+ "item_type": "named_set",
"item_optional": false,
"item_default": {},
- "item_title": "Zone name",
- "item_description": "Zone name for Xfrout statistics",
- "map_item_spec": [
- {
- "item_name": "soaoutv4",
- "item_type": "integer",
- "item_optional": false,
- "item_default": 0,
- "item_title": "SOAOutv4",
- "item_description": "Number of IPv4 SOA queries sent from Xfrin"
- },
- {
- "item_name": "soaoutv6",
- "item_type": "integer",
- "item_optional": false,
- "item_default": 0,
- "item_title": "SOAOutv6",
- "item_description": "Number of IPv6 SOA queries sent from Xfrin"
- },
- {
- "item_name": "axfrreqv4",
- "item_type": "integer",
- "item_optional": false,
- "item_default": 0,
- "item_title": "AXFRReqv4",
- "item_description": "Number of IPv4 AXFR requests sent from Xfrin"
- },
- {
- "item_name": "axfrreqv6",
- "item_type": "integer",
- "item_optional": false,
- "item_default": 0,
- "item_title": "AXFRReqv6",
- "item_description": "Number of IPv6 AXFR requests sent from Xfrin"
- },
- {
- "item_name": "ixfrreqv4",
- "item_type": "integer",
- "item_optional": false,
- "item_default": 0,
- "item_title": "IXFRReqv4",
- "item_description": "Number of IPv4 IXFR requests sent from Xfrin"
- },
- {
- "item_name": "ixfrreqv6",
- "item_type": "integer",
- "item_optional": false,
- "item_default": 0,
- "item_title": "IXFRReqv6",
- "item_description": "Number of IPv6 IXFR requests sent from Xfrin"
- },
- {
- "item_name": "xfrsuccess",
- "item_type": "integer",
- "item_optional": false,
- "item_default": 0,
- "item_title": "XfrSuccess",
- "item_description": "Number of zone transfer requests succeeded"
- },
- {
- "item_name": "xfrfail",
- "item_type": "integer",
- "item_optional": false,
- "item_default": 0,
- "item_title": "XfrFail",
- "item_description": "Number of zone transfer requests failed"
- },
- {
- "item_name": "last_ixfr_duration",
- "item_type": "real",
- "item_optional": false,
- "item_default": 0.0,
- "item_title": "Last IXFR duration",
- "item_description": "Duration of the last IXFR. 0.0 means no successful IXFR done."
- },
- {
- "item_name": "last_axfr_duration",
- "item_type": "real",
- "item_optional": false,
- "item_default": 0.0,
- "item_title": "Last AXFR duration",
- "item_description": "Duration of the last AXFR. 0.0 means no successful AXFR done."
- }
- ]
+ "item_title": "RR class name",
+ "item_description": "RR class name for Xfrout statistics",
+ "named_set_item_spec": {
+ "item_name": "zonename",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "item_title": "Zone name",
+ "item_description": "Zone name for Xfrout statistics",
+ "map_item_spec": [
+ {
+ "item_name": "soaoutv4",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "SOAOutv4",
+ "item_description": "Number of IPv4 SOA queries sent from Xfrin"
+ },
+ {
+ "item_name": "soaoutv6",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "SOAOutv6",
+ "item_description": "Number of IPv6 SOA queries sent from Xfrin"
+ },
+ {
+ "item_name": "axfrreqv4",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "AXFRReqv4",
+ "item_description": "Number of IPv4 AXFR requests sent from Xfrin"
+ },
+ {
+ "item_name": "axfrreqv6",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "AXFRReqv6",
+ "item_description": "Number of IPv6 AXFR requests sent from Xfrin"
+ },
+ {
+ "item_name": "ixfrreqv4",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "IXFRReqv4",
+ "item_description": "Number of IPv4 IXFR requests sent from Xfrin"
+ },
+ {
+ "item_name": "ixfrreqv6",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "IXFRReqv6",
+ "item_description": "Number of IPv6 IXFR requests sent from Xfrin"
+ },
+ {
+ "item_name": "xfrsuccess",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "XfrSuccess",
+ "item_description": "Number of zone transfer requests succeeded"
+ },
+ {
+ "item_name": "xfrfail",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "XfrFail",
+ "item_description": "Number of zone transfer requests failed"
+ },
+ {
+ "item_name": "last_ixfr_duration",
+ "item_type": "real",
+ "item_optional": false,
+ "item_default": 0.0,
+ "item_title": "Last IXFR duration",
+ "item_description": "Duration of the last IXFR. 0.0 means no successful IXFR done."
+ },
+ {
+ "item_name": "last_axfr_duration",
+ "item_type": "real",
+ "item_optional": false,
+ "item_default": 0.0,
+ "item_title": "Last AXFR duration",
+ "item_description": "Duration of the last AXFR. 0.0 means no successful AXFR done."
+ }
+ ]
+ }
}
},
{
diff --git a/src/lib/util/Makefile.am b/src/lib/util/Makefile.am
index 6d07ac8..ff5ef40 100644
--- a/src/lib/util/Makefile.am
+++ b/src/lib/util/Makefile.am
@@ -37,10 +37,6 @@ libb10_util_la_SOURCES += encode/base32hex_from_binary.h
libb10_util_la_SOURCES += encode/base_n.cc encode/hex.h
libb10_util_la_SOURCES += encode/binary_from_base32hex.h
libb10_util_la_SOURCES += encode/binary_from_base16.h
-libb10_util_la_SOURCES += hooks/callout_manager.h hooks/callout_manager.cc
-libb10_util_la_SOURCES += hooks/callout_handle.h hooks/callout_handle.cc
-libb10_util_la_SOURCES += hooks/library_handle.h hooks/library_handle.cc
-libb10_util_la_SOURCES += hooks/server_hooks.h hooks/server_hooks.cc
libb10_util_la_SOURCES += random/qid_gen.h random/qid_gen.cc
libb10_util_la_SOURCES += random/random_number_generator.h
diff --git a/src/lib/util/hooks/callout_handle.cc b/src/lib/util/hooks/callout_handle.cc
deleted file mode 100644
index 8052823..0000000
--- a/src/lib/util/hooks/callout_handle.cc
+++ /dev/null
@@ -1,121 +0,0 @@
-// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#include <util/hooks/callout_handle.h>
-#include <util/hooks/callout_manager.h>
-#include <util/hooks/library_handle.h>
-#include <util/hooks/server_hooks.h>
-
-#include <string>
-#include <utility>
-#include <vector>
-
-using namespace std;
-using namespace isc::util;
-
-namespace isc {
-namespace util {
-
-// Constructor.
-CalloutHandle::CalloutHandle(const boost::shared_ptr<CalloutManager>& manager)
- : arguments_(), context_collection_(), manager_(manager), skip_(false) {
-
- // Call the "context_create" hook. We should be OK doing this - although
- // the constructor has not finished running, all the member variables
- // have been created.
- manager_->callCallouts(ServerHooks::CONTEXT_CREATE, *this);
-}
-
-// Destructor
-CalloutHandle::~CalloutHandle() {
-
- // Call the "context_destroy" hook. We should be OK doing this - although
- // the destructor is being called, all the member variables are still in
- // existence.
- manager_->callCallouts(ServerHooks::CONTEXT_DESTROY, *this);
-}
-
-// Return the name of all argument items.
-
-vector<string>
-CalloutHandle::getArgumentNames() const {
-
- vector<string> names;
- for (ElementCollection::const_iterator i = arguments_.begin();
- i != arguments_.end(); ++i) {
- names.push_back(i->first);
- }
-
- return (names);
-}
-
-// Return the library handle allowing the callout to access the CalloutManager
-// registration/deregistration functions.
-
-LibraryHandle&
-CalloutHandle::getLibraryHandle() const {
- return (manager_->getLibraryHandle());
-}
-
-// Return the context for the currently pointed-to library. This version is
-// used by the "setContext()" method and creates a context for the current
-// library if it does not exist.
-
-CalloutHandle::ElementCollection&
-CalloutHandle::getContextForLibrary() {
- int libindex = manager_->getLibraryIndex();
-
- // Access a reference to the element collection for the given index,
- // creating a new element collection if necessary, and return it.
- return (context_collection_[libindex]);
-}
-
-// The "const" version of the above, used by the "getContext()" method. If
-// the context for the current library doesn't exist, throw an exception.
-
-const CalloutHandle::ElementCollection&
-CalloutHandle::getContextForLibrary() const {
- int libindex = manager_->getLibraryIndex();
-
- ContextCollection::const_iterator libcontext =
- context_collection_.find(libindex);
- if (libcontext == context_collection_.end()) {
- isc_throw(NoSuchCalloutContext, "unable to find callout context "
- "associated with the current library index (" << libindex <<
- ")");
- }
-
- // Return a reference to the context's element collection.
- return (libcontext->second);
-}
-
-// Return the name of all items in the context associated with the current]
-// library.
-
-vector<string>
-CalloutHandle::getContextNames() const {
-
- vector<string> names;
-
- const ElementCollection& elements = getContextForLibrary();
- for (ElementCollection::const_iterator i = elements.begin();
- i != elements.end(); ++i) {
- names.push_back(i->first);
- }
-
- return (names);
-}
-
-} // namespace util
-} // namespace isc
diff --git a/src/lib/util/hooks/callout_handle.h b/src/lib/util/hooks/callout_handle.h
deleted file mode 100644
index 0033505..0000000
--- a/src/lib/util/hooks/callout_handle.h
+++ /dev/null
@@ -1,353 +0,0 @@
-// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#ifndef CALLOUT_HANDLE_H
-#define CALLOUT_HANDLE_H
-
-#include <exceptions/exceptions.h>
-#include <util/hooks/library_handle.h>
-
-#include <boost/any.hpp>
-#include <boost/shared_ptr.hpp>
-
-#include <map>
-#include <string>
-#include <vector>
-
-namespace isc {
-namespace util {
-
-/// @brief No such argument
-///
-/// Thrown if an attempt is made access an argument that does not exist.
-
-class NoSuchArgument : public Exception {
-public:
- NoSuchArgument(const char* file, size_t line, const char* what) :
- isc::Exception(file, line, what) {}
-};
-
-/// @brief No such callout context item
-///
-/// Thrown if an attempt is made to get an item of data from this callout's
-/// context and either the context or an item in the context with that name
-/// does not exist.
-
-class NoSuchCalloutContext : public Exception {
-public:
- NoSuchCalloutContext(const char* file, size_t line, const char* what) :
- isc::Exception(file, line, what) {}
-};
-
-// Forward declaration of the library handle and related collection classes.
-
-class CalloutManager;
-class LibraryHandle;
-
-/// @brief Per-packet callout handle
-///
-/// An object of this class is associated with every packet (or request)
-/// processed by the server. It forms the principle means of passing data
-/// between the server and the user-library callouts.
-///
-/// The class allows access to the following information:
-///
-/// - Arguments. When the callouts associated with a hook are called, they
-/// are passed information by the server (and can return information to it)
-/// through name/value pairs. Each of these pairs is an argument and the
-/// information is accessed through the {get,set}Argument() methods.
-///
-/// - Per-packet context. Each packet has a context associated with it, this
-/// context being on a per-library basis. In other words, As a packet passes
-/// through the callouts associated with a given library, the callouts can
-/// associate and retrieve information with the packet. The per-library
-/// nature of the context means that the callouts within a given library can
-/// pass packet-specific information between one another, but they cannot pass
-/// information to callous within another library. Typically such context
-/// is created in the "context_create" callout and destroyed in the
-/// "context_destroy" callout. The information is accessed through the
-/// {get,set}Context() methods.
-///
-/// - Per-library handle. Allows the callout to dynamically register and
-/// deregister callouts. (In the latter case, only functions registered by
-/// functions in the same library as the callout doing the deregistration
-/// can be removed: callouts registered by other libraries cannot be
-/// modified.)
-
-class CalloutHandle {
-public:
-
- /// Typedef to allow abbreviation of iterator specification in methods.
- /// The std::string is the argument name and the "boost::any" is the
- /// corresponding value associated with it.
- typedef std::map<std::string, boost::any> ElementCollection;
-
- /// Typedef to allow abbreviations in specifications when accessing
- /// context. The ElementCollection is the name/value collection for
- /// a particular context. The "int" corresponds to the index of an
- /// associated library - there is a 1:1 correspondence between libraries
- /// and a name.value collection.
- ///
- /// The collection of contexts is stored in a map, as not every library
- /// will require creation of a context associated with each packet. In
- /// addition, the structure is more flexible in that the size does not
- /// need to be set when the CalloutHandle is constructed.
- typedef std::map<int, ElementCollection> ContextCollection;
-
-
- /// @brief Constructor
- ///
- /// Creates the object and calls the callouts on the "context_create"
- /// hook.
- ///
- /// @param manager Pointer to the callout manager object.
- CalloutHandle(const boost::shared_ptr<CalloutManager>& manager);
-
- /// @brief Destructor
- ///
- /// Calls the context_destroy callback to release any per-packet context.
- ~CalloutHandle();
-
- /// @brief Set argument
- ///
- /// Sets the value of an argument. The argument is created if it does not
- /// already exist.
- ///
- /// @param name Name of the argument.
- /// @param value Value to set. That can be of any data type.
- template <typename T>
- void setArgument(const std::string& name, T value) {
- arguments_[name] = value;
- }
-
- /// @brief Get argument
- ///
- /// Gets the value of an argument.
- ///
- /// @param name Name of the element in the argument list to get.
- /// @param value [out] Value to set. The type of "value" is important:
- /// it must match the type of the value set.
- ///
- /// @throw NoSuchArgument No argument with the given name is present.
- /// @throw boost::bad_any_cast An argument with the given name is present,
- /// but the data type of the value is not the same as the type of
- /// the variable provided to receive the value.
- template <typename T>
- void getArgument(const std::string& name, T& value) const {
- ElementCollection::const_iterator element_ptr = arguments_.find(name);
- if (element_ptr == arguments_.end()) {
- isc_throw(NoSuchArgument, "unable to find argument with name " <<
- name);
- }
-
- value = boost::any_cast<T>(element_ptr->second);
- }
-
- /// @brief Get argument names
- ///
- /// Returns a vector holding the names of arguments in the argument
- /// vector.
- ///
- /// @return Vector of strings reflecting argument names.
- std::vector<std::string> getArgumentNames() const;
-
- /// @brief Delete argument
- ///
- /// Deletes an argument of the given name. If an argument of that name
- /// does not exist, the method is a no-op.
- ///
- /// N.B. If the element is a raw pointer, the pointed-to data is NOT deleted
- /// by this method.
- ///
- /// @param name Name of the element in the argument list to set.
- void deleteArgument(const std::string& name) {
- static_cast<void>(arguments_.erase(name));
- }
-
- /// @brief Delete all arguments
- ///
- /// Deletes all arguments associated with this context.
- ///
- /// N.B. If any elements are raw pointers, the pointed-to data is NOT
- /// deleted by this method.
- void deleteAllArguments() {
- arguments_.clear();
- }
-
- /// @brief Set skip flag
- ///
- /// Sets the "skip" variable in the callout handle. This variable is
- /// interrogated by the server to see if the remaining callouts associated
- /// with the current hook should be bypassed.
- ///
- /// @param skip New value of the "skip" flag.
- void setSkip(bool skip) {
- skip_ = skip;
- }
-
- /// @brief Get skip flag
- ///
- /// Gets the current value of the "skip" flag.
- ///
- /// @return Current value of the skip flag.
- bool getSkip() const {
- return (skip_);
- }
-
- /// @brief Access current library handle
- ///
- /// Returns a reference to the current library handle. This function is
- /// only available when called by a callout (which in turn is called
- /// through the "callCallouts" method), as it is only then that the current
- /// library index is valid. A callout uses the library handle to
- /// dynamically register or deregister callouts.
- ///
- /// @return Reference to the library handle.
- ///
- /// @throw InvalidIndex thrown if this method is called when the current
- /// library index is invalid (typically if it is called outside of
- /// the active callout).
- LibraryHandle& getLibraryHandle() const;
-
- /// @brief Set context
- ///
- /// Sets an element in the context associated with the current library. If
- /// an element of the name is already present, it is replaced.
- ///
- /// @param name Name of the element in the context to set.
- /// @param value Value to set.
- template <typename T>
- void setContext(const std::string& name, T value) {
- getContextForLibrary()[name] = value;
- }
-
- /// @brief Get context
- ///
- /// Gets an element from the context associated with the current library.
- ///
- /// @param name Name of the element in the context to get.
- /// @param value [out] Value to set. The type of "value" is important:
- /// it must match the type of the value set.
- ///
- /// @throw NoSuchCalloutContext Thrown if no context element with the name
- /// "name" is present.
- /// @throw boost::bad_any_cast Thrown if the context element is present
- /// but the type of the data is not the same as the type of the
- /// variable provided to receive its value.
- template <typename T>
- void getContext(const std::string& name, T& value) const {
- const ElementCollection& lib_context = getContextForLibrary();
-
- ElementCollection::const_iterator element_ptr = lib_context.find(name);
- if (element_ptr == lib_context.end()) {
- isc_throw(NoSuchCalloutContext, "unable to find callout context "
- "item " << name << " in the context associated with "
- "current library");
- }
-
- value = boost::any_cast<T>(element_ptr->second);
- }
-
- /// @brief Get context names
- ///
- /// Returns a vector holding the names of items in the context associated
- /// with the current library.
- ///
- /// @return Vector of strings reflecting the names of items in the callout
- /// context associated with the current library.
- std::vector<std::string> getContextNames() const;
-
- /// @brief Delete context element
- ///
- /// Deletes an item of the given name from the context associated with the
- /// current library. If an item of that name does not exist, the method is
- /// a no-op.
- ///
- /// N.B. If the element is a raw pointer, the pointed-to data is NOT deleted
- /// by this.
- ///
- /// @param name Name of the context item to delete.
- void deleteContext(const std::string& name) {
- static_cast<void>(getContextForLibrary().erase(name));
- }
-
- /// @brief Delete all context items
- ///
- /// Deletes all items from the context associated with the current library.
- ///
- /// N.B. If any elements are raw pointers, the pointed-to data is NOT
- /// deleted by this.
- void deleteAllContext() {
- getContextForLibrary().clear();
- }
-
-
-private:
- /// @brief Check index
- ///
- /// Gets the current library index, throwing an exception if it is not set
- /// or is invalid for the current library collection.
- ///
- /// @return Current library index, valid for this library collection.
- ///
- /// @throw InvalidIndex current library index is not valid for the library
- /// handle collection.
- int getLibraryIndex() const;
-
- /// @brief Return reference to context for current library
- ///
- /// Called by all context-setting functions, this returns a reference to
- /// the callout context for the current library, creating a context if it
- /// does not exist.
- ///
- /// @return Reference to the collection of name/value pairs associated
- /// with the current library.
- ///
- /// @throw InvalidIndex current library index is not valid for the library
- /// handle collection.
- ElementCollection& getContextForLibrary();
-
- /// @brief Return reference to context for current library (const version)
- ///
- /// Called by all context-accessing functions, this a reference to the
- /// callout context for the current library. An exception is thrown if
- /// it does not exist.
- ///
- /// @return Reference to the collection of name/value pairs associated
- /// with the current library.
- ///
- /// @throw NoSuchCalloutContext Thrown if there is no ElementCollection
- /// associated with the current library.
- const ElementCollection& getContextForLibrary() const;
-
- // Member variables
-
- /// Collection of arguments passed to the callouts
- ElementCollection arguments_;
-
- /// Context collection - there is one entry per library context.
- ContextCollection context_collection_;
-
- /// Callout manager.
- boost::shared_ptr<CalloutManager> manager_;
-
- /// "Skip" flag, indicating if the caller should bypass remaining callouts.
- bool skip_;
-};
-
-} // namespace util
-} // namespace isc
-
-
-#endif // CALLOUT_HANDLE_H
diff --git a/src/lib/util/hooks/callout_manager.cc b/src/lib/util/hooks/callout_manager.cc
deleted file mode 100644
index af20619..0000000
--- a/src/lib/util/hooks/callout_manager.cc
+++ /dev/null
@@ -1,182 +0,0 @@
-// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#include <util/hooks/callout_handle.h>
-#include <util/hooks/callout_manager.h>
-
-#include <boost/static_assert.hpp>
-
-#include <algorithm>
-#include <functional>
-#include <utility>
-
-using namespace std;
-using namespace isc::util;
-
-namespace isc {
-namespace util {
-
-// Register a callout for the current library.
-
-void
-CalloutManager::registerCallout(const std::string& name, CalloutPtr callout) {
- // Sanity check that the current library index is set to a valid value.
- checkLibraryIndex(current_library_);
-
- // Get the index associated with this hook (validating the name in the
- // process).
- int hook_index = hooks_->getIndex(name);
-
- // Iterate through the callout vector for the hook from start to end,
- // looking for the first entry where the library index is greater than
- // the present index.
- for (CalloutVector::iterator i = hook_vector_[hook_index].begin();
- i != hook_vector_[hook_index].end(); ++i) {
- if (i->first > current_library_) {
- // Found an element whose library index number is greater than the
- // current index, so insert the new element ahead of this one.
- hook_vector_[hook_index].insert(i, make_pair(current_library_,
- callout));
- return;
- }
- }
-
- // Reached the end of the vector, so there is no element in the (possibly
- // empty) set of callouts with a library index greater than the current
- // library index. Inset the callout at the end of the list.
- hook_vector_[hook_index].push_back(make_pair(current_library_, callout));
-}
-
-// Check if callouts are present for a given hook index.
-
-bool
-CalloutManager::calloutsPresent(int hook_index) const {
- // Validate the hook index.
- if ((hook_index < 0) || (hook_index >= hook_vector_.size())) {
- isc_throw(NoSuchHook, "hook index " << hook_index <<
- " is not valid for the list of registered hooks");
- }
-
- // Valid, so are there any callouts associated with that hook?
- return (!hook_vector_[hook_index].empty());
-}
-
-// Call all the callouts for a given hook.
-
-void
-CalloutManager::callCallouts(int hook_index, CalloutHandle& callout_handle) {
-
- // Only initialize and iterate if there are callouts present. This check
- // also catches the case of an invalid index.
- if (calloutsPresent(hook_index)) {
-
- // Clear the "skip" flag so we don't carry state from a previous call.
- callout_handle.setSkip(false);
-
- // Duplicate the callout vector for this hook and work through that.
- // This step is needed because we allow dynamic registration and
- // deregistration of callouts. If a callout attached to a hook modified
- // the list of callouts on that hook, the underlying CalloutVector would
- // change and potentially affect the iteration through that vector.
- CalloutVector callouts(hook_vector_[hook_index]);
-
- // Call all the callouts.
- for (CalloutVector::const_iterator i = callouts.begin();
- i != callouts.end(); ++i) {
- // In case the callout tries to register or deregister a callout,
- // set the current library index to the index associated with the
- // library that registered the callout being called.
- current_library_ = i->first;
-
- // Call the callout
- // @todo Log the return status if non-zero
- static_cast<void>((*i->second)(callout_handle));
- }
-
- // Reset the current library index to an invalid value to catch any
- // programming errors.
- current_library_ = -1;
- }
-}
-
-// Deregister a callout registered by the current library on a particular hook.
-
-bool
-CalloutManager::deregisterCallout(const std::string& name, CalloutPtr callout) {
- // Sanity check that the current library index is set to a valid value.
- checkLibraryIndex(current_library_);
-
- // Get the index associated with this hook (validating the name in the
- // process).
- int hook_index = hooks_->getIndex(name);
-
- /// Construct a CalloutEntry matching the current library and the callout
- /// we want to remove.
- CalloutEntry target(current_library_, callout);
-
- /// To decide if any entries were removed, we'll record the initial size
- /// of the callout vector for the hook, and compare it with the size after
- /// the removal.
- size_t initial_size = hook_vector_[hook_index].size();
-
- // The next bit is standard STL (see "Item 33" in "Effective STL" by
- // Scott Meyers).
- //
- // remove_if reorders the hook vector so that all items not matching
- // the predicate are at the start of the vector and returns a pointer
- // to the next element. (In this case, the predicate is that the item
- // is equal to the value of the passed callout.) The erase() call
- // removes everything from that element to the end of the vector, i.e.
- // all the matching elements.
- hook_vector_[hook_index].erase(remove_if(hook_vector_[hook_index].begin(),
- hook_vector_[hook_index].end(),
- bind1st(equal_to<CalloutEntry>(),
- target)),
- hook_vector_[hook_index].end());
-
- // Return an indication of whether anything was removed.
- return (initial_size != hook_vector_[hook_index].size());
-}
-
-// Deregister all callouts on a given hook.
-
-bool
-CalloutManager::deregisterAllCallouts(const std::string& name) {
-
- // Get the index associated with this hook (validating the name in the
- // process).
- int hook_index = hooks_->getIndex(name);
-
- /// Construct a CalloutEntry matching the current library (the callout
- /// pointer is NULL as we are not checking that).
- CalloutEntry target(current_library_, NULL);
-
- /// To decide if any entries were removed, we'll record the initial size
- /// of the callout vector for the hook, and compare it with the size after
- /// the removal.
- size_t initial_size = hook_vector_[hook_index].size();
-
- // Remove all callouts matching this library.
- hook_vector_[hook_index].erase(remove_if(hook_vector_[hook_index].begin(),
- hook_vector_[hook_index].end(),
- bind1st(CalloutLibraryEqual(),
- target)),
- hook_vector_[hook_index].end());
-
- // Return an indication of whether anything was removed.
- return (initial_size != hook_vector_[hook_index].size());
-}
-
-} // namespace util
-} // namespace isc
diff --git a/src/lib/util/hooks/callout_manager.h b/src/lib/util/hooks/callout_manager.h
deleted file mode 100644
index 7a22433..0000000
--- a/src/lib/util/hooks/callout_manager.h
+++ /dev/null
@@ -1,301 +0,0 @@
-// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#ifndef CALLOUT_MANAGER_H
-#define CALLOUT_MANAGER_H
-
-#include <exceptions/exceptions.h>
-#include <util/hooks/library_handle.h>
-#include <util/hooks/server_hooks.h>
-
-#include <boost/shared_ptr.hpp>
-
-#include <map>
-#include <string>
-
-namespace isc {
-namespace util {
-
-/// @brief No such library
-///
-/// Thrown if an attempt is made to set the current library index to a value
-/// that is invalid for the number of loaded libraries.
-class NoSuchLibrary : public Exception {
-public:
- NoSuchLibrary(const char* file, size_t line, const char* what) :
- isc::Exception(file, line, what) {}
-};
-
-
-/// @brief Callout Manager
-///
-/// This class manages the registration, deregistration and execution of the
-/// library callouts.
-///
-/// In operation, the class needs to know two items of data:
-///
-/// - The list of server hooks, which is used in two ways. Firstly, when a
-/// callout registers or deregisters a hook, it does so by name: the
-/// @ref isc::util::ServerHooks object supplies the names of registered
-/// hooks. Secondly, when the callouts associated with a hook are called by
-/// the server, the server supplies the index of the relevant hook: this is
-/// validated by reference to the list of hook.
-///
-/// - The number of loaded libraries. Each callout registered by a user
-/// library is associated with that library, the callout manager storing both
-/// a pointer to the callout and the index of the library in the list of
-/// loaded libraries. Callouts are allowed to dynamically register and
-/// deregister callouts in the same library (including themselves): they
-/// cannot affect callouts registered by another library. When calling a
-/// callout, the callout manager maintains the idea of a "current library
-/// index": if the callout calls one of the callout registration functions
-/// (indirectly via the @ref LibraryHandle object), the registration
-/// functions use the "current library index" in their processing.
-///
-/// These two items of data are supplied when an object of this class is
-/// constructed.
-///
-/// Note that the callout function do not access the library manager: instead,
-/// they use a LibraryHandle object. This contains an internal pointer to
-/// the CalloutManager, but provides a restricted interface. In that way,
-/// callouts are unable to affect callouts supplied by other libraries.
-
-class CalloutManager {
-private:
-
- // Private typedefs
-
- /// Element in the vector of callouts. The elements in the pair are the
- /// index of the library from which this callout was registered, and a#
- /// pointer to the callout itself.
- typedef std::pair<int, CalloutPtr> CalloutEntry;
-
- /// An element in the hook vector. Each element is a vector of callouts
- /// associated with a given hook.
- typedef std::vector<CalloutEntry> CalloutVector;
-
-public:
-
- /// @brief Constructor
- ///
- /// Initializes member variables, in particular sizing the hook vector
- /// (the vector of callout vectors) to the appropriate size.
- ///
- /// @param hooks Collection of known hook names.
- /// @param num_libraries Number of loaded libraries.
- ///
- /// @throw isc::BadValue if the number of libraries is less than or equal
- /// to 0, or if the pointer to the server hooks object is empty.
- CalloutManager(const boost::shared_ptr<ServerHooks>& hooks,
- int num_libraries)
- : current_library_(-1), hooks_(hooks), hook_vector_(),
- library_handle_(this), num_libraries_(num_libraries)
- {
- if (!hooks) {
- isc_throw(isc::BadValue, "must pass a pointer to a valid server "
- "hooks object to the CalloutManager");
- } else if (num_libraries <= 0) {
- isc_throw(isc::BadValue, "number of libraries passed to the "
- "CalloutManager must be >= 0");
- }
-
- // Parameters OK, do operations that depend on them.
- hook_vector_.resize(hooks_->getCount());
- }
-
- /// @brief Register a callout on a hook for the current library
- ///
- /// Registers a callout function for the current library with a given hook
- /// (the index of the "current library" being given by the current_library_
- /// member). The callout is added to the end of the callouts for this
- /// library that are associated with that hook.
- ///
- /// @param name Name of the hook to which the callout is added.
- /// @param callout Pointer to the callout function to be registered.
- ///
- /// @throw NoSuchHook The hook name is unrecognised.
- /// @throw Unexpected The hook name is valid but an internal data structure
- /// is of the wrong size.
- void registerCallout(const std::string& name, CalloutPtr callout);
-
- /// @brief De-Register a callout on a hook for the current library
- ///
- /// Searches through the functions registered by the the current library
- /// (the index of the "current library" being given by the current_library_
- /// member) with the named hook and removes all entries matching the
- /// callout.
- ///
- /// @param name Name of the hook from which the callout is removed.
- /// @param callout Pointer to the callout function to be removed.
- ///
- /// @return true if a one or more callouts were deregistered.
- ///
- /// @throw NoSuchHook The hook name is unrecognised.
- /// @throw Unexpected The hook name is valid but an internal data structure
- /// is of the wrong size.
- bool deregisterCallout(const std::string& name, CalloutPtr callout);
-
- /// @brief Removes all callouts on a hook for the current library
- ///
- /// Removes all callouts associated with a given hook that were registered
- /// by the current library (the index of the "current library" being given
- /// by the current_library_ member).
- ///
- /// @param name Name of the hook from which the callouts are removed.
- ///
- /// @return true if one or more callouts were deregistered.
- ///
- /// @throw NoSuchHook Thrown if the hook name is unrecognised.
- bool deregisterAllCallouts(const std::string& name);
-
- /// @brief Checks if callouts are present on a hook
- ///
- /// Checks all loaded libraries and returns true if at least one callout
- /// has been registered by any of them for the given hook.
- ///
- /// @param hook_index Hook index for which callouts are checked.
- ///
- /// @return true if callouts are present, false if not.
- ///
- /// @throw NoSuchHook Given index does not correspond to a valid hook.
- bool calloutsPresent(int hook_index) const;
-
- /// @brief Calls the callouts for a given hook
- ///
- /// Iterates through the libray handles and calls the callouts associated
- /// with the given hook index.
- ///
- /// @note This method invalidates the current library index set with
- /// setLibraryIndex().
- ///
- /// @param hook_index Index of the hook to call.
- /// @param callout_handle Reference to the CalloutHandle object for the
- /// current object being processed.
- void callCallouts(int hook_index, CalloutHandle& callout_handle);
-
- /// @brief Get number of libraries
- ///
- /// Returns the number of libraries that this CalloutManager is expected
- /// to serve. This is the number passed to its constructor.
- ///
- /// @return Number of libraries server by this CalloutManager.
- int getNumLibraries() const {
- return (num_libraries_);
- }
-
- /// @brief Get current library index
- ///
- /// Returns the index of the "current" library. This the index associated
- /// with the currently executing callout when callCallouts is executing.
- /// When callCallouts() is not executing (as is the case when the load()
- /// function in a user-library is called during the library load process),
- /// the index can be set by setLibraryIndex().
- ///
- /// @note The value set by this method is lost after a call to
- /// callCallouts.
- ///
- /// @return Current library index.
- int getLibraryIndex() const {
- return (current_library_);
- }
-
- /// @brief Set current library index
- ///
- /// Sets the current library index. This must be in the range 0 to
- /// (numlib - 1), where "numlib" is the number of libraries loaded in the
- /// system, this figure being passed to this object at construction time.
- ///
- /// @param library_index New library index.
- ///
- /// @throw NoSuchLibrary if the index is not valid.
- void setLibraryIndex(int library_index) {
- checkLibraryIndex(library_index);
- current_library_ = library_index;
- }
-
- /// @brief Return library handle
- ///
- /// The library handle is available to the user callout via the callout
- /// handle object. It provides a cut-down view of the CalloutManager,
- /// allowing the callout to register and deregister callouts in the
- /// library of which it is part, whilst denying access to anything that
- /// may affect other libraries.
- ///
- /// @return Reference to callout handle for this manager
- LibraryHandle& getLibraryHandle() {
- return (library_handle_);
- }
-
-private:
- /// @brief Check library index
- ///
- /// Ensures that the current library index is valid. This is called by
- /// the hook registration functions.
- ///
- /// @param library_index Value to check for validity as a library index.
- ///
- /// @throw NoSuchLibrary Library index is not valid.
- void checkLibraryIndex(int library_index) const {
- if ((library_index < 0) || (library_index >= num_libraries_)) {
- isc_throw(NoSuchLibrary, "library index " << library_index <<
- " is not valid for the number of loaded libraries (" <<
- num_libraries_ << ")");
- }
- }
-
- /// @brief Compare two callout entries for library equality
- ///
- /// This is used in callout removal code when all callouts on a hook for a
- /// given library are being removed. It checks whether two callout entries
- /// have the same library index.
- ///
- /// @param ent1 First callout entry to check
- /// @param ent2 Second callout entry to check
- ///
- /// @return bool true if the library entries are the same
- class CalloutLibraryEqual :
- public std::binary_function<CalloutEntry, CalloutEntry, bool> {
- public:
- bool operator()(const CalloutEntry& ent1,
- const CalloutEntry& ent2) const {
- return (ent1.first == ent2.first);
- }
- };
-
- /// Current library index. When a call is made to any of the callout
- /// registration methods, this variable indicates the index of the user
- /// library that should be associated with the call.
- int current_library_;
-
- /// List of server hooks.
- boost::shared_ptr<ServerHooks> hooks_;
-
- /// Vector of callout vectors. There is one entry in this outer vector for
- /// each hook. Each element is itself a vector, with one entry for each
- /// callout registered for that hook.
- std::vector<CalloutVector> hook_vector_;
-
- /// LibraryHandle object user by the callout to access the callout
- /// registration methods on this CalloutManager object.
- LibraryHandle library_handle_;
-
- /// Number of libraries.
- int num_libraries_;
-
-};
-
-} // namespace util
-} // namespace isc
-
-#endif // CALLOUT_MANAGER_H
diff --git a/src/lib/util/hooks/library_handle.cc b/src/lib/util/hooks/library_handle.cc
deleted file mode 100644
index 0a65e54..0000000
--- a/src/lib/util/hooks/library_handle.cc
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#include <util/hooks/callout_manager.h>
-#include <util/hooks/library_handle.h>
-
-namespace isc {
-namespace util {
-
-// Callout manipulation - all deferred to the CalloutManager.
-
-void
-LibraryHandle::registerCallout(const std::string& name, CalloutPtr callout) {
- callout_manager_->registerCallout(name, callout);
-}
-
-bool
-LibraryHandle::deregisterCallout(const std::string& name, CalloutPtr callout) {
- return (callout_manager_->deregisterCallout(name, callout));
-}
-
-bool
-LibraryHandle::deregisterAllCallouts(const std::string& name) {
- return (callout_manager_->deregisterAllCallouts(name));
-}
-
-} // namespace util
-} // namespace isc
diff --git a/src/lib/util/hooks/library_handle.h b/src/lib/util/hooks/library_handle.h
deleted file mode 100644
index 43a78a1..0000000
--- a/src/lib/util/hooks/library_handle.h
+++ /dev/null
@@ -1,115 +0,0 @@
-// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#ifndef LIBRARY_HANDLE_H
-#define LIBRARY_HANDLE_H
-
-#include <string>
-
-namespace isc {
-namespace util {
-
-// Forward declarations
-class CalloutHandle;
-class CalloutManager;
-
-/// Typedef for a callout pointer. (Callouts must have "C" linkage.)
-extern "C" {
- typedef int (*CalloutPtr)(CalloutHandle&);
-};
-
-/// @brief Library handle
-///
-/// This class is accessed by the user library when registering callouts,
-/// either by the library's load() function, or by one of the callouts
-/// themselves.
-///
-/// It is really little more than a shell around the CalloutManager. By
-/// presenting this object to the user-library callouts, callouts can manage
-/// the callout list for their own library, but cannot affect the callouts
-/// registered by other libraries.
-///
-/// (This restriction is achieved by the CalloutManager maintaining the concept
-/// of the "current library". When a callout is registered - either by the
-/// library's load() function, or by a callout in the library - the registration
-/// information includes the library active at the time. When that callout is
-/// called, the CalloutManager uses that information to set the "current
-/// library": the registration functions only operator on data whose
-/// associated library is equal to the "current library".)
-
-class LibraryHandle {
-public:
-
- /// @brief Constructor
- ///
- /// @param manager Back pointer to the containing CalloutManager.
- /// This pointer is used to access appropriate methods in that
- /// object. Note that the "raw" pointer is safe - the only
- /// instance of the LibraryHandle in the system is as a member of
- /// the CalloutManager to which it points.
- LibraryHandle(CalloutManager* manager) : callout_manager_(manager)
- {}
-
- /// @brief Register a callout on a hook
- ///
- /// Registers a callout function with a given hook. The callout is added
- /// to the end of the callouts for the current library that are associated
- /// with that hook.
- ///
- /// @param name Name of the hook to which the callout is added.
- /// @param callout Pointer to the callout function to be registered.
- ///
- /// @throw NoSuchHook The hook name is unrecognised.
- /// @throw Unexpected The hook name is valid but an internal data structure
- /// is of the wrong size.
- void registerCallout(const std::string& name, CalloutPtr callout);
-
- /// @brief De-Register a callout on a hook
- ///
- /// Searches through the functions registered by the current library with
- /// the named hook and removes all entries matching the callout. It does
- /// not affect callouts registered by other libraries.
- ///
- /// @param name Name of the hook from which the callout is removed.
- /// @param callout Pointer to the callout function to be removed.
- ///
- /// @return true if a one or more callouts were deregistered.
- ///
- /// @throw NoSuchHook The hook name is unrecognised.
- /// @throw Unexpected The hook name is valid but an internal data structure
- /// is of the wrong size.
- bool deregisterCallout(const std::string& name, CalloutPtr callout);
-
- /// @brief Removes all callouts on a hook
- ///
- /// Removes all callouts associated with a given hook that were registered.
- /// by the current library. It does not affect callouts that were
- /// registered by other libraries.
- ///
- /// @param name Name of the hook from which the callouts are removed.
- ///
- /// @return true if one or more callouts were deregistered.
- ///
- /// @throw NoSuchHook Thrown if the hook name is unrecognised.
- bool deregisterAllCallouts(const std::string& name);
-
-private:
- /// Back pointer to the collection object for the library
- CalloutManager* callout_manager_;
-};
-
-} // namespace util
-} // namespace isc
-
-#endif // LIBRARY_HANDLE_H
diff --git a/src/lib/util/hooks/server_hooks.cc b/src/lib/util/hooks/server_hooks.cc
deleted file mode 100644
index 478d94c..0000000
--- a/src/lib/util/hooks/server_hooks.cc
+++ /dev/null
@@ -1,125 +0,0 @@
-// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#include <exceptions/exceptions.h>
-#include <util/hooks/server_hooks.h>
-
-#include <utility>
-#include <vector>
-
-using namespace std;
-using namespace isc;
-
-namespace isc {
-namespace util {
-
-// Constructor - register the pre-defined hooks and check that the indexes
-// assigned to them are as expected.
-
-ServerHooks::ServerHooks() {
- int create = registerHook("context_create");
- int destroy = registerHook("context_destroy");
-
- if ((create != CONTEXT_CREATE) || (destroy != CONTEXT_DESTROY)) {
- isc_throw(Unexpected, "pre-defined hook indexes are not as expected. "
- "context_create: expected = " << CONTEXT_CREATE <<
- ", actual = " << create <<
- ". context_destroy: expected = " << CONTEXT_DESTROY <<
- ", actual = " << destroy);
- }
-}
-
-// Register a hook. The index assigned to the hook is the current number
-// of entries in the collection, so ensuring that hook indexes are unique
-// and non-negative.
-
-int
-ServerHooks::registerHook(const string& name) {
-
- // Determine index for the new element and insert.
- int index = hooks_.size();
- pair<HookCollection::iterator, bool> result =
- hooks_.insert(make_pair(name, index));
-
- if (!result.second) {
- // New element was not inserted because an element with the same name
- // already existed.
- isc_throw(DuplicateHook, "hook with name " << name <<
- " is already registered");
- }
-
- // New element inserted, return numeric index.
- return (index);
-}
-
-// Find the index associated with a hook name.
-
-int
-ServerHooks::getIndex(const string& name) const {
-
- // Get iterator to matching element.
- HookCollection::const_iterator i = hooks_.find(name);
- if (i == hooks_.end()) {
- isc_throw(NoSuchHook, "hook name " << name << " is not recognised");
- }
-
- return (i->second);
-}
-
-// Return vector of hook names. The names are not sorted - it is up to the
-// caller to perform sorting if required.
-
-vector<string>
-ServerHooks::getHookNames() const {
-
- vector<string> names;
- HookCollection::const_iterator i;
- for (i = hooks_.begin(); i != hooks_.end(); ++i) {
- names.push_back(i->first);
- }
-
- return (names);
-}
-
-// Hook registration function methods
-
-// Access the hook registration function vector itself
-
-std::vector<HookRegistrationFunction::RegistrationFunctionPtr>&
-HookRegistrationFunction::getFunctionVector() {
- static std::vector<RegistrationFunctionPtr> reg_functions;
- return (reg_functions);
-}
-
-// Constructor - add a registration function to the function vector
-
-HookRegistrationFunction::HookRegistrationFunction(
- HookRegistrationFunction::RegistrationFunctionPtr reg_func) {
- getFunctionVector().push_back(reg_func);
-}
-
-// Execute all registered registration functions
-
-void
-HookRegistrationFunction::execute(ServerHooks& hooks) {
- std::vector<RegistrationFunctionPtr>& reg_functions = getFunctionVector();
- for (int i = 0; i < reg_functions.size(); ++i) {
- (*reg_functions[i])(hooks);
- }
-}
-
-
-
-} // namespace util
-} // namespace isc
diff --git a/src/lib/util/hooks/server_hooks.h b/src/lib/util/hooks/server_hooks.h
deleted file mode 100644
index ce41fa0..0000000
--- a/src/lib/util/hooks/server_hooks.h
+++ /dev/null
@@ -1,207 +0,0 @@
-// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#ifndef SERVER_HOOKS_H
-#define SERVER_HOOKS_H
-
-#include <exceptions/exceptions.h>
-
-#include <map>
-#include <string>
-#include <vector>
-
-namespace isc {
-namespace util {
-
-/// @brief Duplicate hook
-///
-/// Thrown if an attempt is made to register a hook with the same name as a
-/// previously-registered hook.
-class DuplicateHook : public Exception {
-public:
- DuplicateHook(const char* file, size_t line, const char* what) :
- isc::Exception(file, line, what) {}
-};
-
-/// @brief Invalid hook
-///
-/// Thrown if an attempt is made to get the index for an invalid hook.
-class NoSuchHook : public Exception {
-public:
- NoSuchHook(const char* file, size_t line, const char* what) :
- isc::Exception(file, line, what) {}
-};
-
-
-/// @brief Server hook collection
-///
-/// This class is used by the server-side code to register hooks - points in the
-/// server processing at which libraries can register functions (callouts) that
-/// the server will call. These functions can modify data and so affect the
-/// processing of the server.
-///
-/// The ServerHooks class is little more than a wrapper around the std::map
-/// class. It stores a hook, assigning to it a unique index number. This
-/// number is then used by the server code to identify the hook being called.
-/// (Although it would be feasible to use a name as an index, using an integer
-/// will speed up the time taken to locate the callouts, which may make a
-/// difference in a frequently-executed piece of code.)
-
-class ServerHooks {
-public:
-
- /// Index numbers for pre-defined hooks.
- static const int CONTEXT_CREATE = 0;
- static const int CONTEXT_DESTROY = 1;
-
- /// @brief Constructor
- ///
- /// This pre-registers two hooks, context_create and context_destroy, which
- /// are called by the server before processing a packet and after processing
- /// for the packet has completed. They allow the server code to allocate
- /// and destroy per-packet context.
- ///
- /// @throws isc::Unexpected if the registration of the pre-defined hooks
- /// fails in some way.
- ServerHooks();
-
- /// @brief Register a hook
- ///
- /// Registers a hook and returns the hook index.
- ///
- /// @param name Name of the hook
- ///
- /// @return Index of the hook, to be used in subsequent hook-related calls.
- /// This will be greater than or equal to zero (so allowing a
- /// negative value to indicate an invalid index).
- ///
- /// @throws DuplicateHook A hook with the same name has already been
- /// registered.
- int registerHook(const std::string& name);
-
- /// @brief Get hook index
- ///
- /// Returns the index of a hook.
- ///
- /// @param name Name of the hook
- ///
- /// @return Index of the hook, to be used in subsequent calls.
- ///
- /// @throw NoSuchHook if the hook name is unknown to the caller.
- int getIndex(const std::string& name) const;
-
- /// @brief Return number of hooks
- ///
- /// Returns the total number of hooks registered.
- ///
- /// @return Number of hooks registered.
- int getCount() const {
- return (hooks_.size());
- }
-
- /// @brief Get hook names
- ///
- /// Return list of hooks registered in the object.
- ///
- /// @return Vector of strings holding hook names.
- std::vector<std::string> getHookNames() const;
-
-private:
- typedef std::map<std::string, int> HookCollection;
-
- HookCollection hooks_; ///< Hook name/index collection
-};
-
-
-/// @brief Hooks Registration
-///
-/// All hooks must be registered before libraries are loaded and callouts
-/// assigned to them. One way of doing this is to have a global list of hooks:
-/// the addition of any hook anywhere would require updating the list. This
-/// is possible and, if desired, the author of a server can do it.
-///
-/// An alternative is the option provided here, where each component of BIND 10
-/// registers the hooks they are using. To do this, the component should
-/// create a hook registration function of the form:
-///
-/// @code
-/// static int hook1_num = -1; // Initialize number for hook 1
-/// static int hook2_num = -1; // Initialize number for hook 2
-///
-/// void myModuleRegisterHooks(ServerHooks& hooks) {
-/// hook1_num = hooks.registerHook("hook1");
-/// hook2_num = hooks.registerHook("hook2");
-/// }
-/// @endcode
-///
-/// ... which registers the hooks and stores the associated hook index. To
-/// avoid the need to add an explicit call to each of the hook registration
-/// functions to the server initialization code, the component should declare
-/// an object of this class in the same file as the registration function,
-/// but outside of any function. The declaration should include the name
-/// of the registration function, i.e.
-///
-/// @code
-/// HookRegistrationFunction f(myModuleRegisterHooks);
-/// @code
-///
-/// The constructor of this object will run prior to main() getting called and
-/// will add the registration function to a list of such functions. The server
-/// then calls the static class method "execute()" to run all the declared
-/// registration functions.
-
-class HookRegistrationFunction {
-public:
- /// @brief Pointer to a hook registration function
- typedef void (*RegistrationFunctionPtr)(ServerHooks&);
-
- /// @brief Constructor
- ///
- /// For variables declared outside functions or methods, the constructors
- /// are run after the program is loaded and before main() is called. This
- /// constructor adds the passed pointer to a vector of such pointers.
- HookRegistrationFunction(RegistrationFunctionPtr reg_func);
-
- /// @brief Access registration function vector
- ///
- /// One of the problems with functions run prior to starting main() is the
- /// "static initialization fiasco". This occurs because the order in which
- /// objects outside functions are constructed is not defined. So if this
- /// constructor were to depend on a vector declared externally, we would
- /// not be able to guarantee that the vector had been initialised properly
- /// before we used it.
- ///
- /// To get round this situation, the vector is declared statically within
- /// a static function. The first time the function is called, the vector
- /// is initialized before it is used.
- ///
- /// This function returns a reference to the vector used to hold the
- /// pointers.
- ///
- /// @return Reference to the (static) list of registration functions
- static std::vector<RegistrationFunctionPtr>& getFunctionVector();
-
- /// @brief Execute registration functions
- ///
- /// Called by the server initialization code, this function executes all
- /// registered hook registration functions.
- ///
- /// @param hooks ServerHooks object to which hook information will be added.
- static void execute(ServerHooks& hooks);
-};
-
-} // namespace util
-} // namespace isc
-
-#endif // SERVER_HOOKS_H
diff --git a/src/lib/util/tests/Makefile.am b/src/lib/util/tests/Makefile.am
index ceb8b95..ab85fa2 100644
--- a/src/lib/util/tests/Makefile.am
+++ b/src/lib/util/tests/Makefile.am
@@ -25,13 +25,10 @@ run_unittests_SOURCES = run_unittests.cc
run_unittests_SOURCES += base32hex_unittest.cc
run_unittests_SOURCES += base64_unittest.cc
run_unittests_SOURCES += buffer_unittest.cc
-run_unittests_SOURCES += callout_handle_unittest.cc
-run_unittests_SOURCES += callout_manager_unittest.cc
run_unittests_SOURCES += fd_share_tests.cc
run_unittests_SOURCES += fd_tests.cc
run_unittests_SOURCES += filename_unittest.cc
run_unittests_SOURCES += hex_unittest.cc
-run_unittests_SOURCES += handles_unittest.cc
run_unittests_SOURCES += io_utilities_unittest.cc
run_unittests_SOURCES += lru_list_unittest.cc
run_unittests_SOURCES += memory_segment_local_unittest.cc
@@ -42,7 +39,6 @@ run_unittests_SOURCES += memory_segment_common_unittest.h
run_unittests_SOURCES += memory_segment_common_unittest.cc
run_unittests_SOURCES += qid_gen_unittest.cc
run_unittests_SOURCES += random_number_generator_unittest.cc
-run_unittests_SOURCES += server_hooks_unittest.cc
run_unittests_SOURCES += sha1_unittest.cc
run_unittests_SOURCES += socketsession_unittest.cc
run_unittests_SOURCES += strutil_unittest.cc
diff --git a/src/lib/util/tests/callout_handle_unittest.cc b/src/lib/util/tests/callout_handle_unittest.cc
deleted file mode 100644
index 76e18d8..0000000
--- a/src/lib/util/tests/callout_handle_unittest.cc
+++ /dev/null
@@ -1,329 +0,0 @@
-// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#include <util/hooks/callout_handle.h>
-#include <util/hooks/callout_manager.h>
-#include <util/hooks/library_handle.h>
-#include <util/hooks/server_hooks.h>
-
-#include <boost/shared_ptr.hpp>
-
-#include <gtest/gtest.h>
-
-using namespace isc::util;
-using namespace std;
-
-namespace {
-
-/// @file
-/// @brief Holds the CalloutHandle argument tests
-///
-/// Additional testing of the CalloutHandle - together with the interaction
-/// of the LibraryHandle - is done in the handles_unittests set of tests.
-
-class CalloutHandleTest : public ::testing::Test {
-public:
-
- /// @brief Constructor
- ///
- /// Sets up a callout manager to be referenced by the CalloutHandle in
- /// these tests. (The "4" for the number of libraries in the
- /// CalloutManager is arbitrary - it is not used in these tests.)
- CalloutHandleTest()
- : hooks_(new ServerHooks()), manager_(new CalloutManager(hooks_, 4))
- {}
-
- /// Obtain hook manager
- boost::shared_ptr<CalloutManager>& getCalloutManager() {
- return (manager_);
- }
-
-private:
- /// List of server hooks
- boost::shared_ptr<ServerHooks> hooks_;
-
- /// Callout manager accessed by this CalloutHandle.
- boost::shared_ptr<CalloutManager> manager_;
-};
-
-// *** Argument Tests ***
-//
-// The first set of tests check that the CalloutHandle can store and retrieve
-// arguments. These are very similar to the LibraryHandle context tests.
-
-// Test that we can store multiple values of the same type and that they
-// are distinct.
-
-TEST_F(CalloutHandleTest, ArgumentDistinctSimpleType) {
- CalloutHandle handle(getCalloutManager());
-
- // Store and retrieve an int (random value).
- int a = 42;
- handle.setArgument("integer1", a);
- EXPECT_EQ(42, a);
-
- int b = 0;
- handle.getArgument("integer1", b);
- EXPECT_EQ(42, b);
-
- // Add another integer (another random value).
- int c = 142;
- handle.setArgument("integer2", c);
- EXPECT_EQ(142, c);
-
- int d = 0;
- handle.getArgument("integer2", d);
- EXPECT_EQ(142, d);
-
- // Add a short (random value).
- short e = -81;
- handle.setArgument("short", e);
- EXPECT_EQ(-81, e);
-
- short f = 0;
- handle.getArgument("short", f);
- EXPECT_EQ(-81, f);
-}
-
-// Test that trying to get an unknown argument throws an exception.
-
-TEST_F(CalloutHandleTest, ArgumentUnknownName) {
- CalloutHandle handle(getCalloutManager());
-
- // Set an integer
- int a = 42;
- handle.setArgument("integer1", a);
- EXPECT_EQ(42, a);
-
- // Check we can retrieve it
- int b = 0;
- handle.getArgument("integer1", b);
- EXPECT_EQ(42, b);
-
- // Check that getting an unknown name throws an exception.
- int c = 0;
- EXPECT_THROW(handle.getArgument("unknown", c), NoSuchArgument);
-}
-
-// Test that trying to get an argument with an incorrect type throws an
-// exception.
-
-TEST_F(CalloutHandleTest, ArgumentIncorrectType) {
- CalloutHandle handle(getCalloutManager());
-
- // Set an integer
- int a = 42;
- handle.setArgument("integer1", a);
- EXPECT_EQ(42, a);
-
- // Check we can retrieve it
- long b = 0;
- EXPECT_THROW(handle.getArgument("integer1", b), boost::bad_any_cast);
-}
-
-// Now try with some very complex types. The types cannot be defined within
-// the function and they should contain a copy constructor. For this reason,
-// a simple "struct" is used.
-
-struct Alpha {
- int a;
- int b;
- Alpha(int first = 0, int second = 0) : a(first), b(second) {}
-};
-
-struct Beta {
- int c;
- int d;
- Beta(int first = 0, int second = 0) : c(first), d(second) {}
-};
-
-TEST_F(CalloutHandleTest, ComplexTypes) {
- CalloutHandle handle(getCalloutManager());
-
- // Declare two variables of different (complex) types. (Note as to the
- // variable names: aleph and beth are the first two letters of the Hebrew
- // alphabet.)
- Alpha aleph(1, 2);
- EXPECT_EQ(1, aleph.a);
- EXPECT_EQ(2, aleph.b);
- handle.setArgument("aleph", aleph);
-
- Beta beth(11, 22);
- EXPECT_EQ(11, beth.c);
- EXPECT_EQ(22, beth.d);
- handle.setArgument("beth", beth);
-
- // Ensure we can extract the data correctly.
- Alpha aleph2;
- EXPECT_EQ(0, aleph2.a);
- EXPECT_EQ(0, aleph2.b);
- handle.getArgument("aleph", aleph2);
- EXPECT_EQ(1, aleph2.a);
- EXPECT_EQ(2, aleph2.b);
-
- Beta beth2;
- EXPECT_EQ(0, beth2.c);
- EXPECT_EQ(0, beth2.d);
- handle.getArgument("beth", beth2);
- EXPECT_EQ(11, beth2.c);
- EXPECT_EQ(22, beth2.d);
-
- // Ensure that complex types also thrown an exception if we attempt to
- // get a context element of the wrong type.
- EXPECT_THROW(handle.getArgument("aleph", beth), boost::bad_any_cast);
-}
-
-// Check that the context can store pointers. And also check that it respects
-// that a "pointer to X" is not the same as a "pointer to const X".
-
-TEST_F(CalloutHandleTest, PointerTypes) {
- CalloutHandle handle(getCalloutManager());
-
- // Declare a couple of variables, const and non-const.
- Alpha aleph(5, 10);
- const Beta beth(15, 20);
-
- Alpha* pa = ℵ
- const Beta* pcb = ℶ
-
- // Check pointers can be set and retrieved OK.
- handle.setArgument("non_const_pointer", pa);
- handle.setArgument("const_pointer", pcb);
-
- Alpha* pa2 = 0;
- handle.getArgument("non_const_pointer", pa2);
- EXPECT_TRUE(pa == pa2);
-
- const Beta* pcb2 = 0;
- handle.getArgument("const_pointer", pcb2);
- EXPECT_TRUE(pcb == pcb2);
-
- // Check that the "const" is protected in the context.
- const Alpha* pca3;
- EXPECT_THROW(handle.getArgument("non_const_pointer", pca3),
- boost::bad_any_cast);
-
- Beta* pb3;
- EXPECT_THROW(handle.getArgument("const_pointer", pb3),
- boost::bad_any_cast);
-}
-
-// Check that we can get the names of the arguments.
-
-TEST_F(CalloutHandleTest, ContextItemNames) {
- CalloutHandle handle(getCalloutManager());
-
- vector<string> expected_names;
-
- expected_names.push_back("faith");
- handle.setArgument("faith", 42);
- expected_names.push_back("hope");
- handle.setArgument("hope", 43);
- expected_names.push_back("charity");
- handle.setArgument("charity", 44);
-
- // Get the names and check against the expected names. We'll sort
- // both arrays to simplify the checking.
- vector<string> actual_names = handle.getArgumentNames();
-
- sort(actual_names.begin(), actual_names.end());
- sort(expected_names.begin(), expected_names.end());
- EXPECT_TRUE(expected_names == actual_names);
-}
-
-// Test that we can delete an argument.
-
-TEST_F(CalloutHandleTest, DeleteArgument) {
- CalloutHandle handle(getCalloutManager());
-
- int one = 1;
- int two = 2;
- int three = 3;
- int four = 4;
- int value; // Return value
-
- handle.setArgument("one", one);
- handle.setArgument("two", two);
- handle.setArgument("three", three);
- handle.setArgument("four", four);
-
- // Delete "one".
- handle.getArgument("one", value);
- EXPECT_EQ(1, value);
- handle.deleteArgument("one");
-
- EXPECT_THROW(handle.getArgument("one", value), NoSuchArgument);
- handle.getArgument("two", value);
- EXPECT_EQ(2, value);
- handle.getArgument("three", value);
- EXPECT_EQ(3, value);
- handle.getArgument("four", value);
- EXPECT_EQ(4, value);
-
- // Delete "three".
- handle.getArgument("three", value);
- EXPECT_EQ(3, value);
- handle.deleteArgument("three");
-
- EXPECT_THROW(handle.getArgument("one", value), NoSuchArgument);
- handle.getArgument("two", value);
- EXPECT_EQ(2, value);
- EXPECT_THROW(handle.getArgument("three", value), NoSuchArgument);
- handle.getArgument("four", value);
- EXPECT_EQ(4, value);
-}
-
-// Test that we can delete all arguments.
-
-TEST_F(CalloutHandleTest, DeleteAllArguments) {
- CalloutHandle handle(getCalloutManager());
-
- int one = 1;
- int two = 2;
- int three = 3;
- int four = 4;
- int value; // Return value
-
- // Set the arguments. The previous test verifies that this works.
- handle.setArgument("one", one);
- handle.setArgument("two", two);
- handle.setArgument("three", three);
- handle.setArgument("four", four);
-
- // Delete all arguments...
- handle.deleteAllArguments();
-
- // ... and check that none are left.
- EXPECT_THROW(handle.getArgument("one", value), NoSuchArgument);
- EXPECT_THROW(handle.getArgument("two", value), NoSuchArgument);
- EXPECT_THROW(handle.getArgument("three", value), NoSuchArgument);
- EXPECT_THROW(handle.getArgument("four", value), NoSuchArgument);
-}
-
-// Test the "skip" flag.
-
-TEST_F(CalloutHandleTest, SkipFlag) {
- CalloutHandle handle(getCalloutManager());
-
- // Should be false on construction.
- EXPECT_FALSE(handle.getSkip());
-
- handle.setSkip(true);
- EXPECT_TRUE(handle.getSkip());
-
- handle.setSkip(false);
- EXPECT_FALSE(handle.getSkip());
-}
-
-} // Anonymous namespace
diff --git a/src/lib/util/tests/callout_manager_unittest.cc b/src/lib/util/tests/callout_manager_unittest.cc
deleted file mode 100644
index ca9b8ad..0000000
--- a/src/lib/util/tests/callout_manager_unittest.cc
+++ /dev/null
@@ -1,765 +0,0 @@
-// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#include <exceptions/exceptions.h>
-#include <util/hooks/callout_handle.h>
-#include <util/hooks/callout_manager.h>
-#include <util/hooks/library_handle.h>
-#include <util/hooks/server_hooks.h>
-
-#include <boost/scoped_ptr.hpp>
-#include <gtest/gtest.h>
-
-#include <algorithm>
-#include <string>
-#include <vector>
-
-/// @file
-/// @brief CalloutManager and LibraryHandle tests
-///
-/// These set of tests check the CalloutManager and LibraryHandle. They are
-/// together in the same file because the LibraryHandle is little more than a
-/// restricted interface to the CalloutManager, and a lot of the support
-/// structure for the tests is common.
-
-using namespace isc;
-using namespace isc::util;
-using namespace std;
-
-namespace {
-
-class CalloutManagerTest : public ::testing::Test {
-public:
- /// @brief Constructor
- ///
- /// Sets up a collection of three LibraryHandle objects to use in the test.
- CalloutManagerTest() : hooks_(new ServerHooks()) {
-
- // Set up the server hooks
- alpha_index_ = hooks_->registerHook("alpha");
- beta_index_ = hooks_->registerHook("beta");
- gamma_index_ = hooks_->registerHook("gamma");
- delta_index_ = hooks_->registerHook("delta");
-
- // Set up the callout manager with these hooks. Assume a maximum of
- // four libraries.
- callout_manager_.reset(new CalloutManager(hooks_, 10));
-
- // Set up the callout handle.
- callout_handle_.reset(new CalloutHandle(callout_manager_));
-
- // Initialize the static variable.
- callout_value_ = 0;
- }
-
- /// @brief Return the callout handle
- CalloutHandle& getCalloutHandle() {
- return (*callout_handle_);
- }
-
- /// @brief Return the callout manager
- boost::shared_ptr<CalloutManager> getCalloutManager() {
- return (callout_manager_);
- }
-
- boost::shared_ptr<ServerHooks> getServerHooks() {
- return (hooks_);
- }
-
- /// Static variable used for accumulating information
- static int callout_value_;
-
- /// Hook indexes. These are somewhat ubiquitous, so are made public for
- /// ease of reference instead of being accessible by a function.
- int alpha_index_;
- int beta_index_;
- int gamma_index_;
- int delta_index_;
-
-private:
- /// Callout handle used in calls
- boost::shared_ptr<CalloutHandle> callout_handle_;
-
- /// Callout manager used for the test
- boost::shared_ptr<CalloutManager> callout_manager_;
-
- /// Server hooks
- boost::shared_ptr<ServerHooks> hooks_;
-};
-
-// Definition of the static variable.
-int CalloutManagerTest::callout_value_ = 0;
-
-// Callout definitions
-//
-// The callouts defined here are structured in such a way that it is possible
-// to determine the order in which they are called and whether they are called
-// at all. The method used is simple - after a sequence of callouts, the digits
-// in the value, reading left to right, determines the order of the callouts
-// called. For example, callout one followed by two followed by three followed
-// by two followed by one results in a value of 12321.
-//
-// Functions return a zero to indicate success.
-
-extern "C" {
-int callout_general(int number) {
- CalloutManagerTest::callout_value_ =
- 10 * CalloutManagerTest::callout_value_ + number;
- return (0);
-}
-
-int callout_one(CalloutHandle&) {
- return (callout_general(1));
-}
-
-int callout_two(CalloutHandle&) {
- return (callout_general(2));
-}
-
-int callout_three(CalloutHandle&) {
- return (callout_general(3));
-}
-
-int callout_four(CalloutHandle&) {
- return (callout_general(4));
-}
-
-int callout_five(CalloutHandle&) {
- return (callout_general(5));
-}
-
-int callout_six(CalloutHandle&) {
- return (callout_general(6));
-}
-
-int callout_seven(CalloutHandle&) {
- return (callout_general(7));
-}
-
-// The next functions are duplicates of some of the above, but return an error.
-
-int callout_one_error(CalloutHandle& handle) {
- (void) callout_one(handle);
- return (1);
-}
-
-int callout_two_error(CalloutHandle& handle) {
- (void) callout_two(handle);
- return (1);
-}
-
-int callout_three_error(CalloutHandle& handle) {
- (void) callout_three(handle);
- return (1);
-}
-
-int callout_four_error(CalloutHandle& handle) {
- (void) callout_four(handle);
- return (1);
-}
-
-}; // extern "C"
-
-// *** Callout Tests ***
-//
-// The next set of tests check that callouts can be called.
-
-// Constructor - check that we trap bad parameters.
-
-TEST_F(CalloutManagerTest, BadConstructorParameters) {
- boost::scoped_ptr<CalloutManager> cm;
-
- // Invalid number of libraries
- EXPECT_THROW(cm.reset(new CalloutManager(getServerHooks(), 0)), BadValue);
- EXPECT_THROW(cm.reset(new CalloutManager(getServerHooks(), -1)), BadValue);
-
- // Invalid server hooks pointer.
- boost::shared_ptr<ServerHooks> sh;
- EXPECT_THROW(cm.reset(new CalloutManager(sh, 4)), BadValue);
-}
-
-// Check the number of libraries is reported successfully.
-
-TEST_F(CalloutManagerTest, GetNumLibraries) {
- boost::scoped_ptr<CalloutManager> cm;
-
- // Check two valid values of number of libraries to ensure that the
- // GetNumLibraries() returns the value set.
- EXPECT_NO_THROW(cm.reset(new CalloutManager(getServerHooks(), 4)));
- EXPECT_EQ(4, cm->getNumLibraries());
-
- EXPECT_NO_THROW(cm.reset(new CalloutManager(getServerHooks(), 42)));
- EXPECT_EQ(42, cm->getNumLibraries());
-}
-
-// Check that we can only set the current library index to the correct values.
-
-TEST_F(CalloutManagerTest, CheckLibraryIndex) {
- // Check valid indexes
- for (int i = 0; i < 4; ++i) {
- EXPECT_NO_THROW(getCalloutManager()->setLibraryIndex(i));
- }
-
- // Check invalid ones
- EXPECT_THROW(getCalloutManager()->setLibraryIndex(-1), NoSuchLibrary);
- EXPECT_THROW(getCalloutManager()->setLibraryIndex(15), NoSuchLibrary);
-}
-
-// Check that we can only register callouts on valid hook names.
-
-TEST_F(CalloutManagerTest, ValidHookNames) {
- getCalloutManager()->setLibraryIndex(0);
- EXPECT_NO_THROW(getCalloutManager()->registerCallout("alpha", callout_one));
- EXPECT_THROW(getCalloutManager()->registerCallout("unknown", callout_one),
- NoSuchHook);
-}
-
-
-// Check we can register callouts appropriately.
-
-TEST_F(CalloutManagerTest, RegisterCallout) {
- // Ensure that no callouts are attached to any of the hooks.
- EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
- EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
-
- // Set up so that hooks "alpha" and "beta" have callouts attached from a
- // different libraries.
- getCalloutManager()->setLibraryIndex(0);
- getCalloutManager()->registerCallout("alpha", callout_one);
- getCalloutManager()->setLibraryIndex(1);
- getCalloutManager()->registerCallout("beta", callout_two);
-
- // Check all is as expected.
- EXPECT_TRUE(getCalloutManager()->calloutsPresent(alpha_index_));
- EXPECT_TRUE(getCalloutManager()->calloutsPresent(beta_index_));
- EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
- EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
-
- // Check that calling the callouts returns as expected. (This is also a
- // test of the callCallouts method.)
- callout_value_ = 0;
- getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
- EXPECT_EQ(1, callout_value_);
-
- callout_value_ = 0;
- getCalloutManager()->callCallouts(beta_index_, getCalloutHandle());
- EXPECT_EQ(2, callout_value_);
-
- // Register some more callouts from different libraries on hook "alpha".
- getCalloutManager()->setLibraryIndex(2);
- getCalloutManager()->registerCallout("alpha", callout_three);
- getCalloutManager()->registerCallout("alpha", callout_four);
- getCalloutManager()->setLibraryIndex(3);
- getCalloutManager()->registerCallout("alpha", callout_five);
-
- // Check it is as expected.
- callout_value_ = 0;
- getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
- EXPECT_EQ(1345, callout_value_);
-
- // ... and check the additional callouts were not registered on the "beta"
- // hook.
- callout_value_ = 0;
- getCalloutManager()->callCallouts(beta_index_, getCalloutHandle());
- EXPECT_EQ(2, callout_value_);
-
- // Add another callout to hook "alpha" from library index 2 - this should
- // appear at the end of the callout list for that library.
- getCalloutManager()->setLibraryIndex(2);
- getCalloutManager()->registerCallout("alpha", callout_six);
- callout_value_ = 0;
- getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
- EXPECT_EQ(13465, callout_value_);
-
- // Add a callout from library index 1 - this should appear between the
- // callouts from library index 0 and linrary index 2.
- getCalloutManager()->setLibraryIndex(1);
- getCalloutManager()->registerCallout("alpha", callout_seven);
- callout_value_ = 0;
- getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
- EXPECT_EQ(173465, callout_value_);
-}
-
-// Check the "calloutsPresent()" method.
-
-TEST_F(CalloutManagerTest, CalloutsPresent) {
- // Ensure that no callouts are attached to any of the hooks.
- EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
- EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
- EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
- EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
-
- // Set up so that hooks "alpha", "beta" and "delta" have callouts attached
- // to them, and callout "gamma" does not. (In the statements below, the
- // exact callouts attached to a hook are not relevant - only the fact
- // that some callouts are). Chose the libraries for which the callouts
- // are registered randomly.
- getCalloutManager()->setLibraryIndex(0);
- getCalloutManager()->registerCallout("alpha", callout_one);
-
- getCalloutManager()->setLibraryIndex(1);
- getCalloutManager()->registerCallout("alpha", callout_two);
- getCalloutManager()->registerCallout("beta", callout_two);
-
- getCalloutManager()->setLibraryIndex(3);
- getCalloutManager()->registerCallout("alpha", callout_three);
- getCalloutManager()->registerCallout("delta", callout_four);
-
- // Check all is as expected.
- EXPECT_TRUE(getCalloutManager()->calloutsPresent(alpha_index_));
- EXPECT_TRUE(getCalloutManager()->calloutsPresent(beta_index_));
- EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
- EXPECT_TRUE(getCalloutManager()->calloutsPresent(delta_index_));
-
- // Check we fail on an invalid hook index.
- EXPECT_THROW(getCalloutManager()->calloutsPresent(42), NoSuchHook);
- EXPECT_THROW(getCalloutManager()->calloutsPresent(-1), NoSuchHook);
-}
-
-// Test that calling a hook with no callouts on it returns success.
-
-TEST_F(CalloutManagerTest, CallNoCallouts) {
- // Ensure that no callouts are attached to any of the hooks.
- EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
- EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
- EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
- EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
-
- // Call the callouts on an arbitrary hook and ensure that nothing happens.
- callout_value_ = 475;
- getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
- EXPECT_EQ(475, callout_value_); // Unchanged
-}
-
-// Test that the callouts are called in the correct order (i.e. the callouts
-// from the first library in the order they were registered, then the callouts
-// from the second library in the order they were registered etc.)
-
-TEST_F(CalloutManagerTest, CallCalloutsSuccess) {
- // Ensure that no callouts are attached to any of the hooks.
- EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
- EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
- EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
- EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
-
- // Each library contributes one callout on hook "alpha".
- callout_value_ = 0;
- getCalloutManager()->setLibraryIndex(1);
- getCalloutManager()->registerCallout("alpha", callout_one);
- getCalloutManager()->setLibraryIndex(1);
- getCalloutManager()->registerCallout("alpha", callout_two);
- getCalloutManager()->setLibraryIndex(2);
- getCalloutManager()->registerCallout("alpha", callout_three);
- getCalloutManager()->setLibraryIndex(3);
- getCalloutManager()->registerCallout("alpha", callout_four);
- getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
- EXPECT_EQ(1234, callout_value_);
-
- // Do a random selection of callouts on hook "beta".
- callout_value_ = 0;
- getCalloutManager()->setLibraryIndex(0);
- getCalloutManager()->registerCallout("beta", callout_one);
- getCalloutManager()->registerCallout("beta", callout_three);
- getCalloutManager()->setLibraryIndex(1);
- getCalloutManager()->registerCallout("beta", callout_two);
- getCalloutManager()->setLibraryIndex(3);
- getCalloutManager()->registerCallout("beta", callout_four);
- getCalloutManager()->callCallouts(beta_index_, getCalloutHandle());
- EXPECT_EQ(1324, callout_value_);
-
- // Ensure that calling the callouts on a hook with no callouts works.
- callout_value_ = 0;
- getCalloutManager()->callCallouts(gamma_index_, getCalloutHandle());
- EXPECT_EQ(0, callout_value_);
-}
-
-// Test that the callouts are called in order, but that callouts occurring
-// after a callout that returns an error are not called.
-//
-// (Note: in this test, the callouts that return an error set the value of
-// callout_value_ before they return the error code.)
-
-TEST_F(CalloutManagerTest, CallCalloutsError) {
- // Ensure that no callouts are attached to any of the hooks.
- EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
- EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
- EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
- EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
-
- // Each library contributing one callout on hook "alpha". The first callout
- // returns an error (after adding its value to the result).
- callout_value_ = 0;
- getCalloutManager()->setLibraryIndex(0);
- getCalloutManager()->registerCallout("alpha", callout_one_error);
- getCalloutManager()->setLibraryIndex(1);
- getCalloutManager()->registerCallout("alpha", callout_two);
- getCalloutManager()->setLibraryIndex(2);
- getCalloutManager()->registerCallout("alpha", callout_three);
- getCalloutManager()->setLibraryIndex(3);
- getCalloutManager()->registerCallout("alpha", callout_four);
- getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
- EXPECT_EQ(1234, callout_value_);
-
- // Each library contributing multiple callouts on hook "beta". The last
- // callout on the first library returns an error.
- callout_value_ = 0;
- getCalloutManager()->setLibraryIndex(0);
- getCalloutManager()->registerCallout("beta", callout_one);
- getCalloutManager()->registerCallout("beta", callout_one_error);
- getCalloutManager()->setLibraryIndex(1);
- getCalloutManager()->registerCallout("beta", callout_two);
- getCalloutManager()->registerCallout("beta", callout_two);
- getCalloutManager()->registerCallout("beta", callout_three);
- getCalloutManager()->registerCallout("beta", callout_three);
- getCalloutManager()->setLibraryIndex(3);
- getCalloutManager()->registerCallout("beta", callout_four);
- getCalloutManager()->registerCallout("beta", callout_four);
- getCalloutManager()->callCallouts(beta_index_, getCalloutHandle());
- EXPECT_EQ(11223344, callout_value_);
-
- // A callout in a random position in the callout list returns an error.
- callout_value_ = 0;
- getCalloutManager()->setLibraryIndex(0);
- getCalloutManager()->registerCallout("gamma", callout_one);
- getCalloutManager()->registerCallout("gamma", callout_one);
- getCalloutManager()->setLibraryIndex(1);
- getCalloutManager()->registerCallout("gamma", callout_two);
- getCalloutManager()->registerCallout("gamma", callout_two);
- getCalloutManager()->setLibraryIndex(3);
- getCalloutManager()->registerCallout("gamma", callout_four_error);
- getCalloutManager()->registerCallout("gamma", callout_four);
- getCalloutManager()->callCallouts(gamma_index_, getCalloutHandle());
- EXPECT_EQ(112244, callout_value_);
-
- // The last callout on a hook returns an error.
- callout_value_ = 0;
- getCalloutManager()->setLibraryIndex(0);
- getCalloutManager()->registerCallout("delta", callout_one);
- getCalloutManager()->registerCallout("delta", callout_one);
- getCalloutManager()->setLibraryIndex(1);
- getCalloutManager()->registerCallout("delta", callout_two);
- getCalloutManager()->registerCallout("delta", callout_two);
- getCalloutManager()->setLibraryIndex(2);
- getCalloutManager()->registerCallout("delta", callout_three);
- getCalloutManager()->registerCallout("delta", callout_three);
- getCalloutManager()->setLibraryIndex(3);
- getCalloutManager()->registerCallout("delta", callout_four);
- getCalloutManager()->registerCallout("delta", callout_four_error);
- getCalloutManager()->callCallouts(delta_index_, getCalloutHandle());
- EXPECT_EQ(11223344, callout_value_);
-}
-
-// Now test that we can deregister a single callout on a hook.
-
-TEST_F(CalloutManagerTest, DeregisterSingleCallout) {
- // Ensure that no callouts are attached to any of the hooks.
- EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
- EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
- EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
- EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
-
- // Add a callout to hook "alpha" and check it is added correctly.
- callout_value_ = 0;
- getCalloutManager()->setLibraryIndex(0);
- getCalloutManager()->registerCallout("alpha", callout_two);
- getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
- EXPECT_EQ(2, callout_value_);
-
- // Remove it and check that the no callouts are present. We have to reset
- // the current library index here as it was invalidated by the call
- // to callCallouts().
- getCalloutManager()->setLibraryIndex(0);
- EXPECT_TRUE(getCalloutManager()->calloutsPresent(alpha_index_));
- EXPECT_TRUE(getCalloutManager()->deregisterCallout("alpha", callout_two));
- EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
-}
-
-// Now test that we can deregister a single callout on a hook that has multiple
-// callouts from the same library.
-
-TEST_F(CalloutManagerTest, DeregisterSingleCalloutSameLibrary) {
- // Ensure that no callouts are attached to any of the hooks.
- EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
- EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
- EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
- EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
-
- // Add multiple callouts to hook "alpha".
- callout_value_ = 0;
- getCalloutManager()->setLibraryIndex(0);
- getCalloutManager()->registerCallout("alpha", callout_one);
- getCalloutManager()->registerCallout("alpha", callout_two);
- getCalloutManager()->registerCallout("alpha", callout_three);
- getCalloutManager()->registerCallout("alpha", callout_four);
- getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
- EXPECT_EQ(1234, callout_value_);
-
- // Remove the callout_two callout. We have to reset the current library
- // index here as it was invalidated by the call to callCallouts().
- getCalloutManager()->setLibraryIndex(0);
- EXPECT_TRUE(getCalloutManager()->deregisterCallout("alpha", callout_two));
- callout_value_ = 0;
- getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
- EXPECT_EQ(134, callout_value_);
-
- // Try removing it again.
- getCalloutManager()->setLibraryIndex(0);
- EXPECT_FALSE(getCalloutManager()->deregisterCallout("alpha", callout_two));
- callout_value_ = 0;
- getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
- EXPECT_EQ(134, callout_value_);
-
-}
-
-// Check we can deregister multiple callouts from the same library.
-
-TEST_F(CalloutManagerTest, DeregisterMultipleCalloutsSameLibrary) {
- // Ensure that no callouts are attached to any of the hooks.
- EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
- EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
- EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
- EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
-
- // Each library contributes one callout on hook "alpha".
- callout_value_ = 0;
- getCalloutManager()->setLibraryIndex(0);
- getCalloutManager()->registerCallout("alpha", callout_one);
- getCalloutManager()->registerCallout("alpha", callout_two);
- getCalloutManager()->registerCallout("alpha", callout_one);
- getCalloutManager()->registerCallout("alpha", callout_two);
- getCalloutManager()->registerCallout("alpha", callout_three);
- getCalloutManager()->registerCallout("alpha", callout_four);
- getCalloutManager()->registerCallout("alpha", callout_three);
- getCalloutManager()->registerCallout("alpha", callout_four);
- getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
- EXPECT_EQ(12123434, callout_value_);
-
- // Remove the callout_two callouts. We have to reset the current library
- // index here as it was invalidated by the call to callCallouts().
- getCalloutManager()->setLibraryIndex(0);
- EXPECT_TRUE(getCalloutManager()->deregisterCallout("alpha", callout_two));
- callout_value_ = 0;
- getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
- EXPECT_EQ(113434, callout_value_);
-
- // Try removing multiple callouts that includes one at the end of the
- // list of callouts.
- getCalloutManager()->setLibraryIndex(0);
- EXPECT_TRUE(getCalloutManager()->deregisterCallout("alpha", callout_four));
- callout_value_ = 0;
- getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
- EXPECT_EQ(1133, callout_value_);
-
- // ... and from the start.
- getCalloutManager()->setLibraryIndex(0);
- EXPECT_TRUE(getCalloutManager()->deregisterCallout("alpha", callout_one));
- callout_value_ = 0;
- getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
- EXPECT_EQ(33, callout_value_);
-
- // ... and the remaining callouts.
- getCalloutManager()->setLibraryIndex(0);
- EXPECT_TRUE(getCalloutManager()->deregisterCallout("alpha", callout_three));
- callout_value_ = 0;
- getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
- EXPECT_EQ(0, callout_value_);
-
- EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
-}
-
-// Check we can deregister multiple callouts from multiple libraries.
-
-TEST_F(CalloutManagerTest, DeregisterMultipleCalloutsMultipleLibraries) {
- // Ensure that no callouts are attached to any of the hooks.
- EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
- EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
- EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
- EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
-
- // Each library contributes two callouts to hook "alpha".
- callout_value_ = 0;
- getCalloutManager()->setLibraryIndex(0);
- getCalloutManager()->registerCallout("alpha", callout_one);
- getCalloutManager()->registerCallout("alpha", callout_two);
- getCalloutManager()->setLibraryIndex(1);
- getCalloutManager()->registerCallout("alpha", callout_three);
- getCalloutManager()->registerCallout("alpha", callout_four);
- getCalloutManager()->setLibraryIndex(2);
- getCalloutManager()->registerCallout("alpha", callout_five);
- getCalloutManager()->registerCallout("alpha", callout_two);
- getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
- EXPECT_EQ(123452, callout_value_);
-
- // Remove the callout_two callout from library 0. It should not affect
- // the second callout_two callout registered by library 2.
- getCalloutManager()->setLibraryIndex(0);
- EXPECT_TRUE(getCalloutManager()->deregisterCallout("alpha", callout_two));
- callout_value_ = 0;
- getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
- EXPECT_EQ(13452, callout_value_);
-}
-
-// Check we can deregister all callouts from a single library.
-
-TEST_F(CalloutManagerTest, DeregisterAllCallouts) {
- // Ensure that no callouts are attached to hook one.
- EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
-
- // Each library contributes two callouts to hook "alpha".
- callout_value_ = 0;
- getCalloutManager()->setLibraryIndex(0);
- getCalloutManager()->registerCallout("alpha", callout_one);
- getCalloutManager()->registerCallout("alpha", callout_two);
- getCalloutManager()->setLibraryIndex(1);
- getCalloutManager()->registerCallout("alpha", callout_three);
- getCalloutManager()->registerCallout("alpha", callout_four);
- getCalloutManager()->setLibraryIndex(2);
- getCalloutManager()->registerCallout("alpha", callout_five);
- getCalloutManager()->registerCallout("alpha", callout_six);
- getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
- EXPECT_EQ(123456, callout_value_);
-
- // Remove all callouts from library index 1.
- getCalloutManager()->setLibraryIndex(1);
- EXPECT_TRUE(getCalloutManager()->deregisterAllCallouts("alpha"));
- callout_value_ = 0;
- getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
- EXPECT_EQ(1256, callout_value_);
-
- // Remove all callouts from library index 2.
- getCalloutManager()->setLibraryIndex(2);
- EXPECT_TRUE(getCalloutManager()->deregisterAllCallouts("alpha"));
- callout_value_ = 0;
- getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
- EXPECT_EQ(12, callout_value_);
-}
-
-// Check that we can register/deregister callouts on different libraries
-// and different hooks, and that the callout instances are regarded as
-// unique and do not affect one another.
-
-TEST_F(CalloutManagerTest, MultipleCalloutsLibrariesHooks) {
- // Ensure that no callouts are attached to any of the hooks.
- EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
- EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
- EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
- EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
-
- // Register callouts on the alpha hook.
- callout_value_ = 0;
- getCalloutManager()->setLibraryIndex(0);
- getCalloutManager()->registerCallout("alpha", callout_one);
- getCalloutManager()->registerCallout("alpha", callout_two);
- getCalloutManager()->setLibraryIndex(1);
- getCalloutManager()->registerCallout("alpha", callout_three);
- getCalloutManager()->registerCallout("alpha", callout_four);
- getCalloutManager()->setLibraryIndex(2);
- getCalloutManager()->registerCallout("alpha", callout_five);
- getCalloutManager()->registerCallout("alpha", callout_two);
- getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
- EXPECT_EQ(123452, callout_value_);
-
- // Register the same callouts on the beta hook, and check that those
- // on the alpha hook are not affected.
- callout_value_ = 0;
- getCalloutManager()->setLibraryIndex(0);
- getCalloutManager()->registerCallout("beta", callout_five);
- getCalloutManager()->registerCallout("beta", callout_one);
- getCalloutManager()->setLibraryIndex(2);
- getCalloutManager()->registerCallout("beta", callout_four);
- getCalloutManager()->registerCallout("beta", callout_three);
- getCalloutManager()->callCallouts(beta_index_, getCalloutHandle());
- EXPECT_EQ(5143, callout_value_);
-
- // Check that the order of callouts on the alpha hook has not been affected.
- callout_value_ = 0;
- getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
- EXPECT_EQ(123452, callout_value_);
-
- // Remove callout four from beta and check that alpha is not affected.
- getCalloutManager()->setLibraryIndex(2);
- EXPECT_TRUE(getCalloutManager()->deregisterCallout("beta", callout_four));
-
- callout_value_ = 0;
- getCalloutManager()->callCallouts(beta_index_, getCalloutHandle());
- EXPECT_EQ(513, callout_value_);
-
- callout_value_ = 0;
- getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
- EXPECT_EQ(123452, callout_value_);
-}
-
-// Library handle tests. As by inspection the LibraryHandle can be seen to be
-// little more than shell around CalloutManager, only a basic set of tests
-// is done concerning registration and deregistration of functions.
-//
-// More extensive tests (i.e. checking that when a callout is called it can
-// only register and deregister callouts within its library) require that
-// the CalloutHandle object pass the appropriate LibraryHandle to the
-// callout. These tests are done in the handles_unittest tests.
-
-TEST_F(CalloutManagerTest, LibraryHandleRegistration) {
- // Ensure that no callouts are attached to any of the hooks.
- EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
-
- // Set up so that hooks "alpha" and "beta" have callouts attached from a
- // different libraries.
- getCalloutManager()->setLibraryIndex(0);
- getCalloutManager()->getLibraryHandle().registerCallout("alpha",
- callout_one);
- getCalloutManager()->getLibraryHandle().registerCallout("alpha",
- callout_two);
- getCalloutManager()->setLibraryIndex(1);
- getCalloutManager()->getLibraryHandle().registerCallout("alpha",
- callout_three);
- getCalloutManager()->getLibraryHandle().registerCallout("alpha",
- callout_four);
-
- // Check all is as expected.
- EXPECT_TRUE(getCalloutManager()->calloutsPresent(alpha_index_));
- EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
- EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
- EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
-
- // Check that calling the callouts returns as expected. (This is also a
- // test of the callCallouts method.)
- callout_value_ = 0;
- getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
- EXPECT_EQ(1234, callout_value_);
-
- // Deregister a callout on library index 0 (after we check we can't
- // deregister it through library index 1).
- getCalloutManager()->setLibraryIndex(1);
- EXPECT_FALSE(getCalloutManager()->deregisterCallout("alpha", callout_two));
- callout_value_ = 0;
- getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
- EXPECT_EQ(1234, callout_value_);
-
- getCalloutManager()->setLibraryIndex(0);
- EXPECT_TRUE(getCalloutManager()->deregisterCallout("alpha", callout_two));
- callout_value_ = 0;
- getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
- EXPECT_EQ(134, callout_value_);
-
- // Deregister all callouts on library index 1.
- getCalloutManager()->setLibraryIndex(1);
- EXPECT_TRUE(getCalloutManager()->deregisterAllCallouts("alpha"));
- callout_value_ = 0;
- getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
- EXPECT_EQ(1, callout_value_);
-}
-
-
-
-} // Anonymous namespace
diff --git a/src/lib/util/tests/handles_unittest.cc b/src/lib/util/tests/handles_unittest.cc
deleted file mode 100644
index 5e5b237..0000000
--- a/src/lib/util/tests/handles_unittest.cc
+++ /dev/null
@@ -1,917 +0,0 @@
-// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#include <util/hooks/callout_handle.h>
-#include <util/hooks/callout_manager.h>
-#include <util/hooks/library_handle.h>
-#include <util/hooks/server_hooks.h>
-
-#include <boost/lexical_cast.hpp>
-#include <boost/scoped_ptr.hpp>
-#include <gtest/gtest.h>
-
-#include <algorithm>
-#include <string>
-
-/// @file
-/// CalloutHandle/LibraryHandle interaction tests
-///
-/// This file holds unit tests checking the interaction between the
-/// CalloutHandle/LibraryHandle and CalloutManager classes. In particular,
-/// they check that:
-///
-/// - A CalloutHandle's context is shared between callouts from the same
-/// library, but there is a separate context for each library.
-///
-/// - The various methods manipulating the items in the CalloutHandle's context
-/// work correctly.
-///
-/// - An active callout can only modify the registration of callouts registered
-/// by its own library.
-
-using namespace isc::util;
-using namespace std;
-
-namespace {
-
-class HandlesTest : public ::testing::Test {
-public:
- /// @brief Constructor
- ///
- /// Sets up the various elements used in each test.
- HandlesTest() : hooks_(new ServerHooks()), manager_()
- {
- // Set up four hooks, although through gamma
- alpha_index_ = hooks_->registerHook("alpha");
- beta_index_ = hooks_->registerHook("beta");
- gamma_index_ = hooks_->registerHook("gamma");
- delta_index_ = hooks_->registerHook("delta");
-
- // Set up for three libraries.
- manager_.reset(new CalloutManager(hooks_, 3));
-
- // Initialize remaining variables.
- common_string_ = "";
- }
-
- /// @brief Return callout manager
- boost::shared_ptr<CalloutManager> getCalloutManager() {
- return (manager_);
- }
-
- /// Hook indexes - these are frequently accessed, so are accessed directly.
- int alpha_index_;
- int beta_index_;
- int gamma_index_;
- int delta_index_;
-
- /// String accessible by all callouts whatever the library
- static std::string common_string_;
-
-private:
- /// Server hooks
- boost::shared_ptr<ServerHooks> hooks_;
-
- /// Callout manager. Declared static so that the callout functions can
- /// access it.
- boost::shared_ptr<CalloutManager> manager_;
-};
-
-/// Define the common string
-std::string HandlesTest::common_string_;
-
-
-// The next set of functions define the callouts used by the tests. They
-// manipulate the data in such a way that callouts called - and the order in
-// which they were called - can be determined. The functions also check that
-// the "callout context" data areas are separate.
-//
-// Three libraries are assumed, and each supplies four callouts. All callouts
-// manipulate two context elements the CalloutHandle, the elements being called
-// "string" and "int" (which describe the type of data manipulated).
-//
-// For the string item, each callout shifts data to the left and inserts its own
-// data. The data is a string of the form "nmc", where "n" is the number of
-// the library, "m" is the callout number and "y" is the indication of what
-// callout handle was passed as an argument ("1" or "2": "0" is used when no
-// identification has been set in the callout handle).
-//
-// For simplicity, and to cut down the number of functions actually written,
-// the callout indicator ("1" or "2") ) used in the in the CalloutHandle
-// functions is passed via a CalloutArgument. The argument is named "string":
-// use of a name the same as that of one of the context elements serves as a
-// check that the argument name space and argument context space are separate.
-//
-// For integer data, the value starts at zero and an increment is added on each
-// call. This increment is equal to:
-//
-// 100 * library number + 10 * callout number + callout handle
-//
-// Although this gives less information than the string value, the reasons for
-// using it are:
-//
-// - It is a separate item in the context, so checks that the context can
-// handle multiple items.
-// - It provides an item that can be deleted by the context deletion
-// methods.
-
-
-// Values set in the CalloutHandle context. There are three libraries, so
-// there are three contexts for the callout, one for each library.
-
-std::string& resultCalloutString(int index) {
- static std::string result_callout_string[3];
- return (result_callout_string[index]);
-}
-
-int& resultCalloutInt(int index) {
- static int result_callout_int[3];
- return (result_callout_int[index]);
-}
-
-// A simple function to zero the results.
-
-static void zero_results() {
- for (int i = 0; i < 3; ++i) {
- resultCalloutString(i) = "";
- resultCalloutInt(i) = 0;
- }
-}
-
-
-// Library callouts.
-
-// Common code for setting the callout context values.
-
-int
-execute(CalloutHandle& callout_handle, int library_num, int callout_num) {
-
- // Obtain the callout handle number
- int handle_num = 0;
- try {
- callout_handle.getArgument("handle_num", handle_num);
- } catch (const NoSuchArgument&) {
- // handle_num argument not set: this is the case in the tests where
- // the context_create hook check is tested.
- handle_num = 0;
- }
-
- // Create the basic data to be appended to the context value.
- int idata = 100 * library_num + 10 * callout_num + handle_num;
- string sdata = boost::lexical_cast<string>(idata);
-
- // Get the context data. As before, this will not exist for the first
- // callout called. (In real life, the library should create it when the
- // "context_create" hook gets called before any packet processing takes
- // place.)
- int int_value = 0;
- try {
- callout_handle.getContext("int", int_value);
- } catch (const NoSuchCalloutContext&) {
- int_value = 0;
- }
-
- string string_value = "";
- try {
- callout_handle.getContext("string", string_value);
- } catch (const NoSuchCalloutContext&) {
- string_value = "";
- }
-
- // Update the values and set them back in the callout context.
- int_value += idata;
- callout_handle.setContext("int", int_value);
-
- string_value += sdata;
- callout_handle.setContext("string", string_value);
-
- return (0);
-}
-
-// The following functions are the actual callouts - the name is of the
-// form "callout_<library number>_<callout number>"
-
-int
-callout11(CalloutHandle& callout_handle) {
- return (execute(callout_handle, 1, 1));
-}
-
-int
-callout12(CalloutHandle& callout_handle) {
- return (execute(callout_handle, 1, 2));
-}
-
-int
-callout13(CalloutHandle& callout_handle) {
- return (execute(callout_handle, 1, 3));
-}
-
-int
-callout21(CalloutHandle& callout_handle) {
- return (execute(callout_handle, 2, 1));
-}
-
-int
-callout22(CalloutHandle& callout_handle) {
- return (execute(callout_handle, 2, 2));
-}
-
-int
-callout23(CalloutHandle& callout_handle) {
- return (execute(callout_handle, 2, 3));
-}
-
-int
-callout31(CalloutHandle& callout_handle) {
- return (execute(callout_handle, 3, 1));
-}
-
-int
-callout32(CalloutHandle& callout_handle) {
- return (execute(callout_handle, 3, 2));
-}
-
-int
-callout33(CalloutHandle& callout_handle) {
- return (execute(callout_handle, 3, 3));
-}
-
-// Common callout code for the fourth hook (which makes the data available for
-// checking). It copies the library and callout context data to the global
-// variables.
-
-int printExecute(CalloutHandle& callout_handle, int library_num) {
- callout_handle.getContext("string", resultCalloutString(library_num - 1));
- callout_handle.getContext("int", resultCalloutInt(library_num - 1));
-
- return (0);
-}
-
-// These are the actual callouts.
-
-int
-print1(CalloutHandle& callout_handle) {
- return (printExecute(callout_handle, 1));
-}
-
-int
-print2(CalloutHandle& callout_handle) {
- return (printExecute(callout_handle, 2));
-}
-
-int
-print3(CalloutHandle& callout_handle) {
- return (printExecute(callout_handle, 3));
-}
-
-// This test checks the many-faced nature of the context for the CalloutContext.
-
-TEST_F(HandlesTest, ContextAccessCheck) {
- // Register callouts for the different libraries.
- CalloutHandle handle(getCalloutManager());
-
- // Library 0.
- getCalloutManager()->setLibraryIndex(0);
- getCalloutManager()->registerCallout("alpha", callout11);
- getCalloutManager()->registerCallout("beta", callout12);
- getCalloutManager()->registerCallout("gamma", callout13);
- getCalloutManager()->registerCallout("delta", print1);
-
- getCalloutManager()->setLibraryIndex(1);
- getCalloutManager()->registerCallout("alpha", callout21);
- getCalloutManager()->registerCallout("beta", callout22);
- getCalloutManager()->registerCallout("gamma", callout23);
- getCalloutManager()->registerCallout("delta", print2);
-
- getCalloutManager()->setLibraryIndex(2);
- getCalloutManager()->registerCallout("alpha", callout31);
- getCalloutManager()->registerCallout("beta", callout32);
- getCalloutManager()->registerCallout("gamma", callout33);
- getCalloutManager()->registerCallout("delta", print3);
-
- // Create the callout handles and distinguish them by setting the
- // "handle_num" argument.
- CalloutHandle callout_handle_1(getCalloutManager());
- callout_handle_1.setArgument("handle_num", static_cast<int>(1));
-
- CalloutHandle callout_handle_2(getCalloutManager());
- callout_handle_2.setArgument("handle_num", static_cast<int>(2));
-
- // Now call the callouts attached to the first three hooks. Each hook is
- // called twice (once for each callout handle) before the next hook is
- // called.
- getCalloutManager()->callCallouts(alpha_index_, callout_handle_1);
- getCalloutManager()->callCallouts(alpha_index_, callout_handle_2);
- getCalloutManager()->callCallouts(beta_index_, callout_handle_1);
- getCalloutManager()->callCallouts(beta_index_, callout_handle_2);
- getCalloutManager()->callCallouts(gamma_index_, callout_handle_1);
- getCalloutManager()->callCallouts(gamma_index_, callout_handle_2);
-
- // Get the results for each callout (the callout on hook "delta" copies
- // the context values into a location the test can access). Explicitly
- // zero the variables before getting the results so we are certain that
- // the values are the results of the callouts.
-
- zero_results();
-
- // To explain the expected callout context results.
- //
- // Each callout handle maintains a separate context for each library. When
- // the first call to callCallouts() is made, "111" gets appended to
- // the context for library 1 maintained by the first callout handle, "211"
- // gets appended to the context maintained for library 2, and "311" to
- // the context maintained for library 3. In each case, the first digit
- // corresponds to the library number, the second to the callout number and
- // the third to the "handle_num" of the callout handle. For the first call
- // to callCallouts, handle 1 is used, so the last digit is always 1.
- //
- // The next call to callCallouts() calls the same callouts but for the
- // second callout handle. It also maintains three contexts (one for
- // each library) and they will get "112", "212", "312" appended to
- // them. The explanation for the digits is the same as before, except that
- // in this case, the callout handle is number 2, so the third digit is
- // always 2. These additions don't affect the contexts maintained by
- // callout handle 1.
- //
- // The process is then repeated for hooks "beta" and "gamma" which, for
- // callout handle 1, append "121", "221" and "321" for hook "beta" and
- // "311", "321" and "331" for hook "gamma".
- //
- // The expected integer values can be found by summing up the values
- // corresponding to the elements of the strings.
-
- // At this point, we have only called the "print" function for callout
- // handle "1", so the following results are checking the context values
- // maintained in that callout handle.
-
- getCalloutManager()->callCallouts(delta_index_, callout_handle_1);
- EXPECT_EQ("111121131", resultCalloutString(0));
- EXPECT_EQ("211221231", resultCalloutString(1));
- EXPECT_EQ("311321331", resultCalloutString(2));
-
- EXPECT_EQ((111 + 121 + 131), resultCalloutInt(0));
- EXPECT_EQ((211 + 221 + 231), resultCalloutInt(1));
- EXPECT_EQ((311 + 321 + 331), resultCalloutInt(2));
-
- // Repeat the checks for callout 2.
-
- zero_results();
- getCalloutManager()->callCallouts(delta_index_, callout_handle_2);
-
- EXPECT_EQ((112 + 122 + 132), resultCalloutInt(0));
- EXPECT_EQ((212 + 222 + 232), resultCalloutInt(1));
- EXPECT_EQ((312 + 322 + 332), resultCalloutInt(2));
-
- EXPECT_EQ("112122132", resultCalloutString(0));
- EXPECT_EQ("212222232", resultCalloutString(1));
- EXPECT_EQ("312322332", resultCalloutString(2));
-}
-
-// Now repeat the test, but add a deletion callout to the list. The "beta"
-// hook of library 2 will have an additional callout to delete the "int"
-// element: the same hook for library 3 will delete both elements. In
-// addition, the names of context elements for the libraries at this point
-// will be printed.
-
-// List of context item names.
-
-vector<string>&
-getItemNames(int index) {
- static vector<string> context_items[3];
- return (context_items[index]);
-}
-
-// Context item deletion functions.
-
-int
-deleteIntContextItem(CalloutHandle& handle) {
- handle.deleteContext("int");
- return (0);
-}
-
-int
-deleteAllContextItems(CalloutHandle& handle) {
- handle.deleteAllContext();
- return (0);
-}
-
-// Generic print function - copy names in sorted order.
-
-int
-printContextNamesExecute(CalloutHandle& handle, int library_num) {
- const int index = library_num - 1;
- getItemNames(index) = handle.getContextNames();
- sort(getItemNames(index).begin(), getItemNames(index).end());
- return (0);
-}
-
-int
-printContextNames1(CalloutHandle& handle) {
- return (printContextNamesExecute(handle, 1));
-}
-
-int
-printContextNames2(CalloutHandle& handle) {
- return (printContextNamesExecute(handle, 2));
-}
-
-int
-printContextNames3(CalloutHandle& handle) {
- return (printContextNamesExecute(handle, 3));
-}
-
-// Perform the test including deletion of context items.
-
-TEST_F(HandlesTest, ContextDeletionCheck) {
-
- getCalloutManager()->setLibraryIndex(0);
- getCalloutManager()->registerCallout("alpha", callout11);
- getCalloutManager()->registerCallout("beta", callout12);
- getCalloutManager()->registerCallout("beta", printContextNames1);
- getCalloutManager()->registerCallout("gamma", callout13);
- getCalloutManager()->registerCallout("delta", print1);
-
- getCalloutManager()->setLibraryIndex(1);
- getCalloutManager()->registerCallout("alpha", callout21);
- getCalloutManager()->registerCallout("beta", callout22);
- getCalloutManager()->registerCallout("beta", deleteIntContextItem);
- getCalloutManager()->registerCallout("beta", printContextNames2);
- getCalloutManager()->registerCallout("gamma", callout23);
- getCalloutManager()->registerCallout("delta", print2);
-
- getCalloutManager()->setLibraryIndex(2);
- getCalloutManager()->registerCallout("alpha", callout31);
- getCalloutManager()->registerCallout("beta", callout32);
- getCalloutManager()->registerCallout("beta", deleteAllContextItems);
- getCalloutManager()->registerCallout("beta", printContextNames3);
- getCalloutManager()->registerCallout("gamma", callout33);
- getCalloutManager()->registerCallout("delta", print3);
-
- // Create the callout handles and distinguish them by setting the "long"
- // argument.
- CalloutHandle callout_handle_1(getCalloutManager());
- callout_handle_1.setArgument("handle_num", static_cast<int>(1));
-
- CalloutHandle callout_handle_2(getCalloutManager());
- callout_handle_2.setArgument("handle_num", static_cast<int>(2));
-
- // Now call the callouts attached to the first three hooks. Each hook is
- // called twice (once for each callout handle) before the next hook is
- // called.
- getCalloutManager()->callCallouts(alpha_index_, callout_handle_1);
- getCalloutManager()->callCallouts(alpha_index_, callout_handle_2);
- getCalloutManager()->callCallouts(beta_index_, callout_handle_1);
- getCalloutManager()->callCallouts(beta_index_, callout_handle_2);
- getCalloutManager()->callCallouts(gamma_index_, callout_handle_1);
- getCalloutManager()->callCallouts(gamma_index_, callout_handle_2);
-
- // Get the results for each callout. Explicitly zero the variables before
- // getting the results so we are certain that the values are the results
- // of the callouts.
-
- zero_results();
- getCalloutManager()->callCallouts(delta_index_, callout_handle_1);
-
- // The logic by which the expected results are arrived at is described
- // in the ContextAccessCheck test. The results here are different
- // because context items have been modified along the way.
-
- EXPECT_EQ((111 + 121 + 131), resultCalloutInt(0));
- EXPECT_EQ(( 231), resultCalloutInt(1));
- EXPECT_EQ(( 331), resultCalloutInt(2));
-
- EXPECT_EQ("111121131", resultCalloutString(0));
- EXPECT_EQ("211221231", resultCalloutString(1));
- EXPECT_EQ( "331", resultCalloutString(2));
-
- // Repeat the checks for callout handle 2.
-
- zero_results();
- getCalloutManager()->callCallouts(delta_index_, callout_handle_2);
-
- EXPECT_EQ((112 + 122 + 132), resultCalloutInt(0));
- EXPECT_EQ(( 232), resultCalloutInt(1));
- EXPECT_EQ(( 332), resultCalloutInt(2));
-
- EXPECT_EQ("112122132", resultCalloutString(0));
- EXPECT_EQ("212222232", resultCalloutString(1));
- EXPECT_EQ( "332", resultCalloutString(2));
-
- // ... and check what the names of the context items are after the callouts
- // for hook "beta". We know they are in sorted order.
-
- EXPECT_EQ(2, getItemNames(0).size());
- EXPECT_EQ(string("int"), getItemNames(0)[0]);
- EXPECT_EQ(string("string"), getItemNames(0)[1]);
-
- EXPECT_EQ(1, getItemNames(1).size());
- EXPECT_EQ(string("string"), getItemNames(1)[0]);
-
- EXPECT_EQ(0, getItemNames(2).size());
-}
-
-// Tests that the CalloutHandle's constructor and destructor call the
-// context_create and context_destroy callbacks (if registered). For
-// simplicity, we'll use the same callout functions as used above.
-
-TEST_F(HandlesTest, ConstructionDestructionCallouts) {
-
- // Register context callouts.
- getCalloutManager()->setLibraryIndex(0);
- getCalloutManager()->registerCallout("context_create", callout11);
- getCalloutManager()->registerCallout("context_create", print1);
- getCalloutManager()->registerCallout("context_destroy", callout12);
- getCalloutManager()->registerCallout("context_destroy", print1);
-
- // Create the CalloutHandle and check that the constructor callout
- // has run.
- zero_results();
- boost::scoped_ptr<CalloutHandle>
- callout_handle(new CalloutHandle(getCalloutManager()));
- EXPECT_EQ("110", resultCalloutString(0));
- EXPECT_EQ(110, resultCalloutInt(0));
-
- // Check that the destructor callout runs. Note that the "print1" callout
- // didn't destroy the library context - it only copied it to where it
- // could be examined. As a result, the destructor callout appends its
- // elements to the constructor's values and the result is printed.
- zero_results();
- callout_handle.reset();
-
- EXPECT_EQ("110120", resultCalloutString(0));
- EXPECT_EQ((110 + 120), resultCalloutInt(0));
-}
-
-// Dynamic callout registration and deregistration.
-// The following are the dynamic registration/deregistration callouts.
-
-
-// Add callout_78_alpha - adds a callout to hook alpha that appends "78x"
-// (where "x" is the callout handle) to the current output.
-
-int
-callout78(CalloutHandle& callout_handle) {
- return (execute(callout_handle, 7, 8));
-}
-
-int
-add_callout78_alpha(CalloutHandle& callout_handle) {
- callout_handle.getLibraryHandle().registerCallout("alpha", callout78);
- return (0);
-}
-
-int
-delete_callout78_alpha(CalloutHandle& callout_handle) {
- static_cast<void>(
- callout_handle.getLibraryHandle().deregisterCallout("alpha",
- callout78));
- return (0);
-}
-
-// Check that a callout can register another callout on a different hook.
-
-TEST_F(HandlesTest, DynamicRegistrationAnotherHook) {
- // Register callouts for the different libraries.
- CalloutHandle handle(getCalloutManager());
-
- // Set up callouts on "alpha".
- getCalloutManager()->setLibraryIndex(0);
- getCalloutManager()->registerCallout("alpha", callout11);
- getCalloutManager()->registerCallout("delta", print1);
- getCalloutManager()->setLibraryIndex(1);
- getCalloutManager()->registerCallout("alpha", callout21);
- getCalloutManager()->registerCallout("delta", print2);
- getCalloutManager()->setLibraryIndex(2);
- getCalloutManager()->registerCallout("alpha", callout31);
- getCalloutManager()->registerCallout("delta", print3);
-
- // ... and on "beta", set up the function to add a hook to alpha (but only
- // for library 1).
- getCalloutManager()->setLibraryIndex(1);
- getCalloutManager()->registerCallout("beta", add_callout78_alpha);
-
- // See what we get for calling the callouts on alpha first.
- CalloutHandle callout_handle_1(getCalloutManager());
- callout_handle_1.setArgument("handle_num", static_cast<int>(1));
- getCalloutManager()->callCallouts(alpha_index_, callout_handle_1);
-
- zero_results();
- getCalloutManager()->callCallouts(delta_index_, callout_handle_1);
- EXPECT_EQ("111", resultCalloutString(0));
- EXPECT_EQ("211", resultCalloutString(1));
- EXPECT_EQ("311", resultCalloutString(2));
-
- // All as expected, now call the callouts on beta. This should add a
- // callout to the list of callouts for alpha, which we should see when
- // we run the test again.
- getCalloutManager()->callCallouts(beta_index_, callout_handle_1);
-
- // Use a new callout handle so as to get fresh callout context.
- CalloutHandle callout_handle_2(getCalloutManager());
- callout_handle_2.setArgument("handle_num", static_cast<int>(2));
- getCalloutManager()->callCallouts(alpha_index_, callout_handle_2);
-
- zero_results();
- getCalloutManager()->callCallouts(delta_index_, callout_handle_2);
- EXPECT_EQ("112", resultCalloutString(0));
- EXPECT_EQ("212782", resultCalloutString(1));
- EXPECT_EQ("312", resultCalloutString(2));
-}
-
-// Check that a callout can register another callout on the same hook.
-// Note that the registration only applies to a subsequent invocation of
-// callCallouts, not to the current one. In other words, if
-//
-// * the callout list for a library is "A then B then C"
-// * when callCallouts is executed "B" adds "D" to that list,
-//
-// ... the current execution of callCallouts only executes A, B and C. A
-// subsequent invocation will execute A, B, C then D.
-
-TEST_F(HandlesTest, DynamicRegistrationSameHook) {
- // Register callouts for the different libraries.
- CalloutHandle handle(getCalloutManager());
-
- // Set up callouts on "alpha".
- getCalloutManager()->setLibraryIndex(0);
- getCalloutManager()->registerCallout("alpha", callout11);
- getCalloutManager()->registerCallout("alpha", add_callout78_alpha);
- getCalloutManager()->registerCallout("delta", print1);
-
- // See what we get for calling the callouts on alpha first.
- CalloutHandle callout_handle_1(getCalloutManager());
- callout_handle_1.setArgument("handle_num", static_cast<int>(1));
- getCalloutManager()->callCallouts(alpha_index_, callout_handle_1);
- zero_results();
- getCalloutManager()->callCallouts(delta_index_, callout_handle_1);
- EXPECT_EQ("111", resultCalloutString(0));
-
- // Run it again - we should have added something to this hook.
- CalloutHandle callout_handle_2(getCalloutManager());
- callout_handle_2.setArgument("handle_num", static_cast<int>(2));
- getCalloutManager()->callCallouts(alpha_index_, callout_handle_2);
- zero_results();
- getCalloutManager()->callCallouts(delta_index_, callout_handle_2);
- EXPECT_EQ("112782", resultCalloutString(0));
-
- // And a third time...
- CalloutHandle callout_handle_3(getCalloutManager());
- callout_handle_3.setArgument("handle_num", static_cast<int>(3));
- getCalloutManager()->callCallouts(alpha_index_, callout_handle_3);
- zero_results();
- getCalloutManager()->callCallouts(delta_index_, callout_handle_3);
- EXPECT_EQ("113783783", resultCalloutString(0));
-}
-
-// Deregistration of a callout from a different hook
-
-TEST_F(HandlesTest, DynamicDeregistrationDifferentHook) {
- // Register callouts for the different libraries.
- CalloutHandle handle(getCalloutManager());
-
- // Set up callouts on "alpha".
- getCalloutManager()->setLibraryIndex(0);
- getCalloutManager()->registerCallout("alpha", callout11);
- getCalloutManager()->registerCallout("alpha", callout78);
- getCalloutManager()->registerCallout("alpha", callout11);
- getCalloutManager()->registerCallout("delta", print1);
-
- getCalloutManager()->registerCallout("beta", delete_callout78_alpha);
-
- // Call the callouts on alpha
- CalloutHandle callout_handle_1(getCalloutManager());
- callout_handle_1.setArgument("handle_num", static_cast<int>(1));
- getCalloutManager()->callCallouts(alpha_index_, callout_handle_1);
-
- zero_results();
- getCalloutManager()->callCallouts(delta_index_, callout_handle_1);
- EXPECT_EQ("111781111", resultCalloutString(0));
-
- // Run the callouts on hook beta to remove the callout on alpha.
- getCalloutManager()->callCallouts(beta_index_, callout_handle_1);
-
- // The run of the callouts should have altered the callout list on the
- // first library for hook alpha, so call again to make sure.
- CalloutHandle callout_handle_2(getCalloutManager());
- callout_handle_2.setArgument("handle_num", static_cast<int>(2));
- getCalloutManager()->callCallouts(alpha_index_, callout_handle_2);
-
- zero_results();
- getCalloutManager()->callCallouts(delta_index_, callout_handle_2);
- EXPECT_EQ("112112", resultCalloutString(0));
-}
-
-// Deregistration of a callout from the same hook
-
-TEST_F(HandlesTest, DynamicDeregistrationSameHook) {
- // Register callouts for the different libraries.
- CalloutHandle handle(getCalloutManager());
-
- // Set up callouts on "alpha".
- getCalloutManager()->setLibraryIndex(0);
- getCalloutManager()->registerCallout("alpha", callout11);
- getCalloutManager()->registerCallout("alpha", delete_callout78_alpha);
- getCalloutManager()->registerCallout("alpha", callout78);
- getCalloutManager()->registerCallout("delta", print1);
- getCalloutManager()->setLibraryIndex(1);
- getCalloutManager()->registerCallout("alpha", callout21);
- getCalloutManager()->registerCallout("alpha", callout78);
- getCalloutManager()->registerCallout("delta", print2);
-
- // Call the callouts on alpha
- CalloutHandle callout_handle_1(getCalloutManager());
- callout_handle_1.setArgument("handle_num", static_cast<int>(1));
- getCalloutManager()->callCallouts(alpha_index_, callout_handle_1);
-
- zero_results();
- getCalloutManager()->callCallouts(delta_index_, callout_handle_1);
- EXPECT_EQ("111781", resultCalloutString(0));
- EXPECT_EQ("211781", resultCalloutString(1));
-
- // The run of the callouts should have altered the callout list on the
- // first library for hook alpha, so call again to make sure.
- CalloutHandle callout_handle_2(getCalloutManager());
- callout_handle_2.setArgument("handle_num", static_cast<int>(2));
- getCalloutManager()->callCallouts(alpha_index_, callout_handle_2);
-
- zero_results();
- getCalloutManager()->callCallouts(delta_index_, callout_handle_2);
- EXPECT_EQ("112", resultCalloutString(0));
- EXPECT_EQ("212782", resultCalloutString(1));
-}
-
-// Testing the operation of the "skip" flag. Callouts print the value
-// they see in the flag and either leave it unchanged, set it or clear it.
-
-int
-calloutPrintSkip(CalloutHandle& handle) {
- static const std::string YES("Y");
- static const std::string NO("N");
-
- HandlesTest::common_string_ = HandlesTest::common_string_ +
- (handle.getSkip() ? YES : NO);
- return (0);
-}
-
-int
-calloutSetSkip(CalloutHandle& handle) {
- static_cast<void>(calloutPrintSkip(handle));
- handle.setSkip(true);
- return (0);
-}
-
-int
-calloutClearSkip(CalloutHandle& handle) {
- static_cast<void>(calloutPrintSkip(handle));
- handle.setSkip(false);
- return (0);
-}
-
-// Do a series of tests, returning with the skip flag set "true".
-
-TEST_F(HandlesTest, ReturnSkipSet) {
- getCalloutManager()->setLibraryIndex(0);
- getCalloutManager()->registerCallout("alpha", calloutPrintSkip);
- getCalloutManager()->registerCallout("alpha", calloutSetSkip);
- getCalloutManager()->registerCallout("alpha", calloutSetSkip);
- getCalloutManager()->registerCallout("alpha", calloutClearSkip);
-
- getCalloutManager()->setLibraryIndex(1);
- getCalloutManager()->registerCallout("alpha", calloutPrintSkip);
- getCalloutManager()->registerCallout("alpha", calloutSetSkip);
- getCalloutManager()->registerCallout("alpha", calloutSetSkip);
- getCalloutManager()->registerCallout("alpha", calloutClearSkip);
- getCalloutManager()->registerCallout("alpha", calloutClearSkip);
-
- getCalloutManager()->setLibraryIndex(2);
- getCalloutManager()->registerCallout("alpha", calloutPrintSkip);
- getCalloutManager()->registerCallout("alpha", calloutSetSkip);
- getCalloutManager()->registerCallout("alpha", calloutClearSkip);
- getCalloutManager()->registerCallout("alpha", calloutSetSkip);
-
- CalloutHandle callout_handle(getCalloutManager());
- getCalloutManager()->callCallouts(alpha_index_, callout_handle);
-
- // Check result. For each of visual checking, the expected string is
- // divided into sections corresponding to the blocks of callouts above.
- EXPECT_EQ(std::string("NNYY" "NNYYN" "NNYN"), common_string_);
-
- // ... and check that the skip flag on exit from callCallouts is set.
- EXPECT_TRUE(callout_handle.getSkip());
-}
-
-// Repeat the test, returning with the skip flag clear.
-TEST_F(HandlesTest, ReturnSkipClear) {
- getCalloutManager()->setLibraryIndex(0);
- getCalloutManager()->registerCallout("alpha", calloutSetSkip);
- getCalloutManager()->registerCallout("alpha", calloutSetSkip);
- getCalloutManager()->registerCallout("alpha", calloutClearSkip);
-
- getCalloutManager()->setLibraryIndex(1);
- getCalloutManager()->registerCallout("alpha", calloutPrintSkip);
- getCalloutManager()->registerCallout("alpha", calloutSetSkip);
- getCalloutManager()->registerCallout("alpha", calloutClearSkip);
- getCalloutManager()->registerCallout("alpha", calloutSetSkip);
- getCalloutManager()->registerCallout("alpha", calloutClearSkip);
- getCalloutManager()->registerCallout("alpha", calloutClearSkip);
-
- getCalloutManager()->setLibraryIndex(2);
- getCalloutManager()->registerCallout("alpha", calloutClearSkip);
- getCalloutManager()->registerCallout("alpha", calloutPrintSkip);
- getCalloutManager()->registerCallout("alpha", calloutSetSkip);
- getCalloutManager()->registerCallout("alpha", calloutClearSkip);
-
- CalloutHandle callout_handle(getCalloutManager());
- getCalloutManager()->callCallouts(alpha_index_, callout_handle);
-
- // Check result. For each of visual checking, the expected string is
- // divided into sections corresponding to the blocks of callouts above.
- EXPECT_EQ(std::string("NYY" "NNYNYN" "NNNY"), common_string_);
-
- // ... and check that the skip flag on exit from callCallouts is set.
- EXPECT_FALSE(callout_handle.getSkip());
-}
-
-// The next set of callouts do a similar thing to the above "skip" tests,
-// but alter the value of a string argument. This is for testing that the
-// a callout is able to change an argument and return it to the caller.
-
-const char* MODIFIED_ARG = "modified_arg";
-
-int
-calloutSetArgumentCommon(CalloutHandle& handle, const char* what) {
- std::string modified_arg = "";
-
- handle.getArgument(MODIFIED_ARG, modified_arg);
- modified_arg = modified_arg + std::string(what);
- handle.setArgument(MODIFIED_ARG, modified_arg);
- return (0);
-}
-
-int
-calloutSetArgumentYes(CalloutHandle& handle) {
- return (calloutSetArgumentCommon(handle, "Y"));
-}
-
-int
-calloutSetArgumentNo(CalloutHandle& handle) {
- return (calloutSetArgumentCommon(handle, "N"));
-}
-
-// ... and a callout to just copy the argument to the "common_string_" variable
-// but otherwise not alter it.
-
-int
-calloutPrintArgument(CalloutHandle& handle) {
- handle.getArgument(MODIFIED_ARG, HandlesTest::common_string_);
- return (0);
-}
-
-TEST_F(HandlesTest, CheckModifiedArgument) {
- getCalloutManager()->setLibraryIndex(0);
- getCalloutManager()->registerCallout("alpha", calloutSetArgumentYes);
- getCalloutManager()->registerCallout("alpha", calloutSetArgumentNo);
- getCalloutManager()->registerCallout("alpha", calloutSetArgumentNo);
-
- getCalloutManager()->setLibraryIndex(1);
- getCalloutManager()->registerCallout("alpha", calloutSetArgumentYes);
- getCalloutManager()->registerCallout("alpha", calloutSetArgumentYes);
- getCalloutManager()->registerCallout("alpha", calloutPrintArgument);
- getCalloutManager()->registerCallout("alpha", calloutSetArgumentNo);
- getCalloutManager()->registerCallout("alpha", calloutSetArgumentNo);
-
- getCalloutManager()->setLibraryIndex(2);
- getCalloutManager()->registerCallout("alpha", calloutSetArgumentYes);
- getCalloutManager()->registerCallout("alpha", calloutSetArgumentNo);
- getCalloutManager()->registerCallout("alpha", calloutSetArgumentYes);
-
- // Create the argument with an initial empty string value. Then call the
- // sequence of callouts above.
- CalloutHandle callout_handle(getCalloutManager());
- std::string modified_arg = "";
- callout_handle.setArgument(MODIFIED_ARG, modified_arg);
- getCalloutManager()->callCallouts(alpha_index_, callout_handle);
-
- // Check the intermediate and results. For visual checking, the expected
- // string is divided into sections corresponding to the blocks of callouts
- // above.
- EXPECT_EQ(std::string("YNN" "YY"), common_string_);
-
- callout_handle.getArgument(MODIFIED_ARG, modified_arg);
- EXPECT_EQ(std::string("YNN" "YYNN" "YNY"), modified_arg);
-}
-
-
-} // Anonymous namespace
-
diff --git a/src/lib/util/tests/server_hooks_unittest.cc b/src/lib/util/tests/server_hooks_unittest.cc
deleted file mode 100644
index f029ad2..0000000
--- a/src/lib/util/tests/server_hooks_unittest.cc
+++ /dev/null
@@ -1,242 +0,0 @@
-// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#include <util/hooks/server_hooks.h>
-
-#include <gtest/gtest.h>
-
-#include <algorithm>
-#include <string>
-#include <vector>
-
-using namespace isc;
-using namespace isc::util;
-using namespace std;
-
-namespace {
-
-// Checks the registration of hooks and the interrogation methods. As the
-// constructor registers two hooks, this is also a test of the constructor.
-
-TEST(ServerHooksTest, RegisterHooks) {
- ServerHooks hooks;
-
- // There should be two hooks already registered, with indexes 0 and 1.
- EXPECT_EQ(2, hooks.getCount());
- EXPECT_EQ(0, hooks.getIndex("context_create"));
- EXPECT_EQ(1, hooks.getIndex("context_destroy"));
-
- // Check that the constants are as expected. (The intermediate variables
- // are used because of problems with g++ 4.6.1/Ubuntu 11.10 when resolving
- // the value of the ServerHooks constants when they appeared within the
- // gtest macro.)
- const int create_value = ServerHooks::CONTEXT_CREATE;
- const int destroy_value = ServerHooks::CONTEXT_DESTROY;
- EXPECT_EQ(0, create_value);
- EXPECT_EQ(1, destroy_value);
-
- // Register another couple of hooks. The test on returned index is based
- // on knowledge that the hook indexes are assigned in ascending order.
- int alpha = hooks.registerHook("alpha");
- EXPECT_EQ(2, alpha);
- EXPECT_EQ(2, hooks.getIndex("alpha"));
-
- int beta = hooks.registerHook("beta");
- EXPECT_EQ(3, beta);
- EXPECT_EQ(3, hooks.getIndex("beta"));
-
- // Should be four hooks now
- EXPECT_EQ(4, hooks.getCount());
-}
-
-// Check that duplicate names cannot be registered.
-
-TEST(ServerHooksTest, DuplicateHooks) {
- ServerHooks hooks;
-
- // Ensure we can't duplicate one of the existing names.
- EXPECT_THROW(hooks.registerHook("context_create"), DuplicateHook);
-
- // Check we can't duplicate a newly registered hook.
- int gamma = hooks.registerHook("gamma");
- EXPECT_EQ(2, gamma);
- EXPECT_THROW(hooks.registerHook("gamma"), DuplicateHook);
-}
-
-// Checks that we can get the name of the hooks.
-
-TEST(ServerHooksTest, GetHookNames) {
- vector<string> expected_names;
- ServerHooks hooks;
-
- // Add names into the hooks object and to the set of expected names.
- expected_names.push_back("alpha");
- expected_names.push_back("beta");
- expected_names.push_back("gamma");
- expected_names.push_back("delta");
- for (int i = 0; i < expected_names.size(); ++i) {
- hooks.registerHook(expected_names[i].c_str());
- };
-
- // Update the expected names to include the pre-defined hook names.
- expected_names.push_back("context_create");
- expected_names.push_back("context_destroy");
-
- // Get the actual hook names
- vector<string> actual_names = hooks.getHookNames();
-
- // For comparison, sort the names into alphabetical order and do a straight
- // vector comparison.
- sort(expected_names.begin(), expected_names.end());
- sort(actual_names.begin(), actual_names.end());
-
- EXPECT_TRUE(expected_names == actual_names);
-}
-
-// Check that getting an unknown name throws an exception.
-
-TEST(ServerHooksTest, UnknownHookName) {
- ServerHooks hooks;
-
- EXPECT_THROW(static_cast<void>(hooks.getIndex("unknown")), NoSuchHook);
-}
-
-// Check that the count of hooks is correct.
-
-TEST(ServerHooksTest, HookCount) {
- ServerHooks hooks;
-
- // Insert the names into the hooks object
- hooks.registerHook("alpha");
- hooks.registerHook("beta");
- hooks.registerHook("gamma");
- hooks.registerHook("delta");
-
- // Should be two more hooks that the number we have registered.
- EXPECT_EQ(6, hooks.getCount());
-}
-
-// HookRegistrationFunction tests
-
-// Declare some hook registration functions.
-
-int alpha = 0;
-int beta = 0;
-int gamma = 0;
-int delta = 0;
-
-void registerAlphaBeta(ServerHooks& hooks) {
- alpha = hooks.registerHook("alpha");
- beta = hooks.registerHook("beta");
-}
-
-void registerGammaDelta(ServerHooks& hooks) {
- gamma = hooks.registerHook("gamma");
- delta = hooks.registerHook("delta");
-}
-
-// Add them to the registration vector. This addition should happen before
-// any tests are run, so we should start off with two functions in the
-// registration vector.
-
-HookRegistrationFunction f1(registerAlphaBeta);
-HookRegistrationFunction f2(registerGammaDelta);
-
-// This is not registered statically: it is used in the latter part of the
-// test.
-
-int epsilon = 0;
-void registerEpsilon(ServerHooks& hooks) {
- epsilon = hooks.registerHook("epsilon");
-}
-
-// Test that the registration functions were defined and can be executed.
-
-TEST(HookRegistrationFunction, Registration) {
-
- // The first part of the tests checks the static registration. As there
- // is only one list of registration functions, we have to do this first
- // as the static registration is done outside our control, before the
- // tests are loaded.
-
- // Ensure that the hook numbers are initialized.
- EXPECT_EQ(0, alpha);
- EXPECT_EQ(0, beta);
- EXPECT_EQ(0, gamma);
- EXPECT_EQ(0, delta);
-
- // Should have two hook registration functions registered.
- EXPECT_EQ(2, HookRegistrationFunction::getFunctionVector().size());
-
- // Execute the functions and check that four new hooks were defined (two
- // from each function).
- ServerHooks hooks;
- EXPECT_EQ(2, hooks.getCount());
- HookRegistrationFunction::execute(hooks);
- EXPECT_EQ(6, hooks.getCount());
-
- // Check the hook names are as expected.
- vector<string> names = hooks.getHookNames();
- ASSERT_EQ(6, names.size());
- sort(names.begin(), names.end());
- EXPECT_EQ(string("alpha"), names[0]);
- EXPECT_EQ(string("beta"), names[1]);
- EXPECT_EQ(string("context_create"), names[2]);
- EXPECT_EQ(string("context_destroy"), names[3]);
- EXPECT_EQ(string("delta"), names[4]);
- EXPECT_EQ(string("gamma"), names[5]);
-
- // Check that numbers in the range 2-5 inclusive were assigned as the
- // hook indexes (0 and 1 being reserved for context_create and
- // context_destroy).
- vector<int> indexes;
- indexes.push_back(alpha);
- indexes.push_back(beta);
- indexes.push_back(gamma);
- indexes.push_back(delta);
- sort(indexes.begin(), indexes.end());
- EXPECT_EQ(2, indexes[0]);
- EXPECT_EQ(3, indexes[1]);
- EXPECT_EQ(4, indexes[2]);
- EXPECT_EQ(5, indexes[3]);
-
- // One last check. We'll test that the constructor of does indeed
- // add a function to the function vector and that the static initialization
- // was not somehow by chance.
- HookRegistrationFunction::getFunctionVector().clear();
- EXPECT_TRUE(HookRegistrationFunction::getFunctionVector().empty());
- epsilon = 0;
-
- // Register a single registration function.
- HookRegistrationFunction f3(registerEpsilon);
- EXPECT_EQ(1, HookRegistrationFunction::getFunctionVector().size());
-
- // Execute it...
- ServerHooks hooks2;
- EXPECT_EQ(0, epsilon);
- EXPECT_EQ(2, hooks2.getCount());
- HookRegistrationFunction::execute(hooks2);
-
- // There should be three hooks, with the new one assigned an index of 2.
- names = hooks2.getHookNames();
- ASSERT_EQ(3, names.size());
- sort(names.begin(), names.end());
- EXPECT_EQ(string("context_create"), names[0]);
- EXPECT_EQ(string("context_destroy"), names[1]);
- EXPECT_EQ(string("epsilon"), names[2]);
-
- EXPECT_EQ(2, epsilon);
-}
-
-} // Anonymous namespace
diff --git a/tests/lettuce/configurations/xfrin/retransfer_slave.conf.orig b/tests/lettuce/configurations/xfrin/retransfer_slave.conf.orig
index 2e6b17f..aff8218 100644
--- a/tests/lettuce/configurations/xfrin/retransfer_slave.conf.orig
+++ b/tests/lettuce/configurations/xfrin/retransfer_slave.conf.orig
@@ -8,7 +8,6 @@
} ]
},
"Auth": {
- "database_file": "data/test_nonexistent_db.sqlite3",
"listen_on": [ {
"address": "::1",
"port": 47806
diff --git a/tests/lettuce/configurations/xfrin/retransfer_slave_diffs.conf b/tests/lettuce/configurations/xfrin/retransfer_slave_diffs.conf
index 8f5e8fb..24bfa2a 100644
--- a/tests/lettuce/configurations/xfrin/retransfer_slave_diffs.conf
+++ b/tests/lettuce/configurations/xfrin/retransfer_slave_diffs.conf
@@ -8,7 +8,6 @@
} ]
},
"Auth": {
- "database_file": "data/xfrin-before-diffs.sqlite3",
"listen_on": [ {
"address": "::1",
"port": 47806
diff --git a/tests/lettuce/configurations/xfrin/retransfer_slave_notify.conf.orig b/tests/lettuce/configurations/xfrin/retransfer_slave_notify.conf.orig
index 0d048c1..c4ba1ef 100644
--- a/tests/lettuce/configurations/xfrin/retransfer_slave_notify.conf.orig
+++ b/tests/lettuce/configurations/xfrin/retransfer_slave_notify.conf.orig
@@ -8,7 +8,6 @@
} ]
},
"Auth": {
- "database_file": "data/xfrin-notify.sqlite3",
"listen_on": [ {
"address": "::1",
"port": 47806
diff --git a/tests/lettuce/configurations/xfrin/retransfer_slave_notify_v4.conf b/tests/lettuce/configurations/xfrin/retransfer_slave_notify_v4.conf
index 8e55878..b99f3f7 100644
--- a/tests/lettuce/configurations/xfrin/retransfer_slave_notify_v4.conf
+++ b/tests/lettuce/configurations/xfrin/retransfer_slave_notify_v4.conf
@@ -8,7 +8,6 @@
} ]
},
"Auth": {
- "database_file": "data/xfrin-notify.sqlite3",
"listen_on": [ {
"address": "127.0.0.1",
"port": 47806
diff --git a/tests/lettuce/features/terrain/terrain.py b/tests/lettuce/features/terrain/terrain.py
index d82a6e1..d0ba4fe 100644
--- a/tests/lettuce/features/terrain/terrain.py
+++ b/tests/lettuce/features/terrain/terrain.py
@@ -135,7 +135,7 @@ class RunningProcess:
"""
stderr_write = open(self.stderr_filename, "w")
stdout_write = open(self.stdout_filename, "w")
- self.process = subprocess.Popen(args, 1, None, subprocess.PIPE,
+ self.process = subprocess.Popen(args, 0, None, subprocess.PIPE,
stdout_write, stderr_write)
# open them again, this time for reading
self.stderr = open(self.stderr_filename, "r")
diff --git a/tests/lettuce/features/xfrin_notify_handling.feature b/tests/lettuce/features/xfrin_notify_handling.feature
index 375a8a9..4f9d44e 100644
--- a/tests/lettuce/features/xfrin_notify_handling.feature
+++ b/tests/lettuce/features/xfrin_notify_handling.feature
@@ -75,7 +75,7 @@ Feature: Xfrin incoming notify handling
last bindctl output should not contain "error"
When I query statistics zones of bind10 module Xfrout with cmdctl port 47804
- The statistics counters are 0 in category .Xfrout.zones except for the following items
+ The statistics counters are 0 in category .Xfrout.zones.IN except for the following items
| item_name | item_value |
| _SERVER_.notifyoutv6 | 1 |
| _SERVER_.xfrreqdone | 1 |
@@ -100,7 +100,7 @@ Feature: Xfrin incoming notify handling
last bindctl output should not contain "error"
When I query statistics zones of bind10 module Xfrin with cmdctl
- The statistics counters are 0 in category .Xfrin.zones except for the following items
+ The statistics counters are 0 in category .Xfrin.zones.IN except for the following items
| item_name | item_value | min_value |
| _SERVER_.soaoutv6 | 1 | |
| _SERVER_.axfrreqv6 | 1 | |
@@ -177,7 +177,7 @@ Feature: Xfrin incoming notify handling
last bindctl output should not contain "error"
When I query statistics zones of bind10 module Xfrout with cmdctl port 47804
- The statistics counters are 0 in category .Xfrout.zones except for the following items
+ The statistics counters are 0 in category .Xfrout.zones.IN except for the following items
| item_name | item_value |
| _SERVER_.notifyoutv4 | 1 |
| _SERVER_.xfrreqdone | 1 |
@@ -202,7 +202,7 @@ Feature: Xfrin incoming notify handling
last bindctl output should not contain "error"
When I query statistics zones of bind10 module Xfrin with cmdctl
- The statistics counters are 0 in category .Xfrin.zones except for the following items
+ The statistics counters are 0 in category .Xfrin.zones.IN except for the following items
| item_name | item_value | min_value |
| _SERVER_.soaoutv4 | 1 | |
| _SERVER_.axfrreqv4 | 1 | |
@@ -282,7 +282,7 @@ Feature: Xfrin incoming notify handling
last bindctl output should not contain "error"
When I query statistics zones of bind10 module Xfrout with cmdctl port 47804
- The statistics counters are 0 in category .Xfrout.zones except for the following items
+ The statistics counters are 0 in category .Xfrout.zones.IN except for the following items
| item_name | item_value | min_value | max_value |
| _SERVER_.notifyoutv6 | 1 | | |
| _SERVER_.xfrrej | | 1 | 3 |
@@ -310,7 +310,7 @@ Feature: Xfrin incoming notify handling
last bindctl output should not contain "error"
When I query statistics zones of bind10 module Xfrin with cmdctl
- The statistics counters are 0 in category .Xfrin.zones except for the following items
+ The statistics counters are 0 in category .Xfrin.zones.IN except for the following items
| item_name | item_value |
| _SERVER_.soaoutv6 | 1 |
| _SERVER_.axfrreqv6 | 1 |
@@ -388,7 +388,7 @@ Feature: Xfrin incoming notify handling
last bindctl output should not contain "error"
When I query statistics zones of bind10 module Xfrout with cmdctl port 47804
- The statistics counters are 0 in category .Xfrout.zones except for the following items
+ The statistics counters are 0 in category .Xfrout.zones.IN except for the following items
| item_name | item_value | min_value | max_value |
| _SERVER_.notifyoutv4 | 1 | | |
| _SERVER_.xfrrej | | 1 | 3 |
@@ -416,7 +416,7 @@ Feature: Xfrin incoming notify handling
last bindctl output should not contain "error"
When I query statistics zones of bind10 module Xfrin with cmdctl
- The statistics counters are 0 in category .Xfrin.zones except for the following items
+ The statistics counters are 0 in category .Xfrin.zones.IN except for the following items
| item_name | item_value |
| _SERVER_.soaoutv4 | 1 |
| _SERVER_.axfrreqv4 | 1 |
@@ -454,7 +454,7 @@ Feature: Xfrin incoming notify handling
last bindctl output should not contain "error"
When I query statistics zones of bind10 module Xfrout with cmdctl port 47804
- The statistics counters are 0 in category .Xfrout.zones except for the following items
+ The statistics counters are 0 in category .Xfrout.zones.IN except for the following items
| item_name | min_value | max_value |
| _SERVER_.notifyoutv6 | 1 | 5 |
| example.org..notifyoutv6 | 1 | 5 |
More information about the bind10-changes
mailing list