BIND 10 trac1112, updated. 12d62d54d33fbb1572a1aa3089b0d547d02924aa Merge branch 'master' into trac1112
BIND 10 source code commits
bind10-changes at lists.isc.org
Wed Sep 21 03:01:44 UTC 2011
The branch, trac1112 has been updated
via 12d62d54d33fbb1572a1aa3089b0d547d02924aa (commit)
via c38112d8b59bfb6e73b5fbc637fa9eaaae42c52d (commit)
via af10f1ef696ee94f817bc389e0e8b6cd08234333 (commit)
via 3eb0dedb8a5d9835b394484c6112a4b2fcbe9d51 (commit)
via 2f8c4b3da6060a9b57e944726dd61cb1b2a19906 (commit)
via 433f29fd44d8dd6c940e49ee2657b769d70781fe (commit)
via 45ef63790b34ebc2d26081609bb168aefee800dc (commit)
via cbf08d56345922d754182b941b84b18bfddabcda (commit)
via 84a95705e1e8219187e75433baec2fd2fc8ba2fe (commit)
via aa5fd84d438cf165c9836fa545d15c33781401af (commit)
via fac67afceead36ba7296e194942811d9ed3b437b (commit)
via 90b740caf4cc5d207dfa2ac98f1c73d9818792e2 (commit)
via 0ea828cb5c74b0f9a254aeab2c7d31ff214371e5 (commit)
via 170a0661dfb17014a62cd2eeaaa99e408bc55a14 (commit)
via b12f4e55007ee2e8130991f322e782bb31a8a289 (commit)
via 18083458382473b414a3fc7f57623d2241f487ef (commit)
via fbe4ee1f76237fdd586638ce1ded4c6e5bd0bf1d (commit)
via 9c53309978b4a4bf684b3abbb853876c5413f875 (commit)
via 8ee5844e8dc3ec7d99a5890bdc85f54afd8886b6 (commit)
via c9ad781ebbaebb2e57956ac9eda542eaa88a743b (commit)
via 4d39f72b87677c194d282a9e93de67dc0adfb4f3 (commit)
via ece8bd155e646869b10fd08817ee7cd71c699c61 (commit)
via b59f898456b33294d71a333d3f3b4fe9dc81e3dd (commit)
via 02b2e71bdc1564f4272869bb5676727af809870f (commit)
via 8d1942a3b7516e8161b7f54888da2a4a4d27484e (commit)
via 856ff83ad2b97c136de1103a421547bdcb332e74 (commit)
via 7cc9b08f18967fa1a694f5b7e320aad62d0d3e88 (commit)
via 25e56e5d1bc9197e882e3a42285d0efad21a51f2 (commit)
via 87d2a8766e610a0dece7d86268ac9be4122d6d82 (commit)
via 64ac0166d5ea3b565f500f8a770dfa4d7d9f6a28 (commit)
via c86612cd4120b9ad3d00978c04ea252e7d501e44 (commit)
via c1c2ddf5be4556e6e8cd52a314ddd6d026c7e540 (commit)
via ba50f189eced101999efb96672179aa1024204e9 (commit)
via 6906362bebdbe7e0de66f2c8d10a00bd34911121 (commit)
via 83a58b817e5c0432d543b66208f502b059fdbe13 (commit)
via 40126733cc69634035b0cca3a0c90ee3a606ea3b (commit)
via bcafb8b98d5df77108a83a6bd8b7746f7c2616d7 (commit)
via 4ef59f25a452f934408a9ba837cea9b7fab0be48 (commit)
via 3d069e2745070bc23f14c845cb7d8116d919f0da (commit)
via 230df584722d08705f2cb3b99940b764b1cb7865 (commit)
via fda403b09887a24403c3a90d7ad6c95288f2d641 (commit)
via 04b04226b726b6e1fea6bba970556b9ed5cc3446 (commit)
via 3a838eb454ed0de4f073b99e94e02014eca63a56 (commit)
via 748c3e1aeb833012a19b651af7d98757a8ffc50f (commit)
via a0e04c0ad837b4b42caf139573f2a95c86cdac76 (commit)
via fcb2409598d37e2078076cf43794ef6c445ac22f (commit)
via c6d2a365580709981852007cd0a9a3b32afaa5c3 (commit)
via da8bfe82aa18a67b1a99fa459f48cea89ee2a41a (commit)
via 7980a6c8e598d34f5f733f5c6c3ca83c0a0f1187 (commit)
via 9c62a36b0ebf9ff4ef3dad1f4d91195d301348ed (commit)
via 2ec9338d84714ea670ee888f1edf5a4ad220ea9a (commit)
via 1d907966f7f0fe7089efe46d8b808d9115f0d167 (commit)
via 93327a85ea63f7043c49a0af2384a1e274ab1dda (commit)
via 75e756cdf9d5b08e859afac5cef38bd818a90e60 (commit)
via 778bd1be6ced7f4a135e2a6bcc7414c4e4bdc27d (commit)
via 38c8e9a9ccfd7fd57bc5fa5090c86cf7b7920d28 (commit)
via ddf9da5175b1182810838861f1464fb05fe00104 (commit)
via 8fe581570c2ef4f881762f4f22ef4f66c1063491 (commit)
via 2812fa5cb0c2013ef1696888651390aa71a76b4a (commit)
via b131dd71ce147b4efcece9dd8fba16c51fefa492 (commit)
via 84d83c1d8979e2906971af79f2e41083299beb7e (commit)
via 255bf5b18e2b0e28a65062e87dc2d1212376bfc2 (commit)
via e2ada81cd2a090f707147abdb73a90d44db2f2b0 (commit)
via 0953b3f5d7ed1b4a25362f9a2d1a41eeeda8efa6 (commit)
via 8d380bb47dd24c7fd2c4880a4106835d871bf4d5 (commit)
via 77ba8639c274865c762eee688383c321f18ef889 (commit)
via ecf3f4b962026aa9094ee321b03ee32df2fdf1d2 (commit)
via 30df43575158b0cb294ec49a8463fe8b49593e62 (commit)
via 4c0accf0a591b0422c84216150e1b9b4e008609e (commit)
via 1f051716bce3d7aa2545722ba41958df9758cadc (commit)
via 10553ed4ebb5b949ae74d277d398d2e8a3909ea5 (commit)
via d916aef6af6bb8506b1ff4756054a1697410982f (commit)
via 4700bada6282f5ad10b53cd8ca7cc03b8fea791d (commit)
via ef64723fe9638f8d56f58fba44a149ac620eadd9 (commit)
via 5de6f9658f745e05361242042afd518b444d7466 (commit)
via 3f847f9d35bf2bf9ee0d957ea1aa9ffb27a32cdb (commit)
via df047c5ccb5c81f9a3d36f7fc38a19bc7c8f2ac2 (commit)
via a7346d50ae5389ce37e35a7131f0f218663b8c68 (commit)
via ad91831c938430b6d4a8fd7bfae517a0f1e327c1 (commit)
via 43da3c6c1cc7cb5fcb1dbe2f983a53e883408d1b (commit)
via 27b3488b71a5c3b95652eab2720497d6d055346e (commit)
via 087c6def9087019640a437b63c782a5c22de1feb (commit)
via 3b0ccfb2f23961e4cbddb9d0873bab0f4c1d4c3d (commit)
via 0a39659638fc68f60b95b102968d7d0ad75443ea (commit)
via 2684301690d59a41cd20d131491e0714d156fa7c (commit)
via 5baa7aa73ad8d8d5250990a9e330b9b746659452 (commit)
via 1921e1297dfcb878b9417edefe4d87639c827948 (commit)
via 872bd5756ba8b5daeeacedfcd4ec38bc50035ec8 (commit)
via 67d8e93028e014f644868fede3570abb28e5fb43 (commit)
via 4ff5e524a7f79ad7f4513ebed3ca0990392263af (commit)
via 73df015104eb5ac8934ff1176c24079e6e9b09c3 (commit)
via 586d49827ebaa2cf2c70dc030c5830afb1fb89f5 (commit)
via 2b755575c9d0277980008df99f92c38dd6b3a420 (commit)
via 38d1a8aa943424e1a0de0503ee8aa961a95d0e14 (commit)
via 4579a2a9f43a38144539447bb5076bfcbaf8b6d8 (commit)
via 58d7fda0fd2efc2d4bccfdcb55ce6ba42af83aa0 (commit)
via 7f08fc3123ef7d26a2e61dd29455c07510404a7e (commit)
via af6328603521584ff62b25a6f86a923bba5a4f5e (commit)
via 9d48d1964569b49be17afc3e20085a23544a32de (commit)
via 28988a78d3b80c7f1080fce696acf176b74a29fe (commit)
via 5c6391cca55baec236b813b4c2e2b7699595559d (commit)
via 08b5add9a6e405342c0c8bc3bdf5d552ed45df0e (commit)
via a176724d99c073f8e547dea2675a5b7d1df70515 (commit)
via a9b769b8bf12e2922e385c62ce337fb723731699 (commit)
via 6318db7dc90cb6656cc2a1f8e875f2258f6a4343 (commit)
via 35a0136d56de7faca280666ba40bb1b87a85fff6 (commit)
via b97162729a3ad4214e5f6b85452a27904b8f34ca (commit)
via ad36c799ff07d47ebd5c861c63e9feef50408e34 (commit)
via 9d3e78f0d8075ad62391ed005e1e82f79f05e2ca (commit)
via c5e0ebf85ef50e61457f3b99a05109a92b328573 (commit)
via 8216d5dbe1ef23d56ba589fe1de619a601bada4b (commit)
via 1c834de994f51a1fb98add648dad49abfea2c403 (commit)
via 353c08576b5c7fae46be834cb815df744ec2ba96 (commit)
via dc9318330acbd36e07ad5a4e8a68c9a6e2430543 (commit)
via e4a17a0630a6460090c5cdb562e02ba992a74fa8 (commit)
via 954143a2748110c720d28df49159ed4f0bc1a1a2 (commit)
via 8cfa0f76baf92f82bf2865b3557c0a2094e81cb4 (commit)
via bdebd1afa4bf82120c66d9ee8d8cab500ab0b606 (commit)
via 451086b203ef3e4611487630225a7650ad9322e7 (commit)
via c0c7b21ab57bb9445329fed9e1451c534aab6a67 (commit)
via 59add6ec0f7e96ee81a7b9970228b8f795b01997 (commit)
via 1b421982a6fcadebc72d3d6ee7a4e34eec61a25d (commit)
via 45630ca90e823247c429f82b338244a9bba9baf4 (commit)
via 36c6035855db0ae87a64a0d169e0230d936e3e64 (commit)
via 5ece3fe5e40efbcf7d727650475c35850624cfaf (commit)
via d88becea33630677dbb5123cd72fa8695512311a (commit)
via 171088e69ff96a2e242cfdf98e8d1f0415d4c172 (commit)
via 568a8cc472f3207b44b92428e7ac40338d9ede37 (commit)
via 4c98f3dc47545794daccd4978103f6b98236ad82 (commit)
via 2dfa0983e4680f321a3d4f1bd0d826abd88f455c (commit)
via ec8fed8c805b513ea15ad76eb380c639dba88548 (commit)
via ce3be84b9f772fda5f08947fec92764119989019 (commit)
via d60bb44c243f27053589b5501529b0001404373f (commit)
via 92dcac243a4a2924bab85d1519a0c7a20853f9cc (commit)
via 2bd6dc4ac6ac61705517df297320fa79b308b9e3 (commit)
via bbc661e3c38f02b4a1fb50bd4e058a22150b0087 (commit)
via b4ae924f504e9749989059a14e6a5dc830c99e81 (commit)
via 20871297d2aaae57acb79e987ff80a9020d608d1 (commit)
via 2384bcf387e93435658ec1ab92addbf28c9ab640 (commit)
via 1d314b2544b8af8a936c90e00a0dbbb605410952 (commit)
via e3c81bd07046903b4b3bff8325024aafcdb35cba (commit)
via 9001f1db99dfff10957dc2a971e7466a496f0f2f (commit)
via 616fb3be8c0b3c266eaf0aa4ae399918fc7992ef (commit)
via 13e236a3d647d15858b061c7d96288bf7407e090 (commit)
via a7fe0d5982813f092f8a497d350620c02b995649 (commit)
via 9bbf7837ed869bfa42849f433367b0471bf7bc58 (commit)
via ed6fc7857e3fe7d64f19a0bed27226964009f095 (commit)
via edbcbf0ab15f140b96efab5fae808b35e705cf67 (commit)
via f3e53fe5cba59946ddcf24be423eece1ab596769 (commit)
via e0215095818d30e80b59e99689f2cf0dfbbae841 (commit)
via 10cfb9ccd5b2eb489b14804e0ea9a73c80e697e6 (commit)
via acb5dff4449286422f23a7d5867b3bd792c888e5 (commit)
via 39a0a5c65d0802f40ab428474b1e6d981a91fbce (commit)
via 0c9db8bbeb7187218a5b47d82df18e38128d06a3 (commit)
via 9882d600d0bbbc115671b12646e690ccddbf5348 (commit)
via 59b545e90d30444a97c8e925569d240c819d42b4 (commit)
via 7e89c625c5d12b5816c857d0c0910922f8803f82 (commit)
via d71b7da05d3e1a82047e35c2720c759bdc0fb44f (commit)
via a577b387b7e5c9c8afd371767fccc85009e84485 (commit)
via 8e82cd7374cda9ef55f88504a94d31b06d7e1bd4 (commit)
via 1351cb42c92cd415003adf6234d96507c8a6d2db (commit)
via 575bd3ec2fe918257cb448eee8ebbff269d85431 (commit)
via 69eb1a250699f481427c2d12abf14314fee9e6eb (commit)
via a36be891057f7a2505db032768264c79f37f05e7 (commit)
via 23b1e8bb169e058dfb11b826b1b59d606d64ce20 (commit)
via a53c7d7c450de09ceb04b47cb59450225827bd51 (commit)
via c759e90e162192eda89c5046fa446891aac259c7 (commit)
via 21850ab947dbdf98b1d89afc36d8bcfc6001592e (commit)
from ee468e8f02f1cd1bcf09da75170ed62dc230b70e (commit)
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 12d62d54d33fbb1572a1aa3089b0d547d02924aa
Merge: ee468e8f02f1cd1bcf09da75170ed62dc230b70e c38112d8b59bfb6e73b5fbc637fa9eaaae42c52d
Author: Ocean Wang <wanghaidong at cnnic.cn>
Date: Wed Sep 21 11:01:09 2011 +0800
Merge branch 'master' into trac1112
-----------------------------------------------------------------------
Summary of changes:
ChangeLog | 26 +-
configure.ac | 7 +
doc/Doxyfile | 4 +-
src/bin/auth/query.cc | 38 +-
src/bin/auth/query.h | 10 +-
src/bin/auth/tests/query_unittest.cc | 102 +-
src/bin/bind10/bind10_src.py.in | 17 +-
src/bin/bind10/run_bind10.sh.in | 2 +-
src/bin/bind10/tests/Makefile.am | 2 +
src/bin/bind10/tests/bind10_test.py.in | 6 +
src/bin/cfgmgr/plugins/tests/Makefile.am | 2 +-
src/bin/cfgmgr/tests/Makefile.am | 4 +-
src/bin/stats/b10-stats.8 | 2 +-
src/bin/stats/b10-stats.xml | 2 +-
src/bin/stats/stats.py.in | 26 +-
src/bin/stats/tests/b10-stats_test.py | 3 +-
src/bin/stats/tests/isc/cc/session.py | 10 +-
src/bin/tests/Makefile.am | 1 +
src/bin/xfrin/b10-xfrin.8 | 6 +-
src/bin/xfrin/b10-xfrin.xml | 4 +-
src/bin/xfrin/tests/Makefile.am | 2 +-
src/bin/xfrout/tests/Makefile.am | 5 +-
src/bin/zonemgr/tests/zonemgr_test.py | 65 +-
src/bin/zonemgr/zonemgr.py.in | 45 +-
src/lib/cache/cache_messages.mes | 2 +-
src/lib/datasrc/client.h | 63 +-
src/lib/datasrc/database.cc | 457 +++-
src/lib/datasrc/database.h | 486 ++++-
src/lib/datasrc/datasrc_messages.mes | 80 +-
src/lib/datasrc/memory_datasrc.cc | 6 +
src/lib/datasrc/memory_datasrc.h | 11 +
src/lib/datasrc/sqlite3_accessor.cc | 706 ++++---
src/lib/datasrc/sqlite3_accessor.h | 123 +-
src/lib/datasrc/sqlite3_datasrc.cc | 95 +-
src/lib/datasrc/tests/Makefile.am | 7 +-
src/lib/datasrc/tests/client_unittest.cc | 3 +
src/lib/datasrc/tests/database_unittest.cc | 2302 ++++++++++++++------
src/lib/datasrc/tests/memory_datasrc_unittest.cc | 6 +-
src/lib/datasrc/tests/sqlite3_accessor_unittest.cc | 696 +++++--
src/lib/datasrc/tests/testdata/Makefile.am | 6 +
.../{example2.com.sqlite3 => rwtest.sqlite3} | Bin 11264 -> 11264 bytes
src/lib/datasrc/zone.h | 295 +++-
src/lib/dns/Makefile.am | 8 +
src/lib/dns/python/Makefile.am | 52 +-
src/lib/dns/python/edns_python.cc | 262 ++-
.../python/{tsig_rdata_python.h => edns_python.h} | 49 +-
src/lib/dns/python/message_python.cc | 465 ++---
.../python/message_python.h} | 26 +-
src/lib/dns/python/messagerenderer_python.cc | 94 +-
src/lib/dns/python/messagerenderer_python.h | 37 +-
src/lib/dns/python/name_python.cc | 133 +-
src/lib/dns/python/name_python.h | 45 +-
src/lib/dns/python/opcode_python.cc | 231 +--
.../{tsigrecord_python.h => opcode_python.h} | 45 +-
src/lib/dns/python/pydnspp.cc | 689 ++++++-
src/lib/dns/python/pydnspp_common.cc | 36 +
src/lib/dns/python/pydnspp_common.h | 2 -
src/lib/dns/python/pydnspp_towire.h | 4 +-
src/lib/dns/python/question_python.cc | 271 ++-
.../{tsig_rdata_python.h => question_python.h} | 49 +-
src/lib/dns/python/rcode_python.cc | 109 +-
src/lib/dns/python/rcode_python.h | 49 +-
src/lib/dns/python/rdata_python.cc | 289 ++--
.../python/{tsigrecord_python.h => rdata_python.h} | 49 +-
src/lib/dns/python/rrclass_python.cc | 303 ++--
.../{tsig_rdata_python.h => rrclass_python.h} | 51 +-
src/lib/dns/python/rrset_python.cc | 494 +++--
src/lib/dns/python/rrset_python.h | 78 +
src/lib/dns/python/rrttl_python.cc | 281 ++--
.../python/{tsigrecord_python.h => rrttl_python.h} | 46 +-
src/lib/dns/python/rrtype_python.cc | 348 ++--
.../{tsigrecord_python.h => rrtype_python.h} | 47 +-
src/lib/dns/python/tests/Makefile.am | 2 +-
src/lib/dns/python/tsig_python.cc | 105 +-
src/lib/dns/python/tsig_python.h | 28 +-
src/lib/dns/python/tsig_rdata_python.cc | 62 +-
src/lib/dns/python/tsig_rdata_python.h | 29 +-
src/lib/dns/python/tsigerror_python.cc | 105 +-
src/lib/dns/python/tsigerror_python.h | 10 +-
src/lib/dns/python/tsigkey_python.cc | 133 +-
src/lib/dns/python/tsigkey_python.h | 52 +-
src/lib/dns/python/tsigrecord_python.cc | 82 +-
src/lib/dns/python/tsigrecord_python.h | 28 +-
src/lib/dns/rdata/generic/detail/txt_like.h | 172 ++
src/lib/dns/rdata/generic/rrsig_46.cc | 2 +-
src/lib/dns/rdata/generic/rrsig_46.h | 2 +-
.../dns/rdata/generic/{cname_5.cc => spf_99.cc} | 65 +-
src/lib/dns/rdata/generic/{txt_16.h => spf_99.h} | 13 +-
src/lib/dns/rdata/generic/txt_16.cc | 121 +-
src/lib/dns/rdata/generic/txt_16.h | 11 +-
src/lib/dns/rdata/in_1/dhcid_49.cc | 145 ++
.../{generic/nsec3param_51.h => in_1/dhcid_49.h} | 36 +-
src/lib/python/isc/acl/tests/Makefile.am | 2 +-
src/lib/python/isc/datasrc/sqlite3_ds.py | 84 +-
.../python/isc/datasrc/tests/sqlite3_ds_test.py | 50 +-
src/lib/python/isc/log/tests/Makefile.am | 18 +-
src/lib/python/isc/notify/tests/Makefile.am | 2 +-
src/lib/util/python/pycppwrapper_util.h | 2 +-
src/lib/util/python/wrapper_template.cc | 4 +-
src/lib/util/python/wrapper_template.h | 6 +-
100 files changed, 7766 insertions(+), 3982 deletions(-)
create mode 100644 src/lib/datasrc/tests/testdata/Makefile.am
copy src/lib/datasrc/tests/testdata/{example2.com.sqlite3 => rwtest.sqlite3} (67%)
copy src/lib/dns/python/{tsig_rdata_python.h => edns_python.h} (54%)
copy src/lib/{python/isc/acl/dns_requestloader_python.h => dns/python/message_python.h} (69%)
copy src/lib/dns/python/{tsigrecord_python.h => opcode_python.h} (53%)
copy src/lib/dns/python/{tsig_rdata_python.h => question_python.h} (51%)
copy src/lib/dns/python/{tsigrecord_python.h => rdata_python.h} (50%)
copy src/lib/dns/python/{tsig_rdata_python.h => rrclass_python.h} (51%)
create mode 100644 src/lib/dns/python/rrset_python.h
copy src/lib/dns/python/{tsigrecord_python.h => rrttl_python.h} (52%)
copy src/lib/dns/python/{tsigrecord_python.h => rrtype_python.h} (51%)
create mode 100644 src/lib/dns/rdata/generic/detail/txt_like.h
copy src/lib/dns/rdata/generic/{cname_5.cc => spf_99.cc} (53%)
copy src/lib/dns/rdata/generic/{txt_16.h => spf_99.h} (85%)
create mode 100644 src/lib/dns/rdata/in_1/dhcid_49.cc
copy src/lib/dns/rdata/{generic/nsec3param_51.h => in_1/dhcid_49.h} (67%)
-----------------------------------------------------------------------
diff --git a/ChangeLog b/ChangeLog
index 43c9154..a71c8a8 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,7 +1,25 @@
-282. [func] ocean
- libdns++: Implement the NAPTR rrtype according to RFC2915,
- RFC2168 and RFC3403.
- (Trac #1130, git 01d8d0f13289ecdf9996d6d5d26ac0d43e30549c)
+285. [bug] jelte
+ sqlite3 data source: fixed a race condition on initial startup,
+ when the database has not been initialized yet, and multiple
+ processes are trying to do so, resulting in one of them failing.
+ (Trac #326, git 5de6f9658f745e05361242042afd518b444d7466)
+
+284. [bug] jerry
+ b10-zonemgr: zonemgr will not terminate on empty zones, it will
+ log a warning and try to do zone transfer for them.
+ (Trac #1153, git 0a39659638fc68f60b95b102968d7d0ad75443ea)
+
+283. [bug] zhanglikun
+ Make stats and boss processes wait for answer messages from each
+ other in block mode to avoid orphan answer messages, add an internal
+ command "getstats" to boss process for getting statistics data from
+ boss.
+ (Trac #519, git 67d8e93028e014f644868fede3570abb28e5fb43)
+
+282. [func] ocean
+ libdns++: Implement the NAPTR rrtype according to RFC2915,
+ RFC2168 and RFC3403.
+ (Trac #1130, git 01d8d0f13289ecdf9996d6d5d26ac0d43e30549c)
bind10-devel-20110819 released on August 19, 2011
diff --git a/configure.ac b/configure.ac
index 6e129b6..6ae71bf 100644
--- a/configure.ac
+++ b/configure.ac
@@ -12,6 +12,12 @@ AC_PROG_CXX
# Libtool configuration
#
+
+# libtool cannot handle spaces in paths, so exit early if there is one
+if [ test `echo $PWD | grep -c ' '` != "0" ]; then
+ AC_MSG_ERROR([BIND 10 cannot be built in a directory that contains spaces, because of libtool limitations. Please change the directory name, or use a symbolic link that does not contain spaces.])
+fi
+
# On FreeBSD (and probably some others), clang++ does not meet an autoconf
# assumption in identifying libtool configuration regarding shared library:
# the configure script will execute "$CC -shared $CFLAGS/$CXXFLAGS -v" and
@@ -856,6 +862,7 @@ AC_CONFIG_FILES([Makefile
src/lib/exceptions/tests/Makefile
src/lib/datasrc/Makefile
src/lib/datasrc/tests/Makefile
+ src/lib/datasrc/tests/testdata/Makefile
src/lib/xfr/Makefile
src/lib/log/Makefile
src/lib/log/compiler/Makefile
diff --git a/doc/Doxyfile b/doc/Doxyfile
index ceb806f..71b0738 100644
--- a/doc/Doxyfile
+++ b/doc/Doxyfile
@@ -568,8 +568,8 @@ WARN_LOGFILE =
# directories like "/usr/src/myproject". Separate the files or directories
# with spaces.
-INPUT = ../src/lib/cc ../src/lib/config \
- ../src/lib/cryptolink ../src/lib/dns ../src/lib/datasrc \
+INPUT = ../src/lib/exceptions ../src/lib/cc \
+ ../src/lib/config ../src/lib/cryptolink ../src/lib/dns ../src/lib/datasrc \
../src/bin/auth ../src/bin/resolver ../src/lib/bench ../src/lib/log \
../src/lib/log/compiler ../src/lib/asiolink/ ../src/lib/nsas \
../src/lib/testutils ../src/lib/cache ../src/lib/server_common/ \
diff --git a/src/bin/auth/query.cc b/src/bin/auth/query.cc
index 3fe03c8..898fff7 100644
--- a/src/bin/auth/query.cc
+++ b/src/bin/auth/query.cc
@@ -67,20 +67,21 @@ Query::findAddrs(ZoneFinder& zone, const Name& qname,
// Find A rrset
if (qname_ != qname || qtype_ != RRType::A()) {
ZoneFinder::FindResult a_result = zone.find(qname, RRType::A(), NULL,
- options);
+ options | dnssec_opt_);
if (a_result.code == ZoneFinder::SUCCESS) {
response_.addRRset(Message::SECTION_ADDITIONAL,
- boost::const_pointer_cast<RRset>(a_result.rrset));
+ boost::const_pointer_cast<RRset>(a_result.rrset), dnssec_);
}
}
// Find AAAA rrset
if (qname_ != qname || qtype_ != RRType::AAAA()) {
ZoneFinder::FindResult aaaa_result =
- zone.find(qname, RRType::AAAA(), NULL, options);
+ zone.find(qname, RRType::AAAA(), NULL, options | dnssec_opt_);
if (aaaa_result.code == ZoneFinder::SUCCESS) {
response_.addRRset(Message::SECTION_ADDITIONAL,
- boost::const_pointer_cast<RRset>(aaaa_result.rrset));
+ boost::const_pointer_cast<RRset>(aaaa_result.rrset),
+ dnssec_);
}
}
}
@@ -88,7 +89,7 @@ Query::findAddrs(ZoneFinder& zone, const Name& qname,
void
Query::putSOA(ZoneFinder& zone) const {
ZoneFinder::FindResult soa_result(zone.find(zone.getOrigin(),
- RRType::SOA()));
+ RRType::SOA(), NULL, dnssec_opt_));
if (soa_result.code != ZoneFinder::SUCCESS) {
isc_throw(NoSOA, "There's no SOA record in zone " <<
zone.getOrigin().toText());
@@ -99,7 +100,7 @@ Query::putSOA(ZoneFinder& zone) const {
* to insist.
*/
response_.addRRset(Message::SECTION_AUTHORITY,
- boost::const_pointer_cast<RRset>(soa_result.rrset));
+ boost::const_pointer_cast<RRset>(soa_result.rrset), dnssec_);
}
}
@@ -107,14 +108,15 @@ void
Query::getAuthAdditional(ZoneFinder& zone) const {
// Fill in authority and addtional sections.
ZoneFinder::FindResult ns_result = zone.find(zone.getOrigin(),
- RRType::NS());
+ RRType::NS(), NULL,
+ dnssec_opt_);
// zone origin name should have NS records
if (ns_result.code != ZoneFinder::SUCCESS) {
isc_throw(NoApexNS, "There's no apex NS records in zone " <<
zone.getOrigin().toText());
} else {
response_.addRRset(Message::SECTION_AUTHORITY,
- boost::const_pointer_cast<RRset>(ns_result.rrset));
+ boost::const_pointer_cast<RRset>(ns_result.rrset), dnssec_);
// Handle additional for authority section
getAdditional(zone, *ns_result.rrset);
}
@@ -147,12 +149,14 @@ Query::process() const {
keep_doing = false;
std::auto_ptr<RRsetList> target(qtype_is_any ? new RRsetList : NULL);
const ZoneFinder::FindResult db_result(
- result.zone_finder->find(qname_, qtype_, target.get()));
+ result.zone_finder->find(qname_, qtype_, target.get(),
+ dnssec_opt_));
switch (db_result.code) {
case ZoneFinder::DNAME: {
// First, put the dname into the answer
response_.addRRset(Message::SECTION_ANSWER,
- boost::const_pointer_cast<RRset>(db_result.rrset));
+ boost::const_pointer_cast<RRset>(db_result.rrset),
+ dnssec_);
/*
* Empty DNAME should never get in, as it is impossible to
* create one in master file.
@@ -188,7 +192,7 @@ Query::process() const {
qname_.getLabelCount() -
db_result.rrset->getName().getLabelCount()).
concatenate(dname.getDname())));
- response_.addRRset(Message::SECTION_ANSWER, cname);
+ response_.addRRset(Message::SECTION_ANSWER, cname, dnssec_);
break;
}
case ZoneFinder::CNAME:
@@ -202,20 +206,23 @@ Query::process() const {
* So, just put it there.
*/
response_.addRRset(Message::SECTION_ANSWER,
- boost::const_pointer_cast<RRset>(db_result.rrset));
+ boost::const_pointer_cast<RRset>(db_result.rrset),
+ dnssec_);
break;
case ZoneFinder::SUCCESS:
if (qtype_is_any) {
// If quety type is ANY, insert all RRs under the domain
// into answer section.
BOOST_FOREACH(RRsetPtr rrset, *target) {
- response_.addRRset(Message::SECTION_ANSWER, rrset);
+ response_.addRRset(Message::SECTION_ANSWER, rrset,
+ dnssec_);
// Handle additional for answer section
getAdditional(*result.zone_finder, *rrset.get());
}
} else {
response_.addRRset(Message::SECTION_ANSWER,
- boost::const_pointer_cast<RRset>(db_result.rrset));
+ boost::const_pointer_cast<RRset>(db_result.rrset),
+ dnssec_);
// Handle additional for answer section
getAdditional(*result.zone_finder, *db_result.rrset);
}
@@ -233,7 +240,8 @@ Query::process() const {
case ZoneFinder::DELEGATION:
response_.setHeaderFlag(Message::HEADERFLAG_AA, false);
response_.addRRset(Message::SECTION_AUTHORITY,
- boost::const_pointer_cast<RRset>(db_result.rrset));
+ boost::const_pointer_cast<RRset>(db_result.rrset),
+ dnssec_);
getAdditional(*result.zone_finder, *db_result.rrset);
break;
case ZoneFinder::NXDOMAIN:
diff --git a/src/bin/auth/query.h b/src/bin/auth/query.h
index 13523e8..0ebbed8 100644
--- a/src/bin/auth/query.h
+++ b/src/bin/auth/query.h
@@ -139,11 +139,15 @@ public:
/// \param qname The query name
/// \param qtype The RR type of the query
/// \param response The response message to store the answer to the query.
+ /// \param dnssec If the answer should include signatures and NSEC/NSEC3 if
+ /// possible.
Query(const isc::datasrc::DataSourceClient& datasrc_client,
const isc::dns::Name& qname, const isc::dns::RRType& qtype,
- isc::dns::Message& response) :
+ isc::dns::Message& response, bool dnssec = false) :
datasrc_client_(datasrc_client), qname_(qname), qtype_(qtype),
- response_(response)
+ response_(response), dnssec_(dnssec),
+ dnssec_opt_(dnssec ? isc::datasrc::ZoneFinder::FIND_DNSSEC :
+ isc::datasrc::ZoneFinder::FIND_DEFAULT)
{}
/// Process the query.
@@ -211,6 +215,8 @@ private:
const isc::dns::Name& qname_;
const isc::dns::RRType& qtype_;
isc::dns::Message& response_;
+ const bool dnssec_;
+ const isc::datasrc::ZoneFinder::FindOptions dnssec_opt_;
};
}
diff --git a/src/bin/auth/tests/query_unittest.cc b/src/bin/auth/tests/query_unittest.cc
index 68f0a1d..4b8f013 100644
--- a/src/bin/auth/tests/query_unittest.cc
+++ b/src/bin/auth/tests/query_unittest.cc
@@ -111,7 +111,8 @@ public:
dname_name_("dname.example.com"),
has_SOA_(true),
has_apex_NS_(true),
- rrclass_(RRClass::IN())
+ rrclass_(RRClass::IN()),
+ include_rrsig_anyway_(false)
{
stringstream zone_stream;
zone_stream << soa_txt << zone_ns_txt << ns_addrs_txt <<
@@ -137,11 +138,14 @@ public:
// the apex NS.
void setApexNSFlag(bool on) { has_apex_NS_ = on; }
+ // Turn this on if you want it to return RRSIGs regardless of FIND_GLUE_OK
+ void setIncludeRRSIGAnyway(bool on) { include_rrsig_anyway_ = on; }
+
private:
typedef map<RRType, ConstRRsetPtr> RRsetStore;
typedef map<Name, RRsetStore> Domains;
Domains domains_;
- void loadRRset(ConstRRsetPtr rrset) {
+ void loadRRset(RRsetPtr rrset) {
domains_[rrset->getName()][rrset->getType()] = rrset;
if (rrset->getName() == delegation_name_ &&
rrset->getType() == RRType::NS()) {
@@ -149,6 +153,26 @@ private:
} else if (rrset->getName() == dname_name_ &&
rrset->getType() == RRType::DNAME()) {
dname_rrset_ = rrset;
+ // Add some signatures
+ } else if (rrset->getName() == Name("example.com.") &&
+ rrset->getType() == RRType::NS()) {
+ rrset->addRRsig(RdataPtr(new generic::RRSIG("NS 5 3 3600 "
+ "20000101000000 "
+ "20000201000000 "
+ "12345 example.com. "
+ "FAKEFAKEFAKE")));
+ } else if (rrset->getType() == RRType::A()) {
+ rrset->addRRsig(RdataPtr(new generic::RRSIG("A 5 3 3600 "
+ "20000101000000 "
+ "20000201000000 "
+ "12345 example.com. "
+ "FAKEFAKEFAKE")));
+ } else if (rrset->getType() == RRType::AAAA()) {
+ rrset->addRRsig(RdataPtr(new generic::RRSIG("AAAA 5 3 3600 "
+ "20000101000000 "
+ "20000201000000 "
+ "12345 example.com. "
+ "FAKEFAKEFAKE")));
}
}
@@ -161,6 +185,7 @@ private:
ConstRRsetPtr delegation_rrset_;
ConstRRsetPtr dname_rrset_;
const RRClass rrclass_;
+ bool include_rrsig_anyway_;
};
ZoneFinder::FindResult
@@ -195,7 +220,26 @@ MockZoneFinder::find(const Name& name, const RRType& type,
RRsetStore::const_iterator found_rrset =
found_domain->second.find(type);
if (found_rrset != found_domain->second.end()) {
- return (FindResult(SUCCESS, found_rrset->second));
+ ConstRRsetPtr rrset;
+ // Strip whatever signature there is in case DNSSEC is not required
+ // Just to make sure the Query asks for it when it is needed
+ if (options & ZoneFinder::FIND_DNSSEC ||
+ include_rrsig_anyway_ ||
+ !found_rrset->second->getRRsig()) {
+ rrset = found_rrset->second;
+ } else {
+ RRsetPtr noconst(new RRset(found_rrset->second->getName(),
+ found_rrset->second->getClass(),
+ found_rrset->second->getType(),
+ found_rrset->second->getTTL()));
+ for (RdataIteratorPtr
+ i(found_rrset->second->getRdataIterator());
+ !i->isLast(); i->next()) {
+ noconst->addRdata(i->getCurrent());
+ }
+ rrset = noconst;
+ }
+ return (FindResult(SUCCESS, rrset));
}
// If not found but we have a target, fill it with all RRsets here
@@ -304,6 +348,58 @@ TEST_F(QueryTest, exactMatch) {
www_a_txt, zone_ns_txt, ns_addrs_txt);
}
+TEST_F(QueryTest, exactMatchIgnoreSIG) {
+ // Check that we do not include the RRSIG when not requested even when
+ // we receive it from the data source.
+ mock_finder->setIncludeRRSIGAnyway(true);
+ Query query(memory_client, qname, qtype, response);
+ EXPECT_NO_THROW(query.process());
+ // find match rrset
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
+ www_a_txt, zone_ns_txt, ns_addrs_txt);
+}
+
+TEST_F(QueryTest, dnssecPositive) {
+ // Just like exactMatch, but the signatures should be included as well
+ Query query(memory_client, qname, qtype, response, true);
+ EXPECT_NO_THROW(query.process());
+ // find match rrset
+ // We can't let responseCheck to check the additional section as well,
+ // it gets confused by the two RRs for glue.delegation.../RRSIG due
+ // to it's design and fixing it would be hard. Therefore we simply
+ // check manually this one time.
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 4, 6,
+ (www_a_txt + std::string("www.example.com. 3600 IN RRSIG "
+ "A 5 3 3600 20000101000000 "
+ "20000201000000 12345 example.com. "
+ "FAKEFAKEFAKE\n")).c_str(),
+ (zone_ns_txt + std::string("example.com. 3600 IN RRSIG NS 5 "
+ "3 3600 20000101000000 "
+ "20000201000000 12345 "
+ "example.com. FAKEFAKEFAKE\n")).
+ c_str(), NULL);
+ RRsetIterator iterator(response.beginSection(Message::SECTION_ADDITIONAL));
+ const char* additional[] = {
+ "glue.delegation.example.com. 3600 IN A 192.0.2.153\n",
+ "glue.delegation.example.com. 3600 IN RRSIG A 5 3 3600 20000101000000 "
+ "20000201000000 12345 example.com. FAKEFAKEFAKE\n",
+ "glue.delegation.example.com. 3600 IN AAAA 2001:db8::53\n",
+ "glue.delegation.example.com. 3600 IN RRSIG AAAA 5 3 3600 "
+ "20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE\n",
+ "noglue.example.com. 3600 IN A 192.0.2.53\n",
+ "noglue.example.com. 3600 IN RRSIG A 5 3 3600 20000101000000 "
+ "20000201000000 12345 example.com. FAKEFAKEFAKE\n",
+ NULL
+ };
+ for (const char** rr(additional); *rr != NULL; ++ rr) {
+ ASSERT_FALSE(iterator ==
+ response.endSection(Message::SECTION_ADDITIONAL));
+ EXPECT_EQ(*rr, (*iterator)->toText());
+ iterator ++;
+ }
+ EXPECT_TRUE(iterator == response.endSection(Message::SECTION_ADDITIONAL));
+}
+
TEST_F(QueryTest, exactAddrMatch) {
// find match rrset, omit additional data which has already been provided
// in the answer section from the additional.
diff --git a/src/bin/bind10/bind10_src.py.in b/src/bin/bind10/bind10_src.py.in
index b497f7c..81b24f1 100755
--- a/src/bin/bind10/bind10_src.py.in
+++ b/src/bin/bind10/bind10_src.py.in
@@ -307,6 +307,11 @@ class BoB:
process_list.append([pid, self.processes[pid].name])
return process_list
+ def _get_stats_data(self):
+ return { "stats_data": {
+ 'bind10.boot_time': time.strftime('%Y-%m-%dT%H:%M:%SZ', _BASETIME)
+ }}
+
def command_handler(self, command, args):
logger.debug(DBG_COMMANDS, BIND10_RECEIVED_COMMAND, command)
answer = isc.config.ccsession.create_answer(1, "command not implemented")
@@ -316,14 +321,18 @@ class BoB:
if command == "shutdown":
self.runnable = False
answer = isc.config.ccsession.create_answer(0)
+ elif command == "getstats":
+ answer = isc.config.ccsession.create_answer(0, self._get_stats_data())
elif command == "sendstats":
# send statistics data to the stats daemon immediately
cmd = isc.config.ccsession.create_command(
- 'set', { "stats_data": {
- 'bind10.boot_time': time.strftime('%Y-%m-%dT%H:%M:%SZ', _BASETIME)
- }})
+ 'set', self._get_stats_data())
seq = self.cc_session.group_sendmsg(cmd, 'Stats')
- self.cc_session.group_recvmsg(True, seq)
+ # Consume the answer, in case it becomes a orphan message.
+ try:
+ self.cc_session.group_recvmsg(False, seq)
+ except isc.cc.session.SessionTimeout:
+ pass
answer = isc.config.ccsession.create_answer(0)
elif command == "ping":
answer = isc.config.ccsession.create_answer(0, "pong")
diff --git a/src/bin/bind10/run_bind10.sh.in b/src/bin/bind10/run_bind10.sh.in
index b5b9721..30e7322 100755
--- a/src/bin/bind10/run_bind10.sh.in
+++ b/src/bin/bind10/run_bind10.sh.in
@@ -30,7 +30,7 @@ export PYTHONPATH
# required by loadable python modules.
SET_ENV_LIBRARY_PATH=@SET_ENV_LIBRARY_PATH@
if test $SET_ENV_LIBRARY_PATH = yes; then
- @ENV_LIBRARY_PATH@=@abs_top_builddir@/src/lib/dns/.libs:@abs_top_builddir@/src/lib/cryptolink/.libs:@abs_top_builddir@/src/lib/cc/.libs:@abs_top_builddir@/src/lib/config/.libs:@abs_top_builddir@/src/lib/log/.libs:@abs_top_builddir@/src/lib/acl/.libs:@abs_top_builddir@/src/lib/util/.libs:@abs_top_builddir@/src/lib/util/io/.libs:@abs_top_builddir@/src/lib/exceptions/.libs:$@ENV_LIBRARY_PATH@
+ @ENV_LIBRARY_PATH@=@abs_top_builddir@/src/lib/dns/.libs:@abs_top_builddir@/src/lib/dns/python/.libs:@abs_top_builddir@/src/lib/cryptolink/.libs:@abs_top_builddir@/src/lib/cc/.libs:@abs_top_builddir@/src/lib/config/.libs:@abs_top_builddir@/src/lib/log/.libs:@abs_top_builddir@/src/lib/acl/.libs:@abs_top_builddir@/src/lib/util/.libs:@abs_top_builddir@/src/lib/util/io/.libs:@abs_top_builddir@/src/lib/exceptions/.libs:$@ENV_LIBRARY_PATH@
export @ENV_LIBRARY_PATH@
fi
diff --git a/src/bin/bind10/tests/Makefile.am b/src/bin/bind10/tests/Makefile.am
index d9e012f..f388ba1 100644
--- a/src/bin/bind10/tests/Makefile.am
+++ b/src/bin/bind10/tests/Makefile.am
@@ -2,6 +2,7 @@ PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
#PYTESTS = args_test.py bind10_test.py
# NOTE: this has a generated test found in the builddir
PYTESTS = bind10_test.py
+noinst_SCRIPTS = $(PYTESTS)
# If necessary (rare cases), explicitly specify paths to dynamic libraries
# required by loadable python modules.
@@ -19,6 +20,7 @@ if ENABLE_PYTHON_COVERAGE
endif
for pytest in $(PYTESTS) ; do \
echo Running test: $$pytest ; \
+ chmod +x $(abs_builddir)/$$pytest ; \
$(LIBRARY_PATH_PLACEHOLDER) \
env PYTHONPATH=$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_srcdir)/src/bin:$(abs_top_builddir)/src/bin/bind10:$(abs_top_builddir)/src/lib/util/io/.libs \
BIND10_MSGQ_SOCKET_FILE=$(abs_top_builddir)/msgq_socket \
diff --git a/src/bin/bind10/tests/bind10_test.py.in b/src/bin/bind10/tests/bind10_test.py.in
index 077190c..424a610 100644
--- a/src/bin/bind10/tests/bind10_test.py.in
+++ b/src/bin/bind10/tests/bind10_test.py.in
@@ -147,6 +147,12 @@ class TestBoB(unittest.TestCase):
self.assertEqual(bob.command_handler("shutdown", None),
isc.config.ccsession.create_answer(0))
self.assertFalse(bob.runnable)
+ # "getstats" command
+ self.assertEqual(bob.command_handler("getstats", None),
+ isc.config.ccsession.create_answer(0,
+ { "stats_data": {
+ 'bind10.boot_time': time.strftime('%Y-%m-%dT%H:%M:%SZ', _BASETIME)
+ }}))
# "sendstats" command
self.assertEqual(bob.command_handler("sendstats", None),
isc.config.ccsession.create_answer(0))
diff --git a/src/bin/cfgmgr/plugins/tests/Makefile.am b/src/bin/cfgmgr/plugins/tests/Makefile.am
index 07b7a85..bda775b 100644
--- a/src/bin/cfgmgr/plugins/tests/Makefile.am
+++ b/src/bin/cfgmgr/plugins/tests/Makefile.am
@@ -7,7 +7,7 @@ EXTRA_DIST = $(PYTESTS)
# required by loadable python modules.
LIBRARY_PATH_PLACEHOLDER =
if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs
endif
# test using command-line arguments, so use check-local target instead of TESTS
diff --git a/src/bin/cfgmgr/tests/Makefile.am b/src/bin/cfgmgr/tests/Makefile.am
index bd67241..99f8cc9 100644
--- a/src/bin/cfgmgr/tests/Makefile.am
+++ b/src/bin/cfgmgr/tests/Makefile.am
@@ -1,7 +1,8 @@
PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
PYTESTS = b10-cfgmgr_test.py
-EXTRA_DIST = $(PYTESTS) testdata/plugins/testplugin.py
+noinst_SCRIPTS = $(PYTESTS)
+EXTRA_DIST = testdata/plugins/testplugin.py
# If necessary (rare cases), explicitly specify paths to dynamic libraries
# required by loadable python modules.
@@ -19,6 +20,7 @@ if ENABLE_PYTHON_COVERAGE
endif
for pytest in $(PYTESTS) ; do \
echo Running test: $$pytest ; \
+ chmod +x $(abs_builddir)/$$pytest ; \
env TESTDATA_PATH=$(abs_srcdir)/testdata \
$(LIBRARY_PATH_PLACEHOLDER) \
env PYTHONPATH=$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/bin/cfgmgr:$(abs_top_builddir)/src/lib/python/isc/config \
diff --git a/src/bin/stats/b10-stats.8 b/src/bin/stats/b10-stats.8
index b88af6c..98b109b 100644
--- a/src/bin/stats/b10-stats.8
+++ b/src/bin/stats/b10-stats.8
@@ -36,7 +36,7 @@ with other modules like
\fBb10\-auth\fR
and so on\&. It waits for coming data from other modules, then other modules send data to stats module periodically\&. Other modules send stats data to stats module independently from implementation of stats module, so the frequency of sending data may not be constant\&. Stats module collects data and aggregates it\&.
\fBb10\-stats\fR
-invokes "sendstats" command for
+invokes an internal command for
\fBbind10\fR
after its initial starting because it\'s sure to collect statistics data from
\fBbind10\fR\&.
diff --git a/src/bin/stats/b10-stats.xml b/src/bin/stats/b10-stats.xml
index 1164711..9709175 100644
--- a/src/bin/stats/b10-stats.xml
+++ b/src/bin/stats/b10-stats.xml
@@ -64,7 +64,7 @@
send stats data to stats module independently from
implementation of stats module, so the frequency of sending data
may not be constant. Stats module collects data and aggregates
- it. <command>b10-stats</command> invokes "sendstats" command
+ it. <command>b10-stats</command> invokes an internal command
for <command>bind10</command> after its initial starting because it's
sure to collect statistics data from <command>bind10</command>.
<!-- TODO: reword that last sentence? -->
diff --git a/src/bin/stats/stats.py.in b/src/bin/stats/stats.py.in
index ce3d9f4..51d712b 100644
--- a/src/bin/stats/stats.py.in
+++ b/src/bin/stats/stats.py.in
@@ -213,6 +213,14 @@ class CCSessionListener(Listener):
except AttributeError as ae:
logger.error(STATS_UNKNOWN_COMMAND_IN_SPEC, cmd["command_name"])
+ def _update_stats_data(self, args):
+ # 'args' must be dictionary type
+ if isinstance(args, dict) and isinstance(args.get('stats_data'), dict):
+ self.stats_data.update(args['stats_data'])
+
+ # overwrite "stats.LastUpdateTime"
+ self.stats_data['stats.last_update_time'] = get_datetime()
+
def start(self):
"""
start the cc chanel
@@ -225,9 +233,16 @@ class CCSessionListener(Listener):
self.cc_session.start()
# request Bob to send statistics data
logger.debug(DBG_STATS_MESSAGING, STATS_SEND_REQUEST_BOSS)
- cmd = isc.config.ccsession.create_command("sendstats", None)
+ cmd = isc.config.ccsession.create_command("getstats", None)
seq = self.session.group_sendmsg(cmd, 'Boss')
- self.session.group_recvmsg(True, seq)
+ try:
+ answer, env = self.session.group_recvmsg(False, seq)
+ if answer:
+ rcode, arg = isc.config.ccsession.parse_answer(answer)
+ if rcode == 0:
+ self._update_stats_data(arg)
+ except isc.cc.session.SessionTimeout:
+ pass
def stop(self):
"""
@@ -276,12 +291,7 @@ class CCSessionListener(Listener):
"""
handle set command
"""
- # 'args' must be dictionary type
- self.stats_data.update(args['stats_data'])
-
- # overwrite "stats.LastUpdateTime"
- self.stats_data['stats.last_update_time'] = get_datetime()
-
+ self._update_stats_data(args)
return create_answer(0)
def command_remove(self, args, stats_item_name=''):
diff --git a/src/bin/stats/tests/b10-stats_test.py b/src/bin/stats/tests/b10-stats_test.py
index a42c81d..2fb4ab5 100644
--- a/src/bin/stats/tests/b10-stats_test.py
+++ b/src/bin/stats/tests/b10-stats_test.py
@@ -59,6 +59,7 @@ class TestStats(unittest.TestCase):
# check starting
self.assertFalse(self.subject.running)
self.subject.start()
+ self.assertEqual(len(self.session.old_message_queue), 1)
self.assertTrue(self.subject.running)
self.assertEqual(len(self.session.message_queue), 0)
self.assertEqual(self.module_name, 'Stats')
@@ -509,7 +510,7 @@ class TestStats(unittest.TestCase):
def test_for_boss(self):
last_queue = self.session.old_message_queue.pop()
self.assertEqual(
- last_queue.msg, {'command': ['sendstats']})
+ last_queue.msg, {'command': ['getstats']})
self.assertEqual(
last_queue.env['group'], 'Boss')
diff --git a/src/bin/stats/tests/isc/cc/session.py b/src/bin/stats/tests/isc/cc/session.py
index e16d6a9..e18a695 100644
--- a/src/bin/stats/tests/isc/cc/session.py
+++ b/src/bin/stats/tests/isc/cc/session.py
@@ -115,8 +115,16 @@ class Session:
def group_recvmsg(self, nonblock=True, seq=0):
que = self.dequeue()
+ if que.msg != None:
+ cmd = que.msg.get("command")
+ if cmd and cmd[0] == 'getstats':
+ # Create answer for command 'getstats'
+ retdata = { "stats_data": {
+ 'bind10.boot_time' : "1970-01-01T00:00:00Z"
+ }}
+ return {'result': [0, retdata]}, que.env
return que.msg, que.env
-
+
def group_reply(self, routing, msg):
return self.enqueue(msg=msg, env={
"type": "send",
diff --git a/src/bin/tests/Makefile.am b/src/bin/tests/Makefile.am
index 56ff68b..034152c 100644
--- a/src/bin/tests/Makefile.am
+++ b/src/bin/tests/Makefile.am
@@ -20,6 +20,7 @@ if ENABLE_PYTHON_COVERAGE
endif
for pytest in $(PYTESTS) ; do \
echo Running test: $$pytest ; \
+ chmod +x $(abs_builddir)/$$pytest ; \
$(LIBRARY_PATH_PLACEHOLDER) \
env PYTHONPATH=$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/lib/dns/python/.libs \
$(PYCOVERAGE_RUN) $(abs_builddir)/$$pytest || exit ; \
diff --git a/src/bin/xfrin/b10-xfrin.8 b/src/bin/xfrin/b10-xfrin.8
index 7f73213..54dbe7c 100644
--- a/src/bin/xfrin/b10-xfrin.8
+++ b/src/bin/xfrin/b10-xfrin.8
@@ -2,12 +2,12 @@
.\" Title: b10-xfrin
.\" Author: [FIXME: author] [see http://docbook.sf.net/el/author]
.\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
-.\" Date: May 19, 2011
+.\" Date: September 8, 2011
.\" Manual: BIND10
.\" Source: BIND10
.\" Language: English
.\"
-.TH "B10\-XFRIN" "8" "May 19, 2011" "BIND10" "BIND10"
+.TH "B10\-XFRIN" "8" "September 8, 2011" "BIND10" "BIND10"
.\" -----------------------------------------------------------------
.\" * set default formatting
.\" -----------------------------------------------------------------
@@ -61,7 +61,7 @@ receives its configurations from
.PP
The configurable settings are:
.PP
-\fItransfers\-in\fR
+\fItransfers_in\fR
defines the maximum number of inbound zone transfers that can run concurrently\&. The default is 10\&.
.PP
diff --git a/src/bin/xfrin/b10-xfrin.xml b/src/bin/xfrin/b10-xfrin.xml
index 17840fe..d45e15f 100644
--- a/src/bin/xfrin/b10-xfrin.xml
+++ b/src/bin/xfrin/b10-xfrin.xml
@@ -20,7 +20,7 @@
<refentry>
<refentryinfo>
- <date>May 19, 2011</date>
+ <date>September 8, 2011</date>
</refentryinfo>
<refmeta>
@@ -92,7 +92,7 @@ in separate zonemgr process.
The configurable settings are:
</para>
- <para><varname>transfers-in</varname>
+ <para><varname>transfers_in</varname>
defines the maximum number of inbound zone transfers
that can run concurrently. The default is 10.
</para>
diff --git a/src/bin/xfrin/tests/Makefile.am b/src/bin/xfrin/tests/Makefile.am
index 0f485aa..e8319d5 100644
--- a/src/bin/xfrin/tests/Makefile.am
+++ b/src/bin/xfrin/tests/Makefile.am
@@ -6,7 +6,7 @@ EXTRA_DIST = $(PYTESTS)
# required by loadable python modules.
LIBRARY_PATH_PLACEHOLDER =
if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/xfr/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/xfr/.libs:$$$(ENV_LIBRARY_PATH)
endif
# test using command-line arguments, so use check-local target instead of TESTS
diff --git a/src/bin/xfrout/tests/Makefile.am b/src/bin/xfrout/tests/Makefile.am
index 99f4843..1b99e37 100644
--- a/src/bin/xfrout/tests/Makefile.am
+++ b/src/bin/xfrout/tests/Makefile.am
@@ -1,12 +1,12 @@
PYCOVERAGE_RUN=@PYCOVERAGE_RUN@
PYTESTS = xfrout_test.py
-EXTRA_DIST = $(PYTESTS)
+noinst_SCRIPTS = $(PYTESTS)
# If necessary (rare cases), explicitly specify paths to dynamic libraries
# required by loadable python modules.
LIBRARY_PATH_PLACEHOLDER =
if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/acl/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/acl/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$$$(ENV_LIBRARY_PATH)
endif
# test using command-line arguments, so use check-local target instead of TESTS
@@ -18,6 +18,7 @@ if ENABLE_PYTHON_COVERAGE
endif
for pytest in $(PYTESTS) ; do \
echo Running test: $$pytest ; \
+ chmod +x $(abs_builddir)/$$pytest ; \
$(LIBRARY_PATH_PLACEHOLDER) \
env PYTHONPATH=$(abs_top_builddir)/src/bin/xfrout:$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/util/io/.libs \
$(PYCOVERAGE_RUN) $(abs_builddir)/$$pytest || exit ; \
diff --git a/src/bin/zonemgr/tests/zonemgr_test.py b/src/bin/zonemgr/tests/zonemgr_test.py
index 496ce6b..80e41b3 100644
--- a/src/bin/zonemgr/tests/zonemgr_test.py
+++ b/src/bin/zonemgr/tests/zonemgr_test.py
@@ -152,6 +152,16 @@ class TestZonemgrRefresh(unittest.TestCase):
self.assertTrue((time1 + 3600 * (1 - self.zone_refresh._refresh_jitter)) <= zone_timeout)
self.assertTrue(zone_timeout <= time2 + 3600)
+ # No soa rdata
+ self.zone_refresh._zonemgr_refresh_info[ZONE_NAME_CLASS1_IN]["zone_soa_rdata"] = None
+ time3 = time.time()
+ self.zone_refresh._set_zone_retry_timer(ZONE_NAME_CLASS1_IN)
+ zone_timeout = self.zone_refresh._zonemgr_refresh_info[ZONE_NAME_CLASS1_IN]["next_refresh_time"]
+ time4 = time.time()
+ self.assertTrue((time3 + self.zone_refresh._lowerbound_retry * (1 - self.zone_refresh._refresh_jitter))
+ <= zone_timeout)
+ self.assertTrue(zone_timeout <= time4 + self.zone_refresh._lowerbound_retry)
+
def test_zone_not_exist(self):
self.assertFalse(self.zone_refresh._zone_not_exist(ZONE_NAME_CLASS1_IN))
self.assertTrue(self.zone_refresh._zone_not_exist(ZONE_NAME_CLASS1_CH))
@@ -304,8 +314,8 @@ class TestZonemgrRefresh(unittest.TestCase):
def get_zone_soa2(zone_name, db_file):
return None
sqlite3_ds.get_zone_soa = get_zone_soa2
- self.assertRaises(ZonemgrException, self.zone_refresh.zonemgr_add_zone, \
- ZONE_NAME_CLASS1_IN)
+ 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
def test_zone_handle_notify(self):
@@ -362,6 +372,15 @@ 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):
+ return None
+ sqlite3_ds.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
+
def test_find_need_do_refresh_zone(self):
time1 = time.time()
self.zone_refresh._zonemgr_refresh_info = {
@@ -440,6 +459,8 @@ class TestZonemgrRefresh(unittest.TestCase):
"class": "IN" } ]
}
self.zone_refresh.update_config_data(config_data)
+ self.assertTrue(("example.net.", "IN") in
+ self.zone_refresh._zonemgr_refresh_info)
# update all values
config_data = {
@@ -479,14 +500,16 @@ class TestZonemgrRefresh(unittest.TestCase):
"secondary_zones": [ { "name": "doesnotexist",
"class": "IN" } ]
}
- self.assertRaises(ZonemgrException,
- self.zone_refresh.update_config_data,
- config_data)
- self.assertEqual(60, self.zone_refresh._lowerbound_refresh)
- self.assertEqual(30, self.zone_refresh._lowerbound_retry)
- self.assertEqual(19800, self.zone_refresh._max_transfer_timeout)
- self.assertEqual(0.25, self.zone_refresh._refresh_jitter)
- self.assertEqual(0.35, self.zone_refresh._reload_jitter)
+ self.zone_refresh.update_config_data(config_data)
+ name_class = ("doesnotexist.", "IN")
+ self.assertTrue(self.zone_refresh._zonemgr_refresh_info[name_class]["zone_soa_rdata"]
+ is None)
+ # The other configs should be updated successfully
+ self.assertEqual(61, self.zone_refresh._lowerbound_refresh)
+ self.assertEqual(31, self.zone_refresh._lowerbound_retry)
+ self.assertEqual(19801, self.zone_refresh._max_transfer_timeout)
+ self.assertEqual(0.21, self.zone_refresh._refresh_jitter)
+ self.assertEqual(0.71, self.zone_refresh._reload_jitter)
# Make sure we accept 0 as a value
config_data = {
@@ -526,10 +549,11 @@ class TestZonemgrRefresh(unittest.TestCase):
self.zone_refresh._zonemgr_refresh_info)
# This one does not exist
config.set_zone_list_from_name_classes(["example.net", "CH"])
- self.assertRaises(ZonemgrException,
- self.zone_refresh.update_config_data, config)
- # So it should not affect the old ones
- self.assertTrue(("example.net.", "IN") in
+ self.zone_refresh.update_config_data(config)
+ self.assertFalse(("example.net.", "CH") in
+ self.zone_refresh._zonemgr_refresh_info)
+ # Simply skip loading soa for the zone, the other configs should be updated successful
+ self.assertFalse(("example.net.", "IN") in
self.zone_refresh._zonemgr_refresh_info)
# Make sure it works even when we "accidentally" forget the final dot
config.set_zone_list_from_name_classes([("example.net", "IN")])
@@ -596,15 +620,18 @@ 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, it should be rejected
+ # The zone doesn't exist in database, simply skip loading soa for it and log an warning
self.zonemgr._zone_refresh = ZonemgrRefresh(None, "initdb.file", None,
config_data1)
config_data1["secondary_zones"] = [{"name": "nonexistent.example",
"class": "IN"}]
- self.assertNotEqual(self.zonemgr.config_handler(config_data1),
- {"result": [0]})
- # As it is rejected, the old value should be kept
- self.assertEqual(0.5, self.zonemgr._config_data.get("refresh_jitter"))
+ self.assertEqual(self.zonemgr.config_handler(config_data1),
+ {"result": [0]})
+ # other configs should be updated successfully
+ name_class = ("nonexistent.example.", "IN")
+ self.assertTrue(self.zonemgr._zone_refresh._zonemgr_refresh_info[name_class]["zone_soa_rdata"]
+ is None)
+ self.assertEqual(0.1, self.zonemgr._config_data.get("refresh_jitter"))
def test_get_db_file(self):
self.assertEqual("initdb.file", self.zonemgr.get_db_file())
diff --git a/src/bin/zonemgr/zonemgr.py.in b/src/bin/zonemgr/zonemgr.py.in
index 87a0092..d4de6a8 100755
--- a/src/bin/zonemgr/zonemgr.py.in
+++ b/src/bin/zonemgr/zonemgr.py.in
@@ -142,7 +142,10 @@ class ZonemgrRefresh:
"""Set zone next refresh time after zone refresh fail.
now + retry - retry_jitter <= next_refresh_time <= now + retry
"""
- 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)
@@ -174,7 +177,8 @@ class ZonemgrRefresh:
raise ZonemgrException("[b10-zonemgr] Zone (%s, %s) doesn't "
"belong to zonemgr" % zone_name_class)
# Is zone expired?
- if (self._zone_is_expired(zone_name_class)):
+ if ((self._get_zone_soa_rdata(zone_name_class) is None) or
+ self._zone_is_expired(zone_name_class)):
self._set_zone_state(zone_name_class, ZONE_EXPIRED)
else:
self._set_zone_state(zone_name_class, ZONE_OK)
@@ -200,17 +204,19 @@ class ZonemgrRefresh:
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)
- if not zone_soa:
- logger.error(ZONEMGR_NO_SOA, zone_name_class[0], zone_name_class[1])
- raise ZonemgrException("[b10-zonemgr] zone (%s, %s) doesn't have soa." % zone_name_class)
- zone_info["zone_soa_rdata"] = zone_soa[7]
+ 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_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.
- zone_reload_jitter = float(zone_soa[7].split(" ")[RETRY_OFFSET])
- zone_reload_jitter = max(self._lowerbound_retry, zone_reload_jitter)
- self._set_zone_timer(zone_name_class, zone_reload_jitter, self._reload_jitter * zone_reload_jitter)
+ 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)
def _zone_is_expired(self, zone_name_class):
"""Judge whether a zone is expired or not."""
@@ -420,12 +426,6 @@ class ZonemgrRefresh:
def update_config_data(self, new_config):
""" update ZonemgrRefresh config """
- # TODO: we probably want to store all this info in a nice
- # class, so that we don't have to backup and restore every
- # single value.
- # TODO2: We also don't use get_default_value yet
- backup = self._zonemgr_refresh_info.copy()
-
# 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
# 0, we would take default
@@ -435,26 +435,21 @@ class ZonemgrRefresh:
else:
return default
- # store the values so we can restore them if there is a problem
- lowerbound_refresh_backup = self._lowerbound_refresh
self._lowerbound_refresh = val_or_default(
new_config.get('lowerbound_refresh'), self._lowerbound_refresh)
- lowerbound_retry_backup = self._lowerbound_retry
self._lowerbound_retry = val_or_default(
new_config.get('lowerbound_retry'), self._lowerbound_retry)
- max_transfer_timeout_backup = self._max_transfer_timeout
self._max_transfer_timeout = val_or_default(
new_config.get('max_transfer_timeout'), self._max_transfer_timeout)
- refresh_jitter_backup = self._refresh_jitter
self._refresh_jitter = val_or_default(
new_config.get('refresh_jitter'), self._refresh_jitter)
- reload_jitter_backup = self._reload_jitter
self._reload_jitter = val_or_default(
new_config.get('reload_jitter'), self._reload_jitter)
+
try:
required = {}
secondary_zones = new_config.get('secondary_zones')
@@ -469,6 +464,7 @@ 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
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
@@ -478,14 +474,7 @@ class ZonemgrRefresh:
to_drop.append(old_zone)
for drop in to_drop:
del self._zonemgr_refresh_info[drop]
- # If we are not able to find it in database, restore the original
except:
- self._zonemgr_refresh_info = backup
- self._lowerbound_refresh = lowerbound_refresh_backup
- self._lowerbound_retry = lowerbound_retry_backup
- self._max_transfer_timeout = max_transfer_timeout_backup
- self._refresh_jitter = refresh_jitter_backup
- self._reload_jitter = reload_jitter_backup
raise
class Zonemgr:
diff --git a/src/lib/cache/cache_messages.mes b/src/lib/cache/cache_messages.mes
index 7f593ec..19102ae 100644
--- a/src/lib/cache/cache_messages.mes
+++ b/src/lib/cache/cache_messages.mes
@@ -131,7 +131,7 @@ class is being created.
% CACHE_RRSET_LOOKUP looking up %1/%2/%3 in RRset cache
Debug message. The resolver is trying to look up data in the RRset cache.
-% CACHE_RRSET_NOT_FOUND no RRset found for %1/%2/%3
+% CACHE_RRSET_NOT_FOUND no RRset found for %1/%2/%3 in cache
Debug message which can follow CACHE_RRSET_LOOKUP. This means the data is not
in the cache.
diff --git a/src/lib/datasrc/client.h b/src/lib/datasrc/client.h
index c43092d..6a7ae04 100644
--- a/src/lib/datasrc/client.h
+++ b/src/lib/datasrc/client.h
@@ -80,8 +80,8 @@ typedef boost::shared_ptr<ZoneIterator> ZoneIteratorPtr;
/// disruption with a naive copy it's prohibited explicitly. For the expected
/// usage of the client classes the restriction should be acceptable.
///
-/// \todo This class is not complete. It needs more factory methods, for
-/// accessing the whole zone, updating it, loading it, etc.
+/// \todo This class is still not complete. It will need more factory methods,
+/// e.g. for (re)loading a zone.
class DataSourceClient : boost::noncopyable {
public:
/// \brief A helper structure to represent the search result of
@@ -180,6 +180,65 @@ public:
isc_throw(isc::NotImplemented,
"Data source doesn't support iteration");
}
+
+ /// Return an updater to make updates to a specific zone.
+ ///
+ /// The RR class of the zone is the one that the client is expected to
+ /// handle (see the detailed description of this class).
+ ///
+ /// If the specified zone is not found via the client, a NULL pointer
+ /// will be returned; in other words a completely new zone cannot be
+ /// created using an updater. It must be created beforehand (even if
+ /// it's an empty placeholder) in a way specific to the underlying data
+ /// source.
+ ///
+ /// Conceptually, the updater will trigger a separate transaction for
+ /// subsequent updates to the zone within the context of the updater
+ /// (the actual implementation of the "transaction" may vary for the
+ /// specific underlying data source). Until \c commit() is performed
+ /// on the updater, the intermediate updates won't affect the results
+ /// of other methods (and the result of the object's methods created
+ /// by other factory methods). Likewise, if the updater is destructed
+ /// without performing \c commit(), the intermediate updates will be
+ /// effectively canceled and will never affect other methods.
+ ///
+ /// If the underlying data source allows concurrent updates, this method
+ /// can be called multiple times while the previously returned updater(s)
+ /// are still active. In this case each updater triggers a different
+ /// "transaction". Normally it would be for different zones for such a
+ /// case as handling multiple incoming AXFR streams concurrently, but
+ /// this interface does not even prohibit an attempt of getting more than
+ /// one updater for the same zone, as long as the underlying data source
+ /// allows such an operation (and any conflict resolution is left to the
+ /// specific derived class implementation).
+ ///
+ /// If \c replace is true, any existing RRs of the zone will be
+ /// deleted on successful completion of updates (after \c commit() on
+ /// the updater); if it's false, the existing RRs will be
+ /// intact unless explicitly deleted by \c deleteRRset() on the updater.
+ ///
+ /// A data source can be "read only" or can prohibit partial updates.
+ /// In such cases this method will result in an \c isc::NotImplemented
+ /// exception unconditionally or when \c replace is false).
+ ///
+ /// \note To avoid throwing the exception accidentally with a lazy
+ /// implementation, we still keep this method pure virtual without
+ /// an implementation. All derived classes must explicitly define this
+ /// method, even if it simply throws the NotImplemented exception.
+ ///
+ /// \exception NotImplemented The underlying data source does not support
+ /// updates.
+ /// \exception DataSourceError Internal error in the underlying data
+ /// source.
+ /// \exception std::bad_alloc Resource allocation failure.
+ ///
+ /// \param name The zone name to be updated
+ /// \param replace Whether to delete existing RRs before making updates
+ ///
+ /// \return A pointer to the updater; it will be NULL if the specified
+ /// zone isn't found.
+ virtual ZoneUpdaterPtr getUpdater(const isc::dns::Name& name,
+ bool replace) const = 0;
};
}
}
diff --git a/src/lib/datasrc/database.cc b/src/lib/datasrc/database.cc
index f0d9767..2c5aaeb 100644
--- a/src/lib/datasrc/database.cc
+++ b/src/lib/datasrc/database.cc
@@ -12,6 +12,7 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
+#include <string>
#include <vector>
#include <datasrc/database.h>
@@ -21,6 +22,8 @@
#include <exceptions/exceptions.h>
#include <dns/name.h>
#include <dns/rrclass.h>
+#include <dns/rrttl.h>
+#include <dns/rrset.h>
#include <dns/rdata.h>
#include <dns/rdataclass.h>
@@ -29,19 +32,20 @@
#include <boost/foreach.hpp>
-#include <string>
-
using namespace isc::dns;
-using std::string;
+using namespace std;
+using boost::shared_ptr;
+using namespace isc::dns::rdata;
namespace isc {
namespace datasrc {
-DatabaseClient::DatabaseClient(boost::shared_ptr<DatabaseAccessor>
- database) :
- database_(database)
+DatabaseClient::DatabaseClient(RRClass rrclass,
+ boost::shared_ptr<DatabaseAccessor>
+ accessor) :
+ rrclass_(rrclass), accessor_(accessor)
{
- if (database_.get() == NULL) {
+ if (!accessor_) {
isc_throw(isc::InvalidParameter,
"No database provided to DatabaseClient");
}
@@ -49,21 +53,21 @@ DatabaseClient::DatabaseClient(boost::shared_ptr<DatabaseAccessor>
DataSourceClient::FindResult
DatabaseClient::findZone(const Name& name) const {
- std::pair<bool, int> zone(database_->getZone(name));
+ std::pair<bool, int> zone(accessor_->getZone(name.toText()));
// Try exact first
if (zone.first) {
return (FindResult(result::SUCCESS,
- ZoneFinderPtr(new Finder(database_,
+ ZoneFinderPtr(new Finder(accessor_,
zone.second, name))));
}
// Then super domains
// Start from 1, as 0 is covered above
for (size_t i(1); i < name.getLabelCount(); ++i) {
isc::dns::Name superdomain(name.split(i));
- zone = database_->getZone(superdomain);
+ zone = accessor_->getZone(superdomain.toText());
if (zone.first) {
return (FindResult(result::PARTIALMATCH,
- ZoneFinderPtr(new Finder(database_,
+ ZoneFinderPtr(new Finder(accessor_,
zone.second,
superdomain))));
}
@@ -72,10 +76,9 @@ DatabaseClient::findZone(const Name& name) const {
return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
}
-DatabaseClient::Finder::Finder(boost::shared_ptr<DatabaseAccessor>
- database, int zone_id,
- const isc::dns::Name& origin) :
- database_(database),
+DatabaseClient::Finder::Finder(boost::shared_ptr<DatabaseAccessor> accessor,
+ int zone_id, const isc::dns::Name& origin) :
+ accessor_(accessor),
zone_id_(zone_id),
origin_(origin)
{ }
@@ -175,15 +178,27 @@ std::pair<bool, isc::dns::RRsetPtr>
DatabaseClient::Finder::getRRset(const isc::dns::Name& name,
const isc::dns::RRType* type,
bool want_cname, bool want_dname,
- bool want_ns)
+ bool want_ns,
+ const isc::dns::Name* construct_name)
{
RRsigStore sig_store;
- database_->searchForRecords(zone_id_, name.toText());
bool records_found = false;
isc::dns::RRsetPtr result_rrset;
+ // Request the context
+ DatabaseAccessor::IteratorContextPtr
+ context(accessor_->getRecords(name.toText(), zone_id_));
+ // It must not return NULL, that's a bug of the implementation
+ if (!context) {
+ isc_throw(isc::Unexpected, "Iterator context null at " +
+ name.toText());
+ }
+
std::string columns[DatabaseAccessor::COLUMN_COUNT];
- while (database_->getNextRecord(columns, DatabaseAccessor::COLUMN_COUNT)) {
+ if (construct_name == NULL) {
+ construct_name = &name;
+ }
+ while (context->getNext(columns)) {
if (!records_found) {
records_found = true;
}
@@ -217,9 +232,10 @@ DatabaseClient::Finder::getRRset(const isc::dns::Name& name,
isc_throw(DataSourceError, "NS found together with data"
" in non-apex domain " + name.toText());
}
- addOrCreate(result_rrset, name, getClass(), cur_type, cur_ttl,
+ addOrCreate(result_rrset, *construct_name, getClass(),
+ cur_type, cur_ttl,
columns[DatabaseAccessor::RDATA_COLUMN],
- *database_);
+ *accessor_);
} else if (type != NULL && cur_type == *type) {
if (result_rrset &&
result_rrset->getType() == isc::dns::RRType::CNAME()) {
@@ -230,9 +246,10 @@ DatabaseClient::Finder::getRRset(const isc::dns::Name& name,
isc_throw(DataSourceError, "NS found together with data"
" in non-apex domain " + name.toText());
}
- addOrCreate(result_rrset, name, getClass(), cur_type, cur_ttl,
+ addOrCreate(result_rrset, *construct_name, getClass(),
+ cur_type, cur_ttl,
columns[DatabaseAccessor::RDATA_COLUMN],
- *database_);
+ *accessor_);
} else if (want_cname && cur_type == isc::dns::RRType::CNAME()) {
// There should be no other data, so result_rrset should
// be empty.
@@ -240,9 +257,10 @@ DatabaseClient::Finder::getRRset(const isc::dns::Name& name,
isc_throw(DataSourceError, "CNAME found but it is not "
"the only record for " + name.toText());
}
- addOrCreate(result_rrset, name, getClass(), cur_type, cur_ttl,
+ addOrCreate(result_rrset, *construct_name, getClass(),
+ cur_type, cur_ttl,
columns[DatabaseAccessor::RDATA_COLUMN],
- *database_);
+ *accessor_);
} else if (want_dname && cur_type == isc::dns::RRType::DNAME()) {
// There should be max one RR of DNAME present
if (result_rrset &&
@@ -250,9 +268,10 @@ DatabaseClient::Finder::getRRset(const isc::dns::Name& name,
isc_throw(DataSourceError, "DNAME with multiple RRs in " +
name.toText());
}
- addOrCreate(result_rrset, name, getClass(), cur_type, cur_ttl,
+ addOrCreate(result_rrset, *construct_name, getClass(),
+ cur_type, cur_ttl,
columns[DatabaseAccessor::RDATA_COLUMN],
- *database_);
+ *accessor_);
} else if (cur_type == isc::dns::RRType::RRSIG()) {
// If we get signatures before we get the actual data, we
// can't know which ones to keep and which to drop...
@@ -284,6 +303,19 @@ DatabaseClient::Finder::getRRset(const isc::dns::Name& name,
return (std::pair<bool, isc::dns::RRsetPtr>(records_found, result_rrset));
}
+bool
+DatabaseClient::Finder::hasSubdomains(const std::string& name) {
+ // Request the context
+ DatabaseAccessor::IteratorContextPtr
+ context(accessor_->getRecords(name, zone_id_, true));
+ // It must not return NULL, that's a bug of the implementation
+ if (!context) {
+ isc_throw(isc::Unexpected, "Iterator context null at " + name);
+ }
+
+ std::string columns[DatabaseAccessor::COLUMN_COUNT];
+ return (context->getNext(columns));
+}
ZoneFinder::FindResult
DatabaseClient::Finder::find(const isc::dns::Name& name,
@@ -299,98 +331,164 @@ DatabaseClient::Finder::find(const isc::dns::Name& name,
ZoneFinder::Result result_status = SUCCESS;
std::pair<bool, isc::dns::RRsetPtr> found;
logger.debug(DBG_TRACE_DETAILED, DATASRC_DATABASE_FIND_RECORDS)
- .arg(database_->getDBName()).arg(name).arg(type);
-
- try {
- // First, do we have any kind of delegation (NS/DNAME) here?
- Name origin(getOrigin());
- size_t origin_label_count(origin.getLabelCount());
- size_t current_label_count(name.getLabelCount());
- // This is how many labels we remove to get origin
- size_t remove_labels(current_label_count - origin_label_count);
-
- // Now go trough all superdomains from origin down
- for (int i(remove_labels); i > 0; --i) {
- Name superdomain(name.split(i));
- // Look if there's NS or DNAME (but ignore the NS in origin)
- found = getRRset(superdomain, NULL, false, true,
- i != remove_labels && !glue_ok);
- if (found.second) {
- // We found something redirecting somewhere else
- // (it can be only NS or DNAME here)
- result_rrset = found.second;
- if (result_rrset->getType() == isc::dns::RRType::NS()) {
- LOG_DEBUG(logger, DBG_TRACE_DETAILED,
- DATASRC_DATABASE_FOUND_DELEGATION).
- arg(database_->getDBName()).arg(superdomain);
- result_status = DELEGATION;
- } else {
- LOG_DEBUG(logger, DBG_TRACE_DETAILED,
- DATASRC_DATABASE_FOUND_DNAME).
- arg(database_->getDBName()).arg(superdomain);
- result_status = DNAME;
- }
- // Don't search more
- break;
+ .arg(accessor_->getDBName()).arg(name).arg(type);
+ // In case we are in GLUE_OK mode and start matching wildcards,
+ // we can't do it under NS, so we store it here to check
+ isc::dns::RRsetPtr first_ns;
+
+ // First, do we have any kind of delegation (NS/DNAME) here?
+ Name origin(getOrigin());
+ size_t origin_label_count(origin.getLabelCount());
+ // Number of labels in the last known non-empty domain
+ size_t last_known(origin_label_count);
+ size_t current_label_count(name.getLabelCount());
+ // This is how many labels we remove to get origin
+ size_t remove_labels(current_label_count - origin_label_count);
+
+ // Now go trough all superdomains from origin down
+ for (int i(remove_labels); i > 0; --i) {
+ Name superdomain(name.split(i));
+ // Look if there's NS or DNAME (but ignore the NS in origin)
+ found = getRRset(superdomain, NULL, false, true,
+ i != remove_labels && !glue_ok);
+ if (found.first) {
+ // It contains some RRs, so it exists.
+ last_known = superdomain.getLabelCount();
+ // In case we are in GLUE_OK, we want to store the highest
+ // encountered RRset.
+ if (glue_ok && !first_ns && i != remove_labels) {
+ first_ns = getRRset(superdomain, NULL, false, false,
+ true).second;
}
}
-
- if (!result_rrset) { // Only if we didn't find a redirect already
- // Try getting the final result and extract it
- // It is special if there's a CNAME or NS, DNAME is ignored here
- // And we don't consider the NS in origin
- found = getRRset(name, &type, true, false,
- name != origin && !glue_ok);
- records_found = found.first;
+ if (found.second) {
+ // We found something redirecting somewhere else
+ // (it can be only NS or DNAME here)
result_rrset = found.second;
- if (result_rrset && name != origin && !glue_ok &&
- result_rrset->getType() == isc::dns::RRType::NS()) {
+ if (result_rrset->getType() == isc::dns::RRType::NS()) {
LOG_DEBUG(logger, DBG_TRACE_DETAILED,
- DATASRC_DATABASE_FOUND_DELEGATION_EXACT).
- arg(database_->getDBName()).arg(name);
+ DATASRC_DATABASE_FOUND_DELEGATION).
+ arg(accessor_->getDBName()).arg(superdomain);
result_status = DELEGATION;
- } else if (result_rrset && type != isc::dns::RRType::CNAME() &&
- result_rrset->getType() == isc::dns::RRType::CNAME()) {
- result_status = CNAME;
+ } else {
+ LOG_DEBUG(logger, DBG_TRACE_DETAILED,
+ DATASRC_DATABASE_FOUND_DNAME).
+ arg(accessor_->getDBName()).arg(superdomain);
+ result_status = DNAME;
+ }
+ // Don't search more
+ break;
+ }
+ }
+
+ if (!result_rrset) { // Only if we didn't find a redirect already
+ // Try getting the final result and extract it
+ // It is special if there's a CNAME or NS, DNAME is ignored here
+ // And we don't consider the NS in origin
+ found = getRRset(name, &type, true, false, name != origin && !glue_ok);
+ records_found = found.first;
+ result_rrset = found.second;
+ if (result_rrset && name != origin && !glue_ok &&
+ result_rrset->getType() == isc::dns::RRType::NS()) {
+ LOG_DEBUG(logger, DBG_TRACE_DETAILED,
+ DATASRC_DATABASE_FOUND_DELEGATION_EXACT).
+ arg(accessor_->getDBName()).arg(name);
+ result_status = DELEGATION;
+ } else if (result_rrset && type != isc::dns::RRType::CNAME() &&
+ result_rrset->getType() == isc::dns::RRType::CNAME()) {
+ result_status = CNAME;
+ }
+
+ if (!result_rrset && !records_found) {
+ // Nothing lives here.
+ // But check if something lives below this
+ // domain and if so, pretend something is here as well.
+ if (hasSubdomains(name.toText())) {
+ LOG_DEBUG(logger, DBG_TRACE_DETAILED,
+ DATASRC_DATABASE_FOUND_EMPTY_NONTERMINAL).
+ arg(accessor_->getDBName()).arg(name);
+ records_found = true;
+ } else {
+ // It's not empty non-terminal. So check for wildcards.
+ // We remove labels one by one and look for the wildcard there.
+ // Go up to first non-empty domain.
+
+ remove_labels = current_label_count - last_known;
+ Name star("*");
+ for (size_t i(1); i <= remove_labels; ++ i) {
+ // Construct the name with *
+ // TODO: Once the underlying DatabaseAccessor takes
+ // string, do the concatenation on strings, not
+ // Names
+ Name superdomain(name.split(i));
+ Name wildcard(star.concatenate(superdomain));
+ // TODO What do we do about DNAME here?
+ found = getRRset(wildcard, &type, true, false, true,
+ &name);
+ if (found.first) {
+ if (first_ns) {
+ // In case we are under NS, we don't
+ // wildcard-match, but return delegation
+ result_rrset = first_ns;
+ result_status = DELEGATION;
+ records_found = true;
+ // We pretend to switch to non-glue_ok mode
+ glue_ok = false;
+ LOG_DEBUG(logger, DBG_TRACE_DETAILED,
+ DATASRC_DATABASE_WILDCARD_CANCEL_NS).
+ arg(accessor_->getDBName()).arg(wildcard).
+ arg(first_ns->getName());
+ } else if (!hasSubdomains(name.split(i - 1).toText()))
+ {
+ // Nothing we added as part of the * can exist
+ // directly, as we go up only to first existing
+ // domain, but it could be empty non-terminal. In
+ // that case, we need to cancel the match.
+ records_found = true;
+ result_rrset = found.second;
+ LOG_DEBUG(logger, DBG_TRACE_DETAILED,
+ DATASRC_DATABASE_WILDCARD).
+ arg(accessor_->getDBName()).arg(wildcard).
+ arg(name);
+ } else {
+ LOG_DEBUG(logger, DBG_TRACE_DETAILED,
+ DATASRC_DATABASE_WILDCARD_CANCEL_SUB).
+ arg(accessor_->getDBName()).arg(wildcard).
+ arg(name).arg(superdomain);
+ }
+ break;
+ } else if (hasSubdomains(wildcard.toText())) {
+ // Empty non-terminal asterisk
+ records_found = true;
+ LOG_DEBUG(logger, DBG_TRACE_DETAILED,
+ DATASRC_DATABASE_WILDCARD_EMPTY).
+ arg(accessor_->getDBName()).arg(wildcard).
+ arg(name);
+ break;
+ }
+ }
}
}
- } catch (const DataSourceError& dse) {
- logger.error(DATASRC_DATABASE_FIND_ERROR)
- .arg(database_->getDBName()).arg(dse.what());
- // call cleanup and rethrow
- database_->resetSearch();
- throw;
- } catch (const isc::Exception& isce) {
- logger.error(DATASRC_DATABASE_FIND_UNCAUGHT_ISC_ERROR)
- .arg(database_->getDBName()).arg(isce.what());
- // cleanup, change it to a DataSourceError and rethrow
- database_->resetSearch();
- isc_throw(DataSourceError, isce.what());
- } catch (const std::exception& ex) {
- logger.error(DATASRC_DATABASE_FIND_UNCAUGHT_ERROR)
- .arg(database_->getDBName()).arg(ex.what());
- database_->resetSearch();
- throw;
}
if (!result_rrset) {
if (records_found) {
logger.debug(DBG_TRACE_DETAILED,
DATASRC_DATABASE_FOUND_NXRRSET)
- .arg(database_->getDBName()).arg(name)
+ .arg(accessor_->getDBName()).arg(name)
.arg(getClass()).arg(type);
result_status = NXRRSET;
} else {
logger.debug(DBG_TRACE_DETAILED,
DATASRC_DATABASE_FOUND_NXDOMAIN)
- .arg(database_->getDBName()).arg(name)
+ .arg(accessor_->getDBName()).arg(name)
.arg(getClass()).arg(type);
result_status = NXDOMAIN;
}
} else {
logger.debug(DBG_TRACE_DETAILED,
DATASRC_DATABASE_FOUND_RRSET)
- .arg(database_->getDBName()).arg(*result_rrset);
+ .arg(accessor_->getDBName()).arg(*result_rrset);
}
return (FindResult(result_status, result_rrset));
}
@@ -463,7 +561,7 @@ private:
// Load next row of data
void getData() {
string data[DatabaseAccessor::COLUMN_COUNT];
- data_ready_ = context_->getNext(data, DatabaseAccessor::COLUMN_COUNT);
+ data_ready_ = context_->getNext(data);
name_ = data[DatabaseAccessor::NAME_COLUMN];
rtype_ = data[DatabaseAccessor::TYPE_COLUMN];
ttl_ = data[DatabaseAccessor::TTL_COLUMN];
@@ -485,7 +583,7 @@ private:
ZoneIteratorPtr
DatabaseClient::getIterator(const isc::dns::Name& name) const {
// Get the zone
- std::pair<bool, int> zone(database_->getZone(name));
+ std::pair<bool, int> zone(accessor_->getZone(name.toText()));
if (!zone.first) {
// No such zone, can't continue
isc_throw(DataSourceError, "Zone " + name.toText() +
@@ -494,7 +592,7 @@ DatabaseClient::getIterator(const isc::dns::Name& name) const {
}
// Request the context
DatabaseAccessor::IteratorContextPtr
- context(database_->getAllRecords(name, zone.second));
+ context(accessor_->getAllRecords(zone.second));
// It must not return NULL, that's a bug of the implementation
if (context == DatabaseAccessor::IteratorContextPtr()) {
isc_throw(isc::Unexpected, "Iterator context null at " +
@@ -510,5 +608,170 @@ DatabaseClient::getIterator(const isc::dns::Name& name) const {
return (ZoneIteratorPtr(new DatabaseIterator(context, RRClass::IN())));
}
+//
+// Zone updater using some database system as the underlying data source.
+//
+class DatabaseUpdater : public ZoneUpdater {
+public:
+ DatabaseUpdater(shared_ptr<DatabaseAccessor> accessor, int zone_id,
+ const Name& zone_name, const RRClass& zone_class) :
+ committed_(false), accessor_(accessor), zone_id_(zone_id),
+ db_name_(accessor->getDBName()), zone_name_(zone_name.toText()),
+ zone_class_(zone_class),
+ finder_(new DatabaseClient::Finder(accessor_, zone_id_, zone_name))
+ {
+ logger.debug(DBG_TRACE_DATA, DATASRC_DATABASE_UPDATER_CREATED)
+ .arg(zone_name_).arg(zone_class_).arg(db_name_);
+ }
+
+ virtual ~DatabaseUpdater() {
+ if (!committed_) {
+ try {
+ accessor_->rollbackUpdateZone();
+ logger.info(DATASRC_DATABASE_UPDATER_ROLLBACK)
+ .arg(zone_name_).arg(zone_class_).arg(db_name_);
+ } catch (const DataSourceError& e) {
+ // We generally expect that rollback always succeeds, and
+ // it should in fact succeed in a way we execute it. But
+ // as the public API allows rollbackUpdateZone() to fail and
+ // throw, we should expect it. Obviously we cannot re-throw
+ // it. The best we can do is to log it as a critical error.
+ logger.error(DATASRC_DATABASE_UPDATER_ROLLBACKFAIL)
+ .arg(zone_name_).arg(zone_class_).arg(db_name_)
+ .arg(e.what());
+ }
+ }
+
+ logger.debug(DBG_TRACE_DATA, DATASRC_DATABASE_UPDATER_DESTROYED)
+ .arg(zone_name_).arg(zone_class_).arg(db_name_);
+ }
+
+ virtual ZoneFinder& getFinder() { return (*finder_); }
+
+ virtual void addRRset(const RRset& rrset);
+ virtual void deleteRRset(const RRset& rrset);
+ virtual void commit();
+
+private:
+ bool committed_;
+ shared_ptr<DatabaseAccessor> accessor_;
+ const int zone_id_;
+ const string db_name_;
+ const string zone_name_;
+ const RRClass zone_class_;
+ boost::scoped_ptr<DatabaseClient::Finder> finder_;
+};
+
+void
+DatabaseUpdater::addRRset(const RRset& rrset) {
+ if (committed_) {
+ isc_throw(DataSourceError, "Add attempt after commit to zone: "
+ << zone_name_ << "/" << zone_class_);
+ }
+ if (rrset.getClass() != zone_class_) {
+ isc_throw(DataSourceError, "An RRset of a different class is being "
+ << "added to " << zone_name_ << "/" << zone_class_ << ": "
+ << rrset.toText());
+ }
+ if (rrset.getRRsig()) {
+ isc_throw(DataSourceError, "An RRset with RRSIG is being added to "
+ << zone_name_ << "/" << zone_class_ << ": "
+ << rrset.toText());
+ }
+
+ RdataIteratorPtr it = rrset.getRdataIterator();
+ if (it->isLast()) {
+ isc_throw(DataSourceError, "An empty RRset is being added for "
+ << rrset.getName() << "/" << zone_class_ << "/"
+ << rrset.getType());
+ }
+
+ string columns[DatabaseAccessor::ADD_COLUMN_COUNT]; // initialized with ""
+ columns[DatabaseAccessor::ADD_NAME] = rrset.getName().toText();
+ columns[DatabaseAccessor::ADD_REV_NAME] =
+ rrset.getName().reverse().toText();
+ columns[DatabaseAccessor::ADD_TTL] = rrset.getTTL().toText();
+ columns[DatabaseAccessor::ADD_TYPE] = rrset.getType().toText();
+ for (; !it->isLast(); it->next()) {
+ if (rrset.getType() == RRType::RRSIG()) {
+ // XXX: the current interface (based on the current sqlite3
+ // data source schema) requires a separate "sigtype" column,
+ // even though it won't be used in a newer implementation.
+ // We should eventually clean up the schema design and simplify
+ // the interface, but until then we have to conform to the schema.
+ const generic::RRSIG& rrsig_rdata =
+ dynamic_cast<const generic::RRSIG&>(it->getCurrent());
+ columns[DatabaseAccessor::ADD_SIGTYPE] =
+ rrsig_rdata.typeCovered().toText();
+ }
+ columns[DatabaseAccessor::ADD_RDATA] = it->getCurrent().toText();
+ accessor_->addRecordToZone(columns);
+ }
+}
+
+void
+DatabaseUpdater::deleteRRset(const RRset& rrset) {
+ if (committed_) {
+ isc_throw(DataSourceError, "Delete attempt after commit on zone: "
+ << zone_name_ << "/" << zone_class_);
+ }
+ if (rrset.getClass() != zone_class_) {
+ isc_throw(DataSourceError, "An RRset of a different class is being "
+ << "deleted from " << zone_name_ << "/" << zone_class_
+ << ": " << rrset.toText());
+ }
+ if (rrset.getRRsig()) {
+ isc_throw(DataSourceError, "An RRset with RRSIG is being deleted from "
+ << zone_name_ << "/" << zone_class_ << ": "
+ << rrset.toText());
+ }
+
+ RdataIteratorPtr it = rrset.getRdataIterator();
+ if (it->isLast()) {
+ isc_throw(DataSourceError, "An empty RRset is being deleted for "
+ << rrset.getName() << "/" << zone_class_ << "/"
+ << rrset.getType());
+ }
+
+ string params[DatabaseAccessor::DEL_PARAM_COUNT]; // initialized with ""
+ params[DatabaseAccessor::DEL_NAME] = rrset.getName().toText();
+ params[DatabaseAccessor::DEL_TYPE] = rrset.getType().toText();
+ for (; !it->isLast(); it->next()) {
+ params[DatabaseAccessor::DEL_RDATA] = it->getCurrent().toText();
+ accessor_->deleteRecordInZone(params);
+ }
+}
+
+void
+DatabaseUpdater::commit() {
+ if (committed_) {
+ isc_throw(DataSourceError, "Duplicate commit attempt for "
+ << zone_name_ << "/" << zone_class_ << " on "
+ << db_name_);
+ }
+ accessor_->commitUpdateZone();
+ committed_ = true; // make sure the destructor won't trigger rollback
+
+ // We release the accessor immediately after commit is completed so that
+ // we don't hold the possible internal resource any longer.
+ accessor_.reset();
+
+ logger.debug(DBG_TRACE_DATA, DATASRC_DATABASE_UPDATER_COMMIT)
+ .arg(zone_name_).arg(zone_class_).arg(db_name_);
+}
+
+// The updater factory
+ZoneUpdaterPtr
+DatabaseClient::getUpdater(const isc::dns::Name& name, bool replace) const {
+ shared_ptr<DatabaseAccessor> update_accessor(accessor_->clone());
+ const std::pair<bool, int> zone(update_accessor->startUpdateZone(
+ name.toText(), replace));
+ if (!zone.first) {
+ return (ZoneUpdaterPtr());
+ }
+
+ return (ZoneUpdaterPtr(new DatabaseUpdater(update_accessor, zone.second,
+ name, rrclass_)));
+}
}
}
diff --git a/src/lib/datasrc/database.h b/src/lib/datasrc/database.h
index b0f2b09..82918ac 100644
--- a/src/lib/datasrc/database.h
+++ b/src/lib/datasrc/database.h
@@ -15,6 +15,14 @@
#ifndef __DATABASE_DATASRC_H
#define __DATABASE_DATASRC_H
+#include <string>
+
+#include <boost/scoped_ptr.hpp>
+
+#include <dns/rrclass.h>
+#include <dns/rrclass.h>
+#include <dns/rrset.h>
+
#include <datasrc/client.h>
#include <dns/name.h>
@@ -49,12 +57,67 @@ namespace datasrc {
class DatabaseAccessor : boost::noncopyable {
public:
/**
+ * Definitions of the fields as they are required to be filled in
+ * by IteratorContext::getNext()
+ *
+ * When implementing getNext(), the columns array should
+ * be filled with the values as described in this enumeration,
+ * in this order, i.e. TYPE_COLUMN should be the first element
+ * (index 0) of the array, TTL_COLUMN should be the second element
+ * (index 1), etc.
+ */
+ enum RecordColumns {
+ TYPE_COLUMN = 0, ///< The RRType of the record (A/NS/TXT etc.)
+ TTL_COLUMN = 1, ///< The TTL of the record (a
+ SIGTYPE_COLUMN = 2, ///< For RRSIG records, this contains the RRTYPE
+ ///< the RRSIG covers. In the current implementation,
+ ///< this field is ignored.
+ RDATA_COLUMN = 3, ///< Full text representation of the record's RDATA
+ NAME_COLUMN = 4, ///< The domain name of this RR
+ COLUMN_COUNT = 5 ///< The total number of columns, MUST be value of
+ ///< the largest other element in this enum plus 1.
+ };
+
+ /**
+ * Definitions of the fields to be passed to addRecordToZone().
+ *
+ * Each derived implementation of addRecordToZone() should expect
+ * the "columns" vector to be filled with the values as described in this
+ * enumeration, in this order.
+ */
+ enum AddRecordColumns {
+ ADD_NAME = 0, ///< The owner name of the record (a domain name)
+ ADD_REV_NAME = 1, ///< Reversed name of NAME (used for DNSSEC)
+ ADD_TTL = 2, ///< The TTL of the record (in numeric form)
+ ADD_TYPE = 3, ///< The RRType of the record (A/NS/TXT etc.)
+ ADD_SIGTYPE = 4, ///< For RRSIG records, this contains the RRTYPE
+ ///< the RRSIG covers.
+ ADD_RDATA = 5, ///< Full text representation of the record's RDATA
+ ADD_COLUMN_COUNT = 6 ///< Number of columns
+ };
+
+ /**
+ * Definitions of the fields to be passed to deleteRecordInZone().
+ *
+ * Each derived implementation of deleteRecordInZone() should expect
+ * the "params" vector to be filled with the values as described in this
+ * enumeration, in this order.
+ */
+ enum DeleteRecordParams {
+ DEL_NAME = 0, ///< The owner name of the record (a domain name)
+ DEL_TYPE = 1, ///< The RRType of the record (A/NS/TXT etc.)
+ DEL_RDATA = 2, ///< Full text representation of the record's RDATA
+ DEL_PARAM_COUNT = 3 ///< Number of parameters
+ };
+
+ /**
* \brief Destructor
*
* It is empty, but needs a virtual one, since we will use the derived
* classes in polymorphic way.
*/
virtual ~DatabaseAccessor() { }
+
/**
* \brief Retrieve a zone identifier
*
@@ -66,7 +129,8 @@ public:
* It is not specified if and what implementation of this method may throw,
* so code should expect anything.
*
- * \param name The name of the zone's apex to be looked up.
+ * \param name The (fully qualified) domain name of the zone's apex to be
+ * looked up.
* \return The first part of the result indicates if a matching zone
* was found. In case it was, the second part is internal zone ID.
* This one will be passed to methods finding data in the zone.
@@ -74,7 +138,7 @@ public:
* be returned - the ID is only passed back to the database as
* an opaque handle.
*/
- virtual std::pair<bool, int> getZone(const isc::dns::Name& name) const = 0;
+ virtual std::pair<bool, int> getZone(const std::string& name) const = 0;
/**
* \brief This holds the internal context of ZoneIterator for databases
@@ -103,135 +167,296 @@ public:
* \brief Function to provide next resource record
*
* This function should provide data about the next resource record
- * from the iterated zone. The data are not converted yet.
+ * from the data that is searched. The data is not converted yet.
+ *
+ * Depending on how the iterator was constructed, there is a difference
+ * in behaviour; for a 'full zone iterator', created with
+ * getAllRecords(), all COLUMN_COUNT elements of the array are
+ * overwritten.
+ * For a 'name iterator', created with getRecords(), the column
+ * NAME_COLUMN is untouched, since what would be added here is by
+ * definition already known to the caller (it already passes it as
+ * an argument to getRecords()).
+ *
+ * Once this function returns false, any subsequent call to it should
+ * result in false. The implementation of a derived class must ensure
+ * it doesn't cause any disruption due to that such as a crash or
+ * exception.
*
* \note The order of RRs is not strictly set, but the RRs for single
* RRset must not be interleaved with any other RRs (eg. RRsets must be
* "together").
*
* \param columns The data will be returned through here. The order
- * is specified by the RecordColumns enum.
- * \param Size of the columns array. Must be equal to COLUMN_COUNT,
- * otherwise DataSourceError is thrown.
+ * is specified by the RecordColumns enum, and the size must be
+ * COLUMN_COUNT
* \todo Do we consider databases where it is stored in binary blob
* format?
* \throw DataSourceError if there's database-related error. If the
* exception (or any other in case of derived class) is thrown,
* the iterator can't be safely used any more.
+ * \return true if a record was found, and the columns array was
+ * updated. false if there was no more data, in which case
+ * the columns array is untouched.
*/
- virtual bool getNext(std::string columns[], size_t column_data) = 0;
+ virtual bool getNext(std::string (&columns)[COLUMN_COUNT]) = 0;
};
typedef boost::shared_ptr<IteratorContext> IteratorContextPtr;
/**
- * \brief Creates an iterator context for the whole zone.
+ * \brief Creates an iterator context for a specific name.
+ *
+ * Returns an IteratorContextPtr that contains all records of the
+ * given name from the given zone.
*
- * This should create a new iterator context to be used by
- * DatabaseConnection's ZoneIterator. It can be created based on the name
- * or the ID (returned from getZone()), what is more comfortable for the
- * database implementation. Both are provided (and are guaranteed to match,
- * the DatabaseClient first looks up the zone ID and then calls this).
+ * The implementation of the iterator that is returned may leave the
+ * NAME_COLUMN column of the array passed to getNext() untouched, as that
+ * data is already known (it is the same as the name argument here)
*
- * The default implementation throws isc::NotImplemented, to allow
- * "minimal" implementations of the connection not supporting optional
- * functionality.
+ * \exception any Since any implementation can be used, the caller should
+ * expect any exception to be thrown.
*
- * \param name The name of the zone.
+ * \param name The name to search for. This should be a FQDN.
* \param id The ID of the zone, returned from getZone().
+ * \param subdomains If set to true, match subdomains of name instead
+ * of name itself. It is used to find empty domains and match
+ * wildcards.
* \return Newly created iterator context. Must not be NULL.
*/
- virtual IteratorContextPtr getAllRecords(const isc::dns::Name& name,
- int id) const
- {
- /*
- * This is a compromise. We need to document the parameters in doxygen,
- * so they need a name, but then it complains about unused parameter.
- * This is a NOP that "uses" the parameters.
- */
- static_cast<void>(name);
- static_cast<void>(id);
-
- isc_throw(isc::NotImplemented,
- "This database datasource can't be iterated");
- }
+ virtual IteratorContextPtr getRecords(const std::string& name,
+ int id,
+ bool subdomains = false) const = 0;
/**
- * \brief Starts a new search for records of the given name in the given zone
- *
- * The data searched by this call can be retrieved with subsequent calls to
- * getNextRecord().
- *
- * \exception DataSourceError if there is a problem connecting to the
- * backend database
- *
- * \param zone_id The zone to search in, as returned by getZone()
- * \param name The name of the records to find
- */
- virtual void searchForRecords(int zone_id, const std::string& name) = 0;
-
- /**
- * \brief Retrieves the next record from the search started with searchForRecords()
- *
- * Returns a boolean specifying whether or not there was more data to read.
- * In the case of a database error, a DatasourceError is thrown.
+ * \brief Creates an iterator context for the whole zone.
*
- * The columns passed is an array of std::strings consisting of
- * DatabaseConnection::COLUMN_COUNT elements, the elements of which
- * are defined in DatabaseConnection::RecordColumns, in their basic
- * string representation.
+ * Returns an IteratorContextPtr that contains all records of the
+ * zone with the given zone id.
*
- * If you are implementing a derived database connection class, you
- * should have this method check the column_count value, and fill the
- * array with strings conforming to their description in RecordColumn.
+ * Each call to getNext() on the returned iterator should copy all
+ * column fields of the array that is passed, as defined in the
+ * RecordColumns enum.
*
- * \exception DatasourceError if there was an error reading from the database
+ * \exception any Since any implementation can be used, the caller should
+ * expect any exception to be thrown.
*
- * \param columns The elements of this array will be filled with the data
- * for one record as defined by RecordColumns
- * If there was no data, the array is untouched.
- * \return true if there was a next record, false if there was not
+ * \param id The ID of the zone, returned from getZone().
+ * \return Newly created iterator context. Must not be NULL.
*/
- virtual bool getNextRecord(std::string columns[], size_t column_count) = 0;
+ virtual IteratorContextPtr getAllRecords(int id) const = 0;
- /**
- * \brief Resets the current search initiated with searchForRecords()
- *
- * This method will be called when the called of searchForRecords() and
- * getNextRecord() finds bad data, and aborts the current search.
- * It should clean up whatever handlers searchForRecords() created, and
- * any other state modified or needed by getNextRecord()
- *
- * Of course, the implementation of getNextRecord may also use it when
- * it is done with a search. If it does, the implementation of this
- * method should make sure it can handle being called multiple times.
- *
- * The implementation for this method should make sure it never throws.
- */
- virtual void resetSearch() = 0;
+ /// Start a transaction for updating a zone.
+ ///
+ /// Each derived class version of this method starts a database
+ /// transaction to make updates to the given name of zone (whose class was
+ /// specified at the construction of the class).
+ ///
+ /// If \c replace is true, any existing records of the zone will be
+ /// deleted on successful completion of updates (after
+ /// \c commitUpdateZone()); if it's false, the existing records will be
+ /// intact unless explicitly deleted by \c deleteRecordInZone().
+ ///
+ /// A single \c DatabaseAccessor instance can perform at most one update
+ /// transaction; a duplicate call to this method before
+ /// \c commitUpdateZone() or \c rollbackUpdateZone() will result in
+ /// a \c DataSourceError exception. If multiple update attempts need
+ /// to be performed concurrently (and if the underlying database allows
+ /// such operation), separate \c DatabaseAccessor instance must be
+ /// created.
+ ///
+ /// \note The underlying database may not allow concurrent updates to
+ /// the same database instance even if different "connections" (or
+ /// something similar specific to the database implementation) are used
+ /// for different sets of updates. For example, it doesn't seem to be
+ /// possible for SQLite3 unless different databases are used. MySQL
+ /// allows concurrent updates to different tables of the same database,
+ /// but a specific operation may block others. As such, this interface
+ /// doesn't require derived classes to allow concurrent updates with
+ /// multiple \c DatabaseAccessor instances; however, the implementation
+ /// is encouraged to do the best for making it more likely to succeed
+ /// as long as the underlying database system allows concurrent updates.
+ ///
+ /// This method returns a pair of \c bool and \c int. Its first element
+ /// indicates whether the given name of zone is found. If it's false,
+ /// the transaction isn't considered to be started; a subsequent call to
+ /// this method with an existing zone name should succeed. Likewise,
+ /// if a call to this method results in an exception, the transaction
+ /// isn't considered to be started. Note also that if the zone is not
+ /// found this method doesn't try to create a new one in the database.
+ /// It must have been created by some other means beforehand.
+ ///
+ /// The second element is the internal zone ID used for subsequent
+ /// updates. Depending on implementation details of the actual derived
+ /// class method, it may be different from the one returned by
+ /// \c getZone(); for example, a specific implementation may use a
+ /// completely new zone ID when \c replace is true.
+ ///
+ /// \exception DataSourceError Duplicate call to this method, or some
+ /// internal database related error.
+ ///
+ /// \param zone_name A string representation of the zone name to be updated
+ /// \param replace Whether to replace the entire zone (see above)
+ ///
+ /// \return A pair of bool and int, indicating whether the specified zone
+ /// exists and (if so) the zone ID to be used for the update, respectively.
+ virtual std::pair<bool, int> startUpdateZone(const std::string& zone_name,
+ bool replace) = 0;
- /**
- * Definitions of the fields as they are required to be filled in
- * by getNextRecord()
- *
- * When implementing getNextRecord(), the columns array should
- * be filled with the values as described in this enumeration,
- * in this order, i.e. TYPE_COLUMN should be the first element
- * (index 0) of the array, TTL_COLUMN should be the second element
- * (index 1), etc.
- */
- enum RecordColumns {
- TYPE_COLUMN = 0, ///< The RRType of the record (A/NS/TXT etc.)
- TTL_COLUMN = 1, ///< The TTL of the record (a
- SIGTYPE_COLUMN = 2, ///< For RRSIG records, this contains the RRTYPE
- ///< the RRSIG covers. In the current implementation,
- ///< this field is ignored.
- RDATA_COLUMN = 3, ///< Full text representation of the record's RDATA
- NAME_COLUMN = 4 ///< The domain name of this RR
- };
+ /// Add a single record to the zone to be updated.
+ ///
+ /// This method provides a simple interface to insert a new record
+ /// (a database "row") to the zone in the update context started by
+ /// \c startUpdateZone(). The zone to which the record to be added
+ /// is the one specified at the time of the \c startUpdateZone() call.
+ ///
+ /// A successful call to \c startUpdateZone() must have preceded to
+ /// this call; otherwise a \c DataSourceError exception will be thrown.
+ ///
+ /// The row is defined as a vector of strings that has exactly
+ /// ADD_COLUMN_COUNT number of elements. See AddRecordColumns for
+ /// the semantics of each element.
+ ///
+ /// Derived class methods are not required to check whether the given
+ /// values in \c columns are valid in terms of the expected semantics;
+ /// in general, it's the caller's responsibility.
+ /// For example, TTLs would normally be expected to be a textual
+ /// representation of decimal numbers, but this interface doesn't require
+ /// the implementation to perform this level of validation. It may check
+ /// the values, however, and in that case if it detects an error it
+ /// should throw a \c DataSourceError exception.
+ ///
+ /// Likewise, derived class methods are not required to detect any
+ /// duplicate record that is already in the zone.
+ ///
+ /// \note The underlying database schema may not have a trivial mapping
+ /// from this style of definition of rows to actual database records.
+ /// It's the implementation's responsibility to implement the mapping
+ /// in the actual derived method.
+ ///
+ /// \exception DataSourceError Invalid call without starting a transaction,
+ /// or other internal database error.
+ ///
+ /// \param columns An array of strings that defines a record to be added
+ /// to the zone.
+ virtual void addRecordToZone(
+ const std::string (&columns)[ADD_COLUMN_COUNT]) = 0;
- /// The number of fields the columns array passed to getNextRecord should have
- static const size_t COLUMN_COUNT = 5;
+ /// Delete a single record from the zone to be updated.
+ ///
+ /// This method provides a simple interface to delete a record
+ /// (a database "row") from the zone in the update context started by
+ /// \c startUpdateZone(). The zone from which the record to be deleted
+ /// is the one specified at the time of the \c startUpdateZone() call.
+ ///
+ /// A successful call to \c startUpdateZone() must have preceded to
+ /// this call; otherwise a \c DataSourceError exception will be thrown.
+ ///
+ /// The record to be deleted is specified by a vector of strings that has
+ /// exactly DEL_PARAM_COUNT number of elements. See DeleteRecordParams
+ /// for the semantics of each element.
+ ///
+ /// \note In IXFR, TTL may also be specified, but we intentionally
+ /// ignore that in this interface, because it's not guaranteed
+ /// that all records have the same TTL (unlike the RRset
+ /// assumption) and there can even be multiple records for the
+ /// same name, type and rdata with different TTLs. If we only
+ /// delete one of them, subsequent lookup will still return a
+ /// positive answer, which would be confusing. It's a higher
+ /// layer's responsibility to check if there is at least one
+ /// record in the database that has the given TTL.
+ ///
+ /// Like \c addRecordToZone, derived class methods are not required to
+ /// validate the semantics of the given parameters or to check if there
+ /// is a record that matches the specified parameter; if there isn't
+ /// it simply ignores the result.
+ ///
+ /// \exception DataSourceError Invalid call without starting a transaction,
+ /// or other internal database error.
+ ///
+ /// \param params An array of strings that defines a record to be deleted
+ /// from the zone.
+ virtual void deleteRecordInZone(
+ const std::string (¶ms)[DEL_PARAM_COUNT]) = 0;
+
+ /// Commit updates to the zone.
+ ///
+ /// This method completes a transaction of making updates to the zone
+ /// in the context started by startUpdateZone.
+ ///
+ /// A successful call to \c startUpdateZone() must have preceded to
+ /// this call; otherwise a \c DataSourceError exception will be thrown.
+ /// Once this method successfully completes, the transaction isn't
+ /// considered to exist any more. So a new transaction can now be
+ /// started. On the other hand, a duplicate call to this method after
+ /// a successful completion of it is invalid and should result in
+ /// a \c DataSourceError exception.
+ ///
+ /// If some internal database error happens, a \c DataSourceError
+ /// exception must be thrown. In that case the transaction is still
+ /// considered to be valid; the caller must explicitly rollback it
+ /// or (if it's confident that the error is temporary) try to commit it
+ /// again.
+ ///
+ /// \exception DataSourceError Call without a transaction, duplicate call
+ /// to the method or internal database error.
+ virtual void commitUpdateZone() = 0;
+
+ /// Rollback updates to the zone made so far.
+ ///
+ /// This method rollbacks a transaction of making updates to the zone
+ /// in the context started by startUpdateZone. When it succeeds
+ /// (it normally should, but see below), the underlying database should
+ /// be reverted to the point before performing the corresponding
+ /// \c startUpdateZone().
+ ///
+ /// A successful call to \c startUpdateZone() must have preceded to
+ /// this call; otherwise a \c DataSourceError exception will be thrown.
+ /// Once this method successfully completes, the transaction isn't
+ /// considered to exist any more. So a new transaction can now be
+ /// started. On the other hand, a duplicate call to this method after
+ /// a successful completion of it is invalid and should result in
+ /// a \c DataSourceError exception.
+ ///
+ /// Normally this method should not fail. But it may not always be
+ /// possible to guarantee it depending on the characteristics of the
+ /// underlying database system. So this interface doesn't require the
+ /// actual implementation for the error free property. But if a specific
+ /// implementation of this method can fail, it is encouraged to document
+ /// when that can happen with its implication.
+ ///
+ /// \exception DataSourceError Call without a transaction, duplicate call
+ /// to the method or internal database error.
+ virtual void rollbackUpdateZone() = 0;
+
+ /// Clone the accessor with the same configuration.
+ ///
+ /// Each derived class implementation of this method will create a new
+ /// accessor of the same derived class with the same configuration
+ /// (such as the database server address) as that of the caller object
+ /// and return it.
+ ///
+ /// Note that other internal states won't be copied to the new accessor
+ /// even though the name of "clone" may indicate so. For example, even
+ /// if the calling accessor is in the middle of a update transaction,
+ /// the new accessor will not start a transaction to trace the same
+ /// updates.
+ ///
+ /// The intended use case of cloning is to create a separate context
+ /// where a specific set of database operations can be performed
+ /// independently from the original accessor. The updater will use it
+ /// so that multiple updaters can be created concurrently even if the
+ /// underlying database system doesn't allow running multiple transactions
+ /// in a single database connection.
+ ///
+ /// The underlying database system may not support the functionality
+ /// that would be needed to implement this method. For example, it
+ /// may not allow a single thread (or process) to have more than one
+ /// database connections. In such a case the derived class implementation
+ /// should throw a \c DataSourceError exception.
+ ///
+ /// \return A shared pointer to the cloned accessor.
+ virtual boost::shared_ptr<DatabaseAccessor> clone() = 0;
/**
* \brief Returns a string identifying this dabase backend
@@ -267,16 +492,19 @@ public:
/**
* \brief Constructor
*
- * It initializes the client with a database.
+ * It initializes the client with a database via the given accessor.
*
- * \exception isc::InvalidParameter if database is NULL. It might throw
+ * \exception isc::InvalidParameter if accessor is NULL. It might throw
* standard allocation exception as well, but doesn't throw anything else.
*
- * \param database The database to use to get data. As the parameter
- * suggests, the client takes ownership of the database and will
- * delete it when itself deleted.
+ * \param rrclass The RR class of the zones that this client will handle.
+ * \param accessor The accessor to the database to use to get data.
+ * As the parameter suggests, the client takes ownership of the accessor
+ * and will delete it when itself deleted.
*/
- DatabaseClient(boost::shared_ptr<DatabaseAccessor> database);
+ DatabaseClient(isc::dns::RRClass rrclass,
+ boost::shared_ptr<DatabaseAccessor> accessor);
+
/**
* \brief Corresponding ZoneFinder implementation
*
@@ -366,20 +594,22 @@ public:
* applications shouldn't need it.
*/
int zone_id() const { return (zone_id_); }
+
/**
- * \brief The database.
+ * \brief The database accessor.
*
- * This function provides the database stored inside as
+ * This function provides the database accessor stored inside as
* passed to the constructor. This is meant for testing purposes and
* normal applications shouldn't need it.
*/
- const DatabaseAccessor& database() const {
- return (*database_);
+ const DatabaseAccessor& getAccessor() const {
+ return (*accessor_);
}
private:
- boost::shared_ptr<DatabaseAccessor> database_;
+ boost::shared_ptr<DatabaseAccessor> accessor_;
const int zone_id_;
const isc::dns::Name origin_;
+
/**
* \brief Searches database for an RRset
*
@@ -403,6 +633,9 @@ public:
* DataSourceError.
* \param want_ns This allows redirection by NS to be returned. If
* any other data is met as well, DataSourceError is thrown.
+ * \param construct_name If set to non-NULL, the resulting RRset will
+ * be constructed for this name instead of the queried one. This
+ * is useful for wildcards.
* \note It may happen that some of the above error conditions are not
* detected in some circumstances. The goal here is not to validate
* the domain in DB, but to avoid bad behaviour resulting from
@@ -420,8 +653,21 @@ public:
type,
bool want_cname,
bool want_dname,
- bool want_ns);
+ bool want_ns, const
+ isc::dns::Name*
+ construct_name = NULL);
+
+ /**
+ * \brief Checks if something lives below this domain.
+ *
+ * This looks if there's any subdomain of the given name. It can be
+ * used to test if domain is empty non-terminal.
+ *
+ * \param name The domain to check.
+ */
+ bool hasSubdomains(const std::string& name);
};
+
/**
* \brief Find a zone in the database
*
@@ -457,12 +703,26 @@ public:
*/
virtual ZoneIteratorPtr getIterator(const isc::dns::Name& name) const;
+ /// This implementation internally clones the accessor from the one
+ /// used in the client and starts a separate transaction using the cloned
+ /// accessor. The returned updater will be able to work separately from
+ /// the original client.
+ virtual ZoneUpdaterPtr getUpdater(const isc::dns::Name& name,
+ bool replace) const;
+
private:
- /// \brief Our database.
- const boost::shared_ptr<DatabaseAccessor> database_;
+ /// \brief The RR class that this client handles.
+ const isc::dns::RRClass rrclass_;
+
+ /// \brief The accessor to our database.
+ const boost::shared_ptr<DatabaseAccessor> accessor_;
};
}
}
-#endif
+#endif // __DATABASE_DATASRC_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/datasrc/datasrc_messages.mes b/src/lib/datasrc/datasrc_messages.mes
index 89f2806..efb88fd 100644
--- a/src/lib/datasrc/datasrc_messages.mes
+++ b/src/lib/datasrc/datasrc_messages.mes
@@ -63,12 +63,6 @@ The maximum allowed number of items of the hotspot cache is set to the given
number. If there are too many, some of them will be dropped. The size of 0
means no limit.
-% DATASRC_DATABASE_FIND_ERROR error retrieving data from datasource %1: %2
-This was an internal error while reading data from a datasource. This can either
-mean the specific data source implementation is not behaving correctly, or the
-data it provides is invalid. The current search is aborted.
-The error message contains specific information about the error.
-
% DATASRC_DATABASE_FIND_RECORDS looking in datasource %1 for record %2/%3
Debug information. The database data source is looking up records with the given
name and type in the database.
@@ -79,18 +73,6 @@ different TTL values. This isn't allowed on the wire and is considered
an error, so we set it to the lowest value we found (but we don't modify the
database). The data in database should be checked and fixed.
-% DATASRC_DATABASE_FIND_UNCAUGHT_ERROR uncaught general error retrieving data from datasource %1: %2
-There was an uncaught general exception while reading data from a datasource.
-This most likely points to a logic error in the code, and can be considered a
-bug. The current search is aborted. Specific information about the exception is
-printed in this error message.
-
-% DATASRC_DATABASE_FIND_UNCAUGHT_ISC_ERROR uncaught error retrieving data from datasource %1: %2
-There was an uncaught ISC exception while reading data from a datasource. This
-most likely points to a logic error in the code, and can be considered a bug.
-The current search is aborted. Specific information about the exception is
-printed in this error message.
-
% DATASRC_DATABASE_FOUND_DELEGATION Found delegation at %2 in %1
When searching for a domain, the program met a delegation to a different zone
at the given domain name. It will return that one instead.
@@ -105,6 +87,11 @@ When searching for a domain, the program met a DNAME redirection to a different
place in the domain space at the given domain name. It will return that one
instead.
+% DATASRC_DATABASE_FOUND_EMPTY_NONTERMINAL empty non-terminal %2 in %1
+The domain name doesn't have any RRs, so it doesn't exist in the database.
+However, it has a subdomain, so it exists in the DNS address space. So we
+return NXRRSET instead of NXDOMAIN.
+
% DATASRC_DATABASE_FOUND_NXDOMAIN search in datasource %1 resulted in NXDOMAIN for %2/%3/%4
The data returned by the database backend did not contain any data for the given
domain name, class and type.
@@ -135,6 +122,28 @@ were found to be different. This isn't allowed on the wire and is considered
an error, so we set it to the lowest value we found (but we don't modify the
database). The data in database should be checked and fixed.
+% DATASRC_DATABASE_WILDCARD constructing RRset %3 from wildcard %2 in %1
+The database doesn't contain directly matching domain, but it does contain a
+wildcard one which is being used to synthesize the answer.
+
+% DATASRC_DATABASE_WILDCARD_CANCEL_NS canceled wildcard match on %2 because %3 contains NS in %1
+The database was queried to provide glue data and it didn't find direct match.
+It could create it from given wildcard, but matching wildcards is forbidden
+under a zone cut, which was found. Therefore the delegation will be returned
+instead.
+
+% DATASRC_DATABASE_WILDCARD_CANCEL_SUB wildcard %2 can't be used to construct %3 because %4 exists in %1
+The answer could be constructed using the wildcard, but the given subdomain
+exists, therefore this name is something like empty non-terminal (actually,
+from the protocol point of view, it is empty non-terminal, but the code
+discovers it differently).
+
+% DATASRC_DATABASE_WILDCARD_EMPTY implicit wildcard %2 used to construct %3 in %1
+The given wildcard exists implicitly in the domainspace, as empty nonterminal
+(eg. there's something like subdomain.*.example.org, so *.example.org exists
+implicitly, but is empty). This will produce NXRRSET, because the constructed
+domain is empty as well as the wildcard.
+
% DATASRC_DO_QUERY handling query for '%1/%2'
A debug message indicating that a query for the given name and RR type is being
processed.
@@ -581,3 +590,38 @@ data source.
% DATASRC_UNEXPECTED_QUERY_STATE unexpected query state
This indicates a programming error. An internal task of unknown type was
generated.
+
+% DATASRC_DATABASE_UPDATER_CREATED zone updater created for '%1/%2' on %3
+Debug information. A zone updater object is created to make updates to
+the shown zone on the shown backend database.
+
+% DATASRC_DATABASE_UPDATER_DESTROYED zone updater destroyed for '%1/%2' on %3
+Debug information. A zone updater object is destroyed, either successfully
+or after failure of, making updates to the shown zone on the shown backend
+database.
+
+%DATASRC_DATABASE_UPDATER_ROLLBACK zone updates roll-backed for '%1/%2' on %3
+A zone updater is being destroyed without committing the changes.
+This would typically mean the update attempt was aborted due to some
+error, but may also be a bug of the application that forgets committing
+the changes. The intermediate changes made through the updater won't
+be applied to the underlying database. The zone name, its class, and
+the underlying database name are shown in the log message.
+
+%DATASRC_DATABASE_UPDATER_ROLLBACKFAIL failed to roll back zone updates for '%1/%2' on %3: %4
+A zone updater is being destroyed without committing the changes to
+the database, and attempts to rollback incomplete updates, but it
+unexpectedly fails. The higher level implementation does not expect
+it to fail, so this means either a serious operational error in the
+underlying data source (such as a system failure of a database) or
+software bug in the underlying data source implementation. In either
+case if this message is logged the administrator should carefully
+examine the underlying data source to see what exactly happens and
+whether the data is still valid. The zone name, its class, and the
+underlying database name as well as the error message thrown from the
+database module are shown in the log message.
+
+% DATASRC_DATABASE_UPDATER_COMMIT updates committed for '%1/%2' on %3
+Debug information. A set of updates to a zone has been successfully
+committed to the corresponding database backend. The zone name,
+its class and the database name are printed.
diff --git a/src/lib/datasrc/memory_datasrc.cc b/src/lib/datasrc/memory_datasrc.cc
index 1fc9252..630b1c0 100644
--- a/src/lib/datasrc/memory_datasrc.cc
+++ b/src/lib/datasrc/memory_datasrc.cc
@@ -17,6 +17,8 @@
#include <boost/shared_ptr.hpp>
#include <boost/bind.hpp>
+#include <exceptions/exceptions.h>
+
#include <dns/name.h>
#include <dns/rrclass.h>
#include <dns/rrsetlist.h>
@@ -793,5 +795,9 @@ InMemoryClient::getIterator(const Name& name) const {
return (ZoneIteratorPtr(new MemoryIterator(zone->impl_->domains_, name)));
}
+ZoneUpdaterPtr
+InMemoryClient::getUpdater(const isc::dns::Name&, bool) const {
+ isc_throw(isc::NotImplemented, "Update attempt on in memory data source");
+}
} // end of namespace datasrc
} // end of namespace dns
diff --git a/src/lib/datasrc/memory_datasrc.h b/src/lib/datasrc/memory_datasrc.h
index 6cd1753..c569548 100644
--- a/src/lib/datasrc/memory_datasrc.h
+++ b/src/lib/datasrc/memory_datasrc.h
@@ -266,6 +266,17 @@ public:
/// \brief Implementation of the getIterator method
virtual ZoneIteratorPtr getIterator(const isc::dns::Name& name) const;
+ /// In-memory data source is read-only, so this derived method will
+ /// result in a NotImplemented exception.
+ ///
+ /// \note We plan to use a database-based data source as a backend
+ /// persistent storage for an in-memory data source. When it's
+ /// implemented we may also want to allow the user of the in-memory client
+ /// to update via its updater (this may or may not be a good idea and
+ /// is subject to further discussions).
+ virtual ZoneUpdaterPtr getUpdater(const isc::dns::Name& name,
+ bool replace) const;
+
private:
// TODO: Do we still need the PImpl if nobody should manipulate this class
// directly any more (it should be handled through DataSourceClient)?
diff --git a/src/lib/datasrc/sqlite3_accessor.cc b/src/lib/datasrc/sqlite3_accessor.cc
index bab7d29..956f447 100644
--- a/src/lib/datasrc/sqlite3_accessor.cc
+++ b/src/lib/datasrc/sqlite3_accessor.cc
@@ -14,43 +14,120 @@
#include <sqlite3.h>
+#include <string>
+#include <vector>
+
+#include <boost/foreach.hpp>
+
#include <datasrc/sqlite3_accessor.h>
#include <datasrc/logger.h>
#include <datasrc/data_source.h>
#include <util/filename.h>
-#include <boost/lexical_cast.hpp>
+using namespace std;
+
+#define SQLITE_SCHEMA_VERSION 1
namespace isc {
namespace datasrc {
+// The following enum and char* array define the SQL statements commonly
+// used in this implementation. Corresponding prepared statements (of
+// type sqlite3_stmt*) are maintained in the statements_ array of the
+// SQLite3Parameters structure.
+
+enum StatementID {
+ ZONE = 0,
+ ANY = 1,
+ ANY_SUB = 2,
+ BEGIN = 3,
+ COMMIT = 4,
+ ROLLBACK = 5,
+ DEL_ZONE_RECORDS = 6,
+ ADD_RECORD = 7,
+ DEL_RECORD = 8,
+ ITERATE = 9,
+ NUM_STATEMENTS = 10
+};
+
+const char* const text_statements[NUM_STATEMENTS] = {
+ // note for ANY and ITERATE: the order of the SELECT values is
+ // specifically chosen to match the enum values in RecordColumns
+ "SELECT id FROM zones WHERE name=?1 AND rdclass = ?2", // ZONE
+ "SELECT rdtype, ttl, sigtype, rdata FROM records " // ANY
+ "WHERE zone_id=?1 AND name=?2",
+ "SELECT rdtype, ttl, sigtype, rdata " // ANY_SUB
+ "FROM records WHERE zone_id=?1 AND name LIKE (\"%.\" || ?2)",
+ "BEGIN", // BEGIN
+ "COMMIT", // COMMIT
+ "ROLLBACK", // ROLLBACK
+ "DELETE FROM records WHERE zone_id=?1", // DEL_ZONE_RECORDS
+ "INSERT INTO records " // ADD_RECORD
+ "(zone_id, name, rname, ttl, rdtype, sigtype, rdata) "
+ "VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)",
+ "DELETE FROM records WHERE zone_id=?1 AND name=?2 " // DEL_RECORD
+ "AND rdtype=?3 AND rdata=?4",
+ "SELECT rdtype, ttl, sigtype, rdata, name FROM records " // ITERATE
+ "WHERE zone_id = ?1 ORDER BY name, rdtype"
+};
+
struct SQLite3Parameters {
SQLite3Parameters() :
- db_(NULL), version_(-1),
- q_zone_(NULL), q_any_(NULL)
- /*q_record_(NULL), q_addrs_(NULL), q_referral_(NULL),
- q_count_(NULL), q_previous_(NULL), q_nsec3_(NULL),
- q_prevnsec3_(NULL) */
- {}
+ db_(NULL), version_(-1), updating_zone(false), updated_zone_id(-1)
+ {
+ for (int i = 0; i < NUM_STATEMENTS; ++i) {
+ statements_[i] = NULL;
+ }
+ }
+
sqlite3* db_;
int version_;
- sqlite3_stmt* q_zone_;
- sqlite3_stmt* q_any_;
- /*
- TODO: Yet unneeded statements
- sqlite3_stmt* q_record_;
- sqlite3_stmt* q_addrs_;
- sqlite3_stmt* q_referral_;
- sqlite3_stmt* q_count_;
- sqlite3_stmt* q_previous_;
- sqlite3_stmt* q_nsec3_;
- sqlite3_stmt* q_prevnsec3_;
- */
+ sqlite3_stmt* statements_[NUM_STATEMENTS];
+ bool updating_zone; // whether or not updating the zone
+ int updated_zone_id; // valid only when updating_zone is true
};
-SQLite3Database::SQLite3Database(const std::string& filename,
- const isc::dns::RRClass& rrclass) :
+// This is a helper class to encapsulate the code logic of executing
+// a specific SQLite3 statement, ensuring the corresponding prepared
+// statement is always reset whether the execution is completed successfully
+// or it results in an exception.
+// Note that an object of this class is intended to be used for "ephemeral"
+// statement, which is completed with a single "step" (normally within a
+// single call to an SQLite3Database method). In particular, it cannot be
+// used for "SELECT" variants, which generally expect multiple matching rows.
+class StatementProcessor {
+public:
+ // desc will be used on failure in the what() message of the resulting
+ // DataSourceError exception.
+ StatementProcessor(SQLite3Parameters& dbparameters, StatementID stmt_id,
+ const char* desc) :
+ dbparameters_(dbparameters), stmt_id_(stmt_id), desc_(desc)
+ {
+ sqlite3_clear_bindings(dbparameters_.statements_[stmt_id_]);
+ }
+
+ ~StatementProcessor() {
+ sqlite3_reset(dbparameters_.statements_[stmt_id_]);
+ }
+
+ void exec() {
+ if (sqlite3_step(dbparameters_.statements_[stmt_id_]) != SQLITE_DONE) {
+ sqlite3_reset(dbparameters_.statements_[stmt_id_]);
+ isc_throw(DataSourceError, "failed to " << desc_ << ": " <<
+ sqlite3_errmsg(dbparameters_.db_));
+ }
+ }
+
+private:
+ SQLite3Parameters& dbparameters_;
+ const StatementID stmt_id_;
+ const char* const desc_;
+};
+
+SQLite3Accessor::SQLite3Accessor(const std::string& filename,
+ const isc::dns::RRClass& rrclass) :
dbparameters_(new SQLite3Parameters),
+ filename_(filename),
class_(rrclass.toText()),
database_name_("sqlite3_" +
isc::util::Filename(filename).nameAndExtension())
@@ -60,6 +137,25 @@ SQLite3Database::SQLite3Database(const std::string& filename,
open(filename);
}
+SQLite3Accessor::SQLite3Accessor(const std::string& filename,
+ const string& rrclass) :
+ dbparameters_(new SQLite3Parameters),
+ filename_(filename),
+ class_(rrclass),
+ database_name_("sqlite3_" +
+ isc::util::Filename(filename).nameAndExtension())
+{
+ LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_SQLITE_NEWCONN);
+
+ open(filename);
+}
+
+boost::shared_ptr<DatabaseAccessor>
+SQLite3Accessor::clone() {
+ return (boost::shared_ptr<DatabaseAccessor>(new SQLite3Accessor(filename_,
+ class_)));
+}
+
namespace {
// This is a helper class to initialize a Sqlite3 DB safely. An object of
@@ -72,35 +168,10 @@ namespace {
class Initializer {
public:
~Initializer() {
- if (params_.q_zone_ != NULL) {
- sqlite3_finalize(params_.q_zone_);
- }
- if (params_.q_any_ != NULL) {
- sqlite3_finalize(params_.q_any_);
- }
- /*
- if (params_.q_record_ != NULL) {
- sqlite3_finalize(params_.q_record_);
- }
- if (params_.q_addrs_ != NULL) {
- sqlite3_finalize(params_.q_addrs_);
- }
- if (params_.q_referral_ != NULL) {
- sqlite3_finalize(params_.q_referral_);
- }
- if (params_.q_count_ != NULL) {
- sqlite3_finalize(params_.q_count_);
+ for (int i = 0; i < NUM_STATEMENTS; ++i) {
+ sqlite3_finalize(params_.statements_[i]);
}
- if (params_.q_previous_ != NULL) {
- sqlite3_finalize(params_.q_previous_);
- }
- if (params_.q_nsec3_ != NULL) {
- sqlite3_finalize(params_.q_nsec3_);
- }
- if (params_.q_prevnsec3_ != NULL) {
- sqlite3_finalize(params_.q_prevnsec3_);
- }
- */
+
if (params_.db_ != NULL) {
sqlite3_close(params_.db_);
}
@@ -136,45 +207,6 @@ const char* const SCHEMA_LIST[] = {
NULL
};
-const char* const q_zone_str = "SELECT id FROM zones WHERE name=?1 AND rdclass = ?2";
-
-const char* const q_any_str = "SELECT rdtype, ttl, sigtype, rdata, name "
- "FROM records WHERE zone_id=?1 AND name=?2";
-
-const char* const q_iterate_str = "SELECT rdtype, ttl, sigtype, rdata, name FROM records "
- "WHERE zone_id = ?1 "
- "ORDER BY name, rdtype";
-
-/* TODO: Prune the statements, not everything will be needed maybe?
-const char* const q_record_str = "SELECT rdtype, ttl, sigtype, rdata "
- "FROM records WHERE zone_id=?1 AND name=?2 AND "
- "((rdtype=?3 OR sigtype=?3) OR "
- "(rdtype='CNAME' OR sigtype='CNAME') OR "
- "(rdtype='NS' OR sigtype='NS'))";
-
-const char* const q_addrs_str = "SELECT rdtype, ttl, sigtype, rdata "
- "FROM records WHERE zone_id=?1 AND name=?2 AND "
- "(rdtype='A' OR sigtype='A' OR rdtype='AAAA' OR sigtype='AAAA')";
-
-const char* const q_referral_str = "SELECT rdtype, ttl, sigtype, rdata FROM "
- "records WHERE zone_id=?1 AND name=?2 AND"
- "(rdtype='NS' OR sigtype='NS' OR rdtype='DS' OR sigtype='DS' OR "
- "rdtype='DNAME' OR sigtype='DNAME')";
-
-const char* const q_count_str = "SELECT COUNT(*) FROM records "
- "WHERE zone_id=?1 AND rname LIKE (?2 || '%');";
-
-const char* const q_previous_str = "SELECT name FROM records "
- "WHERE zone_id=?1 AND rdtype = 'NSEC' AND "
- "rname < $2 ORDER BY rname DESC LIMIT 1";
-
-const char* const q_nsec3_str = "SELECT rdtype, ttl, rdata FROM nsec3 "
- "WHERE zone_id = ?1 AND hash = $2";
-
-const char* const q_prevnsec3_str = "SELECT hash FROM nsec3 "
- "WHERE zone_id = ?1 AND hash <= $2 ORDER BY hash DESC LIMIT 1";
- */
-
sqlite3_stmt*
prepare(sqlite3* const db, const char* const statement) {
sqlite3_stmt* prepared = NULL;
@@ -185,47 +217,101 @@ prepare(sqlite3* const db, const char* const statement) {
return (prepared);
}
-void
-checkAndSetupSchema(Initializer* initializer) {
- sqlite3* const db = initializer->params_.db_;
+// small function to sleep for 0.1 seconds, needed when waiting for
+// exclusive database locks (which should only occur on startup, and only
+// when the database has not been created yet)
+void doSleep() {
+ struct timespec req;
+ req.tv_sec = 0;
+ req.tv_nsec = 100000000;
+ nanosleep(&req, NULL);
+}
+// returns the schema version if the schema version table exists
+// returns -1 if it does not
+int checkSchemaVersion(sqlite3* db) {
sqlite3_stmt* prepared = NULL;
- if (sqlite3_prepare_v2(db, "SELECT version FROM schema_version", -1,
- &prepared, NULL) == SQLITE_OK &&
- sqlite3_step(prepared) == SQLITE_ROW) {
- initializer->params_.version_ = sqlite3_column_int(prepared, 0);
- sqlite3_finalize(prepared);
- } else {
- logger.info(DATASRC_SQLITE_SETUP);
- if (prepared != NULL) {
- sqlite3_finalize(prepared);
+ // At this point in time, the database might be exclusively locked, in
+ // which case even prepare() will return BUSY, so we may need to try a
+ // few times
+ for (size_t i = 0; i < 50; ++i) {
+ int rc = sqlite3_prepare_v2(db, "SELECT version FROM schema_version",
+ -1, &prepared, NULL);
+ if (rc == SQLITE_ERROR) {
+ // this is the error that is returned when the table does not
+ // exist
+ return (-1);
+ } else if (rc == SQLITE_OK) {
+ break;
+ } else if (rc != SQLITE_BUSY || i == 50) {
+ isc_throw(SQLite3Error, "Unable to prepare version query: "
+ << rc << " " << sqlite3_errmsg(db));
}
+ doSleep();
+ }
+ if (sqlite3_step(prepared) != SQLITE_ROW) {
+ isc_throw(SQLite3Error,
+ "Unable to query version: " << sqlite3_errmsg(db));
+ }
+ int version = sqlite3_column_int(prepared, 0);
+ sqlite3_finalize(prepared);
+ return (version);
+}
+
+// return db version
+int create_database(sqlite3* db) {
+ // try to get an exclusive lock. Once that is obtained, do the version
+ // check *again*, just in case this process was racing another
+ //
+ // try for 5 secs (50*0.1)
+ int rc;
+ logger.info(DATASRC_SQLITE_SETUP);
+ for (size_t i = 0; i < 50; ++i) {
+ rc = sqlite3_exec(db, "BEGIN EXCLUSIVE TRANSACTION", NULL, NULL,
+ NULL);
+ if (rc == SQLITE_OK) {
+ break;
+ } else if (rc != SQLITE_BUSY || i == 50) {
+ isc_throw(SQLite3Error, "Unable to acquire exclusive lock "
+ "for database creation: " << sqlite3_errmsg(db));
+ }
+ doSleep();
+ }
+ int schema_version = checkSchemaVersion(db);
+ if (schema_version == -1) {
for (int i = 0; SCHEMA_LIST[i] != NULL; ++i) {
if (sqlite3_exec(db, SCHEMA_LIST[i], NULL, NULL, NULL) !=
SQLITE_OK) {
isc_throw(SQLite3Error,
- "Failed to set up schema " << SCHEMA_LIST[i]);
+ "Failed to set up schema " << SCHEMA_LIST[i]);
}
}
+ sqlite3_exec(db, "COMMIT TRANSACTION", NULL, NULL, NULL);
+ return (SQLITE_SCHEMA_VERSION);
+ } else {
+ return (schema_version);
}
+}
- initializer->params_.q_zone_ = prepare(db, q_zone_str);
- initializer->params_.q_any_ = prepare(db, q_any_str);
- /* TODO: Yet unneeded statements
- initializer->params_.q_record_ = prepare(db, q_record_str);
- initializer->params_.q_addrs_ = prepare(db, q_addrs_str);
- initializer->params_.q_referral_ = prepare(db, q_referral_str);
- initializer->params_.q_count_ = prepare(db, q_count_str);
- initializer->params_.q_previous_ = prepare(db, q_previous_str);
- initializer->params_.q_nsec3_ = prepare(db, q_nsec3_str);
- initializer->params_.q_prevnsec3_ = prepare(db, q_prevnsec3_str);
- */
+void
+checkAndSetupSchema(Initializer* initializer) {
+ sqlite3* const db = initializer->params_.db_;
+
+ int schema_version = checkSchemaVersion(db);
+ if (schema_version != SQLITE_SCHEMA_VERSION) {
+ schema_version = create_database(db);
+ }
+ initializer->params_.version_ = schema_version;
+
+ for (int i = 0; i < NUM_STATEMENTS; ++i) {
+ initializer->params_.statements_[i] = prepare(db, text_statements[i]);
+ }
}
}
void
-SQLite3Database::open(const std::string& name) {
+SQLite3Accessor::open(const std::string& name) {
LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_SQLITE_CONNOPEN).arg(name);
if (dbparameters_->db_ != NULL) {
// There shouldn't be a way to trigger this anyway
@@ -239,19 +325,18 @@ SQLite3Database::open(const std::string& name) {
}
checkAndSetupSchema(&initializer);
- initializer.move(dbparameters_);
+ initializer.move(dbparameters_.get());
}
-SQLite3Database::~SQLite3Database() {
+SQLite3Accessor::~SQLite3Accessor() {
LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_SQLITE_DROPCONN);
if (dbparameters_->db_ != NULL) {
close();
}
- delete dbparameters_;
}
void
-SQLite3Database::close(void) {
+SQLite3Accessor::close(void) {
LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_SQLITE_CONNCLOSE);
if (dbparameters_->db_ == NULL) {
isc_throw(DataSourceError,
@@ -259,217 +344,318 @@ SQLite3Database::close(void) {
}
// XXX: sqlite3_finalize() could fail. What should we do in that case?
- sqlite3_finalize(dbparameters_->q_zone_);
- dbparameters_->q_zone_ = NULL;
-
- sqlite3_finalize(dbparameters_->q_any_);
- dbparameters_->q_any_ = NULL;
-
- /* TODO: Once they are needed or not, uncomment or drop
- sqlite3_finalize(dbparameters->q_record_);
- dbparameters->q_record_ = NULL;
-
- sqlite3_finalize(dbparameters->q_addrs_);
- dbparameters->q_addrs_ = NULL;
-
- sqlite3_finalize(dbparameters->q_referral_);
- dbparameters->q_referral_ = NULL;
-
- sqlite3_finalize(dbparameters->q_count_);
- dbparameters->q_count_ = NULL;
-
- sqlite3_finalize(dbparameters->q_previous_);
- dbparameters->q_previous_ = NULL;
-
- sqlite3_finalize(dbparameters->q_prevnsec3_);
- dbparameters->q_prevnsec3_ = NULL;
-
- sqlite3_finalize(dbparameters->q_nsec3_);
- dbparameters->q_nsec3_ = NULL;
- */
+ for (int i = 0; i < NUM_STATEMENTS; ++i) {
+ sqlite3_finalize(dbparameters_->statements_[i]);
+ dbparameters_->statements_[i] = NULL;
+ }
sqlite3_close(dbparameters_->db_);
dbparameters_->db_ = NULL;
}
std::pair<bool, int>
-SQLite3Database::getZone(const isc::dns::Name& name) const {
+SQLite3Accessor::getZone(const std::string& name) const {
int rc;
+ sqlite3_stmt* const stmt = dbparameters_->statements_[ZONE];
// Take the statement (simple SELECT id FROM zones WHERE...)
// and prepare it (bind the parameters to it)
- sqlite3_reset(dbparameters_->q_zone_);
- rc = sqlite3_bind_text(dbparameters_->q_zone_, 1, name.toText().c_str(),
- -1, SQLITE_TRANSIENT);
+ sqlite3_reset(stmt);
+ rc = sqlite3_bind_text(stmt, 1, name.c_str(), -1, SQLITE_STATIC);
if (rc != SQLITE_OK) {
isc_throw(SQLite3Error, "Could not bind " << name <<
" to SQL statement (zone)");
}
- rc = sqlite3_bind_text(dbparameters_->q_zone_, 2, class_.c_str(), -1,
- SQLITE_STATIC);
+ rc = sqlite3_bind_text(stmt, 2, class_.c_str(), -1, SQLITE_STATIC);
if (rc != SQLITE_OK) {
isc_throw(SQLite3Error, "Could not bind " << class_ <<
" to SQL statement (zone)");
}
// Get the data there and see if it found anything
- rc = sqlite3_step(dbparameters_->q_zone_);
- std::pair<bool, int> result;
+ rc = sqlite3_step(stmt);
if (rc == SQLITE_ROW) {
- result = std::pair<bool, int>(true,
- sqlite3_column_int(dbparameters_->
- q_zone_, 0));
- return (result);
+ const int zone_id = sqlite3_column_int(stmt, 0);
+ sqlite3_reset(stmt);
+ return (pair<bool, int>(true, zone_id));
} else if (rc == SQLITE_DONE) {
- result = std::pair<bool, int>(false, 0);
// Free resources
- sqlite3_reset(dbparameters_->q_zone_);
- return (result);
+ sqlite3_reset(stmt);
+ return (pair<bool, int>(false, 0));
}
+ sqlite3_reset(stmt);
isc_throw(DataSourceError, "Unexpected failure in sqlite3_step: " <<
- sqlite3_errmsg(dbparameters_->db_));
+ sqlite3_errmsg(dbparameters_->db_));
// Compilers might not realize isc_throw always throws
return (std::pair<bool, int>(false, 0));
}
-namespace {
-// This helper function converts from the unsigned char* type (used by
-// sqlite3) to char* (wanted by std::string). Technically these types
-// might not be directly convertable
-// In case sqlite3_column_text() returns NULL, we just make it an
-// empty string.
-// The sqlite3parameters value is only used to check the error code if
-// ucp == NULL
-const char*
-convertToPlainChar(const unsigned char* ucp,
- SQLite3Parameters* dbparameters) {
- if (ucp == NULL) {
- // The field can really be NULL, in which case we return an
- // empty string, or sqlite may have run out of memory, in
- // which case we raise an error
- if (dbparameters != NULL &&
- sqlite3_errcode(dbparameters->db_) == SQLITE_NOMEM) {
- isc_throw(DataSourceError,
- "Sqlite3 backend encountered a memory allocation "
- "error in sqlite3_column_text()");
- } else {
- return ("");
- }
+class SQLite3Accessor::Context : public DatabaseAccessor::IteratorContext {
+public:
+ // Construct an iterator for all records. When constructed this
+ // way, the getNext() call will copy all fields
+ Context(const boost::shared_ptr<const SQLite3Accessor>& accessor, int id) :
+ iterator_type_(ITT_ALL),
+ accessor_(accessor),
+ statement_(NULL),
+ name_("")
+ {
+ // We create the statement now and then just keep getting data from it
+ statement_ = prepare(accessor->dbparameters_->db_,
+ text_statements[ITERATE]);
+ bindZoneId(id);
}
- const void* p = ucp;
- return (static_cast<const char*>(p));
-}
-}
-// TODO: Once we want to have iterator returned from searchForRecords, this
-// class can be reused. It should be modified to take the sqlite3 statement
-// instead of creating it in constructor, it doesn't have to care which one
-// it is, just provide data from it.
-class SQLite3Database::Context : public DatabaseAccessor::IteratorContext {
-public:
- Context(const boost::shared_ptr<const SQLite3Database>& database, int id) :
- database_(database),
- statement(NULL)
+ // Construct an iterator for records with a specific name. When constructed
+ // this way, the getNext() call will copy all fields except name
+ Context(const boost::shared_ptr<const SQLite3Accessor>& accessor, int id,
+ const std::string& name, bool subdomains) :
+ iterator_type_(ITT_NAME),
+ accessor_(accessor),
+ statement_(NULL),
+ name_(name)
+
{
// We create the statement now and then just keep getting data from it
- statement = prepare(database->dbparameters_->db_, q_iterate_str);
- if (sqlite3_bind_int(statement, 1, id) != SQLITE_OK) {
- isc_throw(SQLite3Error, "Could not bind " << id <<
- " to SQL statement (iterate)");
- }
+ statement_ = prepare(accessor->dbparameters_->db_,
+ subdomains ? text_statements[ANY_SUB] :
+ text_statements[ANY]);
+ bindZoneId(id);
+ bindName(name_);
}
- bool getNext(std::string data[], size_t size) {
- if (size != COLUMN_COUNT) {
- isc_throw(DataSourceError, "getNext received size of " << size <<
- ", not " << COLUMN_COUNT);
- }
+
+ bool getNext(std::string (&data)[COLUMN_COUNT]) {
// If there's another row, get it
- int rc(sqlite3_step(statement));
+ // If finalize has been called (e.g. when previous getNext() got
+ // SQLITE_DONE), directly return false
+ if (statement_ == NULL) {
+ return false;
+ }
+ const int rc(sqlite3_step(statement_));
if (rc == SQLITE_ROW) {
- for (size_t i(0); i < size; ++ i) {
- data[i] = convertToPlainChar(sqlite3_column_text(statement, i),
- database_->dbparameters_);
+ // For both types, we copy the first four columns
+ copyColumn(data, TYPE_COLUMN);
+ copyColumn(data, TTL_COLUMN);
+ copyColumn(data, SIGTYPE_COLUMN);
+ copyColumn(data, RDATA_COLUMN);
+ // Only copy Name if we are iterating over every record
+ if (iterator_type_ == ITT_ALL) {
+ copyColumn(data, NAME_COLUMN);
}
return (true);
} else if (rc != SQLITE_DONE) {
isc_throw(DataSourceError,
"Unexpected failure in sqlite3_step: " <<
- sqlite3_errmsg(database_->dbparameters_->db_));
+ sqlite3_errmsg(accessor_->dbparameters_->db_));
}
+ finalize();
return (false);
}
+
virtual ~Context() {
- if (statement) {
- sqlite3_finalize(statement);
- }
+ finalize();
}
private:
- boost::shared_ptr<const SQLite3Database> database_;
- sqlite3_stmt *statement;
+ // Depending on which constructor is called, behaviour is slightly
+ // different. We keep track of what to do with the iterator type
+ // See description of getNext() and the constructors
+ enum IteratorType {
+ ITT_ALL,
+ ITT_NAME
+ };
+
+ void copyColumn(std::string (&data)[COLUMN_COUNT], int column) {
+ data[column] = convertToPlainChar(sqlite3_column_text(statement_,
+ column));
+ }
+
+ void bindZoneId(const int zone_id) {
+ if (sqlite3_bind_int(statement_, 1, zone_id) != SQLITE_OK) {
+ finalize();
+ isc_throw(SQLite3Error, "Could not bind int " << zone_id <<
+ " to SQL statement: " <<
+ sqlite3_errmsg(accessor_->dbparameters_->db_));
+ }
+ }
+
+ void bindName(const std::string& name) {
+ if (sqlite3_bind_text(statement_, 2, name.c_str(), -1,
+ SQLITE_TRANSIENT) != SQLITE_OK) {
+ const char* errmsg = sqlite3_errmsg(accessor_->dbparameters_->db_);
+ finalize();
+ isc_throw(SQLite3Error, "Could not bind text '" << name <<
+ "' to SQL statement: " << errmsg);
+ }
+ }
+
+ void finalize() {
+ sqlite3_finalize(statement_);
+ statement_ = NULL;
+ }
+
+ // This helper method converts from the unsigned char* type (used by
+ // sqlite3) to char* (wanted by std::string). Technically these types
+ // might not be directly convertable
+ // In case sqlite3_column_text() returns NULL, we just make it an
+ // empty string, unless it was caused by a memory error
+ const char* convertToPlainChar(const unsigned char* ucp) {
+ if (ucp == NULL) {
+ // The field can really be NULL, in which case we return an
+ // empty string, or sqlite may have run out of memory, in
+ // which case we raise an error
+ if (sqlite3_errcode(accessor_->dbparameters_->db_)
+ == SQLITE_NOMEM) {
+ isc_throw(DataSourceError,
+ "Sqlite3 backend encountered a memory allocation "
+ "error in sqlite3_column_text()");
+ } else {
+ return ("");
+ }
+ }
+ const void* p = ucp;
+ return (static_cast<const char*>(p));
+ }
+
+ const IteratorType iterator_type_;
+ boost::shared_ptr<const SQLite3Accessor> accessor_;
+ sqlite3_stmt *statement_;
+ const std::string name_;
};
DatabaseAccessor::IteratorContextPtr
-SQLite3Database::getAllRecords(const isc::dns::Name&, int id) const {
+SQLite3Accessor::getRecords(const std::string& name, int id,
+ bool subdomains) const
+{
+ return (IteratorContextPtr(new Context(shared_from_this(), id, name,
+ subdomains)));
+}
+
+DatabaseAccessor::IteratorContextPtr
+SQLite3Accessor::getAllRecords(int id) const {
return (IteratorContextPtr(new Context(shared_from_this(), id)));
}
-void
-SQLite3Database::searchForRecords(int zone_id, const std::string& name) {
- resetSearch();
- if (sqlite3_bind_int(dbparameters_->q_any_, 1, zone_id) != SQLITE_OK) {
+pair<bool, int>
+SQLite3Accessor::startUpdateZone(const string& zone_name, const bool replace) {
+ if (dbparameters_->updating_zone) {
isc_throw(DataSourceError,
- "Error in sqlite3_bind_int() for zone_id " <<
- zone_id << ": " << sqlite3_errmsg(dbparameters_->db_));
+ "duplicate zone update on SQLite3 data source");
}
- // use transient since name is a ref and may disappear
- if (sqlite3_bind_text(dbparameters_->q_any_, 2, name.c_str(), -1,
- SQLITE_TRANSIENT) != SQLITE_OK) {
- isc_throw(DataSourceError,
- "Error in sqlite3_bind_text() for name " <<
- name << ": " << sqlite3_errmsg(dbparameters_->db_));
- }
-}
-bool
-SQLite3Database::getNextRecord(std::string columns[], size_t column_count) {
- if (column_count != COLUMN_COUNT) {
- isc_throw(DataSourceError,
- "Datasource backend caller did not pass a column array "
- "of size " << COLUMN_COUNT << " to getNextRecord()");
+ const pair<bool, int> zone_info(getZone(zone_name));
+ if (!zone_info.first) {
+ return (zone_info);
}
- sqlite3_stmt* current_stmt = dbparameters_->q_any_;
- const int rc = sqlite3_step(current_stmt);
+ StatementProcessor(*dbparameters_, BEGIN,
+ "start an SQLite3 transaction").exec();
- if (rc == SQLITE_ROW) {
- for (int column = 0; column < column_count; ++column) {
- try {
- columns[column] = convertToPlainChar(sqlite3_column_text(
- current_stmt, column),
- dbparameters_);
- } catch (const std::bad_alloc&) {
+ if (replace) {
+ try {
+ StatementProcessor delzone_exec(*dbparameters_, DEL_ZONE_RECORDS,
+ "delete zone records");
+
+ sqlite3_clear_bindings(
+ dbparameters_->statements_[DEL_ZONE_RECORDS]);
+ if (sqlite3_bind_int(dbparameters_->statements_[DEL_ZONE_RECORDS],
+ 1, zone_info.second) != SQLITE_OK) {
isc_throw(DataSourceError,
- "bad_alloc in Sqlite3Connection::getNextRecord");
+ "failed to bind SQLite3 parameter: " <<
+ sqlite3_errmsg(dbparameters_->db_));
}
+
+ delzone_exec.exec();
+ } catch (const DataSourceError&) {
+ // Once we start a transaction, if something unexpected happens
+ // we need to rollback the transaction so that a subsequent update
+ // is still possible with this accessor.
+ StatementProcessor(*dbparameters_, ROLLBACK,
+ "rollback an SQLite3 transaction").exec();
+ throw;
}
- return (true);
- } else if (rc == SQLITE_DONE) {
- // reached the end of matching rows
- resetSearch();
- return (false);
}
- isc_throw(DataSourceError, "Unexpected failure in sqlite3_step: " <<
- sqlite3_errmsg(dbparameters_->db_));
- // Compilers might not realize isc_throw always throws
- return (false);
+
+ dbparameters_->updating_zone = true;
+ dbparameters_->updated_zone_id = zone_info.second;
+
+ return (zone_info);
+}
+
+void
+SQLite3Accessor::commitUpdateZone() {
+ if (!dbparameters_->updating_zone) {
+ isc_throw(DataSourceError, "committing zone update on SQLite3 "
+ "data source without transaction");
+ }
+
+ StatementProcessor(*dbparameters_, COMMIT,
+ "commit an SQLite3 transaction").exec();
+ dbparameters_->updating_zone = false;
+ dbparameters_->updated_zone_id = -1;
}
void
-SQLite3Database::resetSearch() {
- sqlite3_reset(dbparameters_->q_any_);
- sqlite3_clear_bindings(dbparameters_->q_any_);
+SQLite3Accessor::rollbackUpdateZone() {
+ if (!dbparameters_->updating_zone) {
+ isc_throw(DataSourceError, "rolling back zone update on SQLite3 "
+ "data source without transaction");
+ }
+
+ StatementProcessor(*dbparameters_, ROLLBACK,
+ "rollback an SQLite3 transaction").exec();
+ dbparameters_->updating_zone = false;
+ dbparameters_->updated_zone_id = -1;
+}
+
+namespace {
+// Commonly used code sequence for adding/deleting record
+template <typename COLUMNS_TYPE>
+void
+doUpdate(SQLite3Parameters& dbparams, StatementID stmt_id,
+ COLUMNS_TYPE update_params, const char* exec_desc)
+{
+ sqlite3_stmt* const stmt = dbparams.statements_[stmt_id];
+ StatementProcessor executer(dbparams, stmt_id, exec_desc);
+
+ int param_id = 0;
+ if (sqlite3_bind_int(stmt, ++param_id, dbparams.updated_zone_id)
+ != SQLITE_OK) {
+ isc_throw(DataSourceError, "failed to bind SQLite3 parameter: " <<
+ sqlite3_errmsg(dbparams.db_));
+ }
+ const size_t column_count =
+ sizeof(update_params) / sizeof(update_params[0]);
+ for (int i = 0; i < column_count; ++i) {
+ if (sqlite3_bind_text(stmt, ++param_id, update_params[i].c_str(), -1,
+ SQLITE_TRANSIENT) != SQLITE_OK) {
+ isc_throw(DataSourceError, "failed to bind SQLite3 parameter: " <<
+ sqlite3_errmsg(dbparams.db_));
+ }
+ }
+ executer.exec();
+}
+}
+
+void
+SQLite3Accessor::addRecordToZone(const string (&columns)[ADD_COLUMN_COUNT]) {
+ if (!dbparameters_->updating_zone) {
+ isc_throw(DataSourceError, "adding record to SQLite3 "
+ "data source without transaction");
+ }
+ doUpdate<const string (&)[DatabaseAccessor::ADD_COLUMN_COUNT]>(
+ *dbparameters_, ADD_RECORD, columns, "add record to zone");
+}
+
+void
+SQLite3Accessor::deleteRecordInZone(const string (¶ms)[DEL_PARAM_COUNT]) {
+ if (!dbparameters_->updating_zone) {
+ isc_throw(DataSourceError, "deleting record in SQLite3 "
+ "data source without transaction");
+ }
+ doUpdate<const string (&)[DatabaseAccessor::DEL_PARAM_COUNT]>(
+ *dbparameters_, DEL_RECORD, params, "delete record from zone");
}
}
diff --git a/src/lib/datasrc/sqlite3_accessor.h b/src/lib/datasrc/sqlite3_accessor.h
index f75207a..fae249b 100644
--- a/src/lib/datasrc/sqlite3_accessor.h
+++ b/src/lib/datasrc/sqlite3_accessor.h
@@ -21,6 +21,7 @@
#include <exceptions/exceptions.h>
#include <boost/enable_shared_from_this.hpp>
+#include <boost/scoped_ptr.hpp>
#include <string>
namespace isc {
@@ -52,8 +53,8 @@ struct SQLite3Parameters;
* According to the design, it doesn't interpret the data in any way, it just
* provides unified access to the DB.
*/
-class SQLite3Database : public DatabaseAccessor,
- public boost::enable_shared_from_this<SQLite3Database> {
+class SQLite3Accessor : public DatabaseAccessor,
+ public boost::enable_shared_from_this<SQLite3Accessor> {
public:
/**
* \brief Constructor
@@ -68,14 +69,30 @@ public:
* file can contain multiple classes of data, single database can
* provide only one class).
*/
- SQLite3Database(const std::string& filename,
+ SQLite3Accessor(const std::string& filename,
const isc::dns::RRClass& rrclass);
+
+ /**
+ * \brief Constructor
+ *
+ * Same as the other version, but takes rrclass as a bare string.
+ * we should obsolete the other version and unify the constructor to
+ * this version; the SQLite3Accessor is expected to be "dumb" and
+ * shouldn't care about DNS specific information such as RRClass.
+ */
+ SQLite3Accessor(const std::string& filename, const std::string& rrclass);
+
/**
* \brief Destructor
*
* Closes the database.
*/
- ~SQLite3Database();
+ ~SQLite3Accessor();
+
+ /// This implementation internally opens a new sqlite3 database for the
+ /// same file name specified in the constructor of the original accessor.
+ virtual boost::shared_ptr<DatabaseAccessor> clone();
+
/**
* \brief Look up a zone
*
@@ -85,60 +102,66 @@ public:
*
* \exception SQLite3Error if something about the database is broken.
*
- * \param name The name of zone to look up
+ * \param name The (fully qualified) domain name of zone to look up
* \return The pair contains if the lookup was successful in the first
* element and the zone id in the second if it was.
*/
- virtual std::pair<bool, int> getZone(const isc::dns::Name& name) const;
+ virtual std::pair<bool, int> getZone(const std::string& name) const;
- /// \brief Implementation of DatabaseAbstraction::getAllRecords
- virtual IteratorContextPtr getAllRecords(const isc::dns::Name&,
- int id) const;
- /**
- * \brief Start a new search for the given name in the given zone.
+ /** \brief Look up all resource records for a name
*
- * This implements the searchForRecords from DatabaseConnection.
- * This particular implementation does not raise DataSourceError.
+ * This implements the getRecords() method from DatabaseAccessor
*
- * \exception DataSourceError when sqlite3_bind_int() or
- * sqlite3_bind_text() fails
+ * \exception SQLite3Error if there is an sqlite3 error when performing
+ * the query
*
- * \param zone_id The zone to seach in, as returned by getZone()
- * \param name The name to find records for
+ * \param name the name to look up
+ * \param id the zone id, as returned by getZone()
+ * \param subdomains Match subdomains instead of the name.
+ * \return Iterator that contains all records with the given name
*/
- virtual void searchForRecords(int zone_id, const std::string& name);
+ virtual IteratorContextPtr getRecords(const std::string& name,
+ int id,
+ bool subdomains = false) const;
- /**
- * \brief Retrieve the next record from the search started with
- * searchForRecords
- *
- * This implements the getNextRecord from DatabaseConnection.
- * See the documentation there for more information.
- *
- * If this method raises an exception, the contents of columns are undefined.
+ /** \brief Look up all resource records for a zone
*
- * \exception DataSourceError if there is an error returned by sqlite_step()
- * When this exception is raised, the current
- * search as initialized by searchForRecords() is
- * NOT reset, and the caller is expected to take
- * care of that.
- * \param columns This vector will be cleared, and the fields of the record will
- * be appended here as strings (in the order rdtype, ttl, sigtype,
- * and rdata). If there was no data (i.e. if this call returns
- * false), the vector is untouched.
- * \return true if there was a next record, false if there was not
- */
- virtual bool getNextRecord(std::string columns[], size_t column_count);
-
- /**
- * \brief Resets any state created by searchForRecords
+ * This implements the getRecords() method from DatabaseAccessor
*
- * This implements the resetSearch from DatabaseConnection.
- * See the documentation there for more information.
+ * \exception SQLite3Error if there is an sqlite3 error when performing
+ * the query
*
- * This function never throws.
+ * \param id the zone id, as returned by getZone()
+ * \return Iterator that contains all records in the given zone
*/
- virtual void resetSearch();
+ virtual IteratorContextPtr getAllRecords(int id) const;
+
+ virtual std::pair<bool, int> startUpdateZone(const std::string& zone_name,
+ bool replace);
+
+ /// \note we are quite impatient here: it's quite possible that the COMMIT
+ /// fails due to other process performing SELECT on the same database
+ /// (consider the case where COMMIT is done by xfrin or dynamic update
+ /// server while an authoritative server is busy reading the DB).
+ /// In a future version we should probably need to introduce some retry
+ /// attempt and/or increase timeout before giving up the COMMIT, even
+ /// if it still doesn't guarantee 100% success. Right now this
+ /// implementation throws a \c DataSourceError exception in such a case.
+ virtual void commitUpdateZone();
+
+ /// \note In SQLite3 rollback can fail if there's another unfinished
+ /// statement is performed for the same database structure.
+ /// Although it's not expected to happen in our expected usage, it's not
+ /// guaranteed to be prevented at the API level. If it ever happens, this
+ /// method throws a \c DataSourceError exception. It should be
+ /// considered a bug of the higher level application program.
+ virtual void rollbackUpdateZone();
+
+ virtual void addRecordToZone(
+ const std::string (&columns)[ADD_COLUMN_COUNT]);
+
+ virtual void deleteRecordInZone(
+ const std::string (¶ms)[DEL_PARAM_COUNT]);
/// The SQLite3 implementation of this method returns a string starting
/// with a fixed prefix of "sqlite3_" followed by the DB file name
@@ -149,7 +172,9 @@ public:
private:
/// \brief Private database data
- SQLite3Parameters* dbparameters_;
+ boost::scoped_ptr<SQLite3Parameters> dbparameters_;
+ /// \brief The filename of the DB (necessary for clone())
+ const std::string filename_;
/// \brief The class for which the queries are done
const std::string class_;
/// \brief Opens the database
@@ -165,4 +190,8 @@ private:
}
}
-#endif
+#endif // __DATASRC_SQLITE3_CONNECTION_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/datasrc/sqlite3_datasrc.cc b/src/lib/datasrc/sqlite3_datasrc.cc
index 18ee929..03b057c 100644
--- a/src/lib/datasrc/sqlite3_datasrc.cc
+++ b/src/lib/datasrc/sqlite3_datasrc.cc
@@ -26,6 +26,8 @@
#include <dns/rrset.h>
#include <dns/rrsetlist.h>
+#define SQLITE_SCHEMA_VERSION 1
+
using namespace std;
using namespace isc::dns;
using namespace isc::dns::rdata;
@@ -77,6 +79,8 @@ const char* const SCHEMA_LIST[] = {
NULL
};
+const char* const q_version_str = "SELECT version FROM schema_version";
+
const char* const q_zone_str = "SELECT id FROM zones WHERE name=?1";
const char* const q_record_str = "SELECT rdtype, ttl, sigtype, rdata "
@@ -254,7 +258,7 @@ Sqlite3DataSrc::findRecords(const Name& name, const RRType& rdtype,
}
break;
}
-
+
sqlite3_reset(query);
sqlite3_clear_bindings(query);
@@ -295,7 +299,7 @@ Sqlite3DataSrc::findRecords(const Name& name, const RRType& rdtype,
//
sqlite3_reset(dbparameters->q_count_);
sqlite3_clear_bindings(dbparameters->q_count_);
-
+
rc = sqlite3_bind_int(dbparameters->q_count_, 1, zone_id);
if (rc != SQLITE_OK) {
isc_throw(Sqlite3Error, "Could not bind zone ID " << zone_id <<
@@ -653,29 +657,90 @@ prepare(sqlite3* const db, const char* const statement) {
return (prepared);
}
-void
-checkAndSetupSchema(Sqlite3Initializer* initializer) {
- sqlite3* const db = initializer->params_.db_;
+// small function to sleep for 0.1 seconds, needed when waiting for
+// exclusive database locks (which should only occur on startup, and only
+// when the database has not been created yet)
+void do_sleep() {
+ struct timespec req;
+ req.tv_sec = 0;
+ req.tv_nsec = 100000000;
+ nanosleep(&req, NULL);
+}
+// returns the schema version if the schema version table exists
+// returns -1 if it does not
+int check_schema_version(sqlite3* db) {
sqlite3_stmt* prepared = NULL;
- if (sqlite3_prepare_v2(db, "SELECT version FROM schema_version", -1,
- &prepared, NULL) == SQLITE_OK &&
- sqlite3_step(prepared) == SQLITE_ROW) {
- initializer->params_.version_ = sqlite3_column_int(prepared, 0);
- sqlite3_finalize(prepared);
- } else {
- logger.info(DATASRC_SQLITE_SETUP);
- if (prepared != NULL) {
- sqlite3_finalize(prepared);
+ // At this point in time, the database might be exclusively locked, in
+ // which case even prepare() will return BUSY, so we may need to try a
+ // few times
+ for (size_t i = 0; i < 50; ++i) {
+ int rc = sqlite3_prepare_v2(db, q_version_str, -1, &prepared, NULL);
+ if (rc == SQLITE_ERROR) {
+ // this is the error that is returned when the table does not
+ // exist
+ return (-1);
+ } else if (rc == SQLITE_OK) {
+ break;
+ } else if (rc != SQLITE_BUSY || i == 50) {
+ isc_throw(Sqlite3Error, "Unable to prepare version query: "
+ << rc << " " << sqlite3_errmsg(db));
}
+ do_sleep();
+ }
+ if (sqlite3_step(prepared) != SQLITE_ROW) {
+ isc_throw(Sqlite3Error,
+ "Unable to query version: " << sqlite3_errmsg(db));
+ }
+ int version = sqlite3_column_int(prepared, 0);
+ sqlite3_finalize(prepared);
+ return (version);
+}
+
+// return db version
+int create_database(sqlite3* db) {
+ // try to get an exclusive lock. Once that is obtained, do the version
+ // check *again*, just in case this process was racing another
+ //
+ // try for 5 secs (50*0.1)
+ int rc;
+ logger.info(DATASRC_SQLITE_SETUP);
+ for (size_t i = 0; i < 50; ++i) {
+ rc = sqlite3_exec(db, "BEGIN EXCLUSIVE TRANSACTION", NULL, NULL,
+ NULL);
+ if (rc == SQLITE_OK) {
+ break;
+ } else if (rc != SQLITE_BUSY || i == 50) {
+ isc_throw(Sqlite3Error, "Unable to acquire exclusive lock "
+ "for database creation: " << sqlite3_errmsg(db));
+ }
+ do_sleep();
+ }
+ int schema_version = check_schema_version(db);
+ if (schema_version == -1) {
for (int i = 0; SCHEMA_LIST[i] != NULL; ++i) {
if (sqlite3_exec(db, SCHEMA_LIST[i], NULL, NULL, NULL) !=
SQLITE_OK) {
isc_throw(Sqlite3Error,
- "Failed to set up schema " << SCHEMA_LIST[i]);
+ "Failed to set up schema " << SCHEMA_LIST[i]);
}
}
+ sqlite3_exec(db, "COMMIT TRANSACTION", NULL, NULL, NULL);
+ return (SQLITE_SCHEMA_VERSION);
+ } else {
+ return (schema_version);
+ }
+}
+
+void
+checkAndSetupSchema(Sqlite3Initializer* initializer) {
+ sqlite3* const db = initializer->params_.db_;
+
+ int schema_version = check_schema_version(db);
+ if (schema_version != SQLITE_SCHEMA_VERSION) {
+ schema_version = create_database(db);
}
+ initializer->params_.version_ = schema_version;
initializer->params_.q_zone_ = prepare(db, q_zone_str);
initializer->params_.q_record_ = prepare(db, q_record_str);
diff --git a/src/lib/datasrc/tests/Makefile.am b/src/lib/datasrc/tests/Makefile.am
index a913818..48cbc76 100644
--- a/src/lib/datasrc/tests/Makefile.am
+++ b/src/lib/datasrc/tests/Makefile.am
@@ -1,8 +1,12 @@
+SUBDIRS = . testdata
+
AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
AM_CPPFLAGS += -I$(top_builddir)/src/lib/dns -I$(top_srcdir)/src/lib/dns
AM_CPPFLAGS += $(BOOST_INCLUDES)
AM_CPPFLAGS += $(SQLITE_CFLAGS)
-AM_CPPFLAGS += -DTEST_DATA_DIR=\"$(srcdir)/testdata\"
+AM_CPPFLAGS += -DTEST_DATA_DIR=\"$(abs_srcdir)/testdata\"
+AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_builddir)/testdata\"
+AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
AM_CXXFLAGS = $(B10_CXXFLAGS)
@@ -61,3 +65,4 @@ EXTRA_DIST += testdata/sql1.example.com.signed
EXTRA_DIST += testdata/sql2.example.com.signed
EXTRA_DIST += testdata/test-root.sqlite3
EXTRA_DIST += testdata/test.sqlite3
+EXTRA_DIST += testdata/rwtest.sqlite3
diff --git a/src/lib/datasrc/tests/client_unittest.cc b/src/lib/datasrc/tests/client_unittest.cc
index 1a88f18..5b2c91a 100644
--- a/src/lib/datasrc/tests/client_unittest.cc
+++ b/src/lib/datasrc/tests/client_unittest.cc
@@ -32,6 +32,9 @@ public:
virtual FindResult findZone(const isc::dns::Name&) const {
return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
}
+ virtual ZoneUpdaterPtr getUpdater(const isc::dns::Name&, bool) const {
+ return (ZoneUpdaterPtr());
+ }
};
class ClientTest : public ::testing::Test {
diff --git a/src/lib/datasrc/tests/database_unittest.cc b/src/lib/datasrc/tests/database_unittest.cc
index 7627aae..4ed9f12 100644
--- a/src/lib/datasrc/tests/database_unittest.cc
+++ b/src/lib/datasrc/tests/database_unittest.cc
@@ -12,6 +12,8 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
+#include <boost/foreach.hpp>
+
#include <gtest/gtest.h>
#include <dns/name.h>
@@ -23,6 +25,7 @@
#include <datasrc/zone.h>
#include <datasrc/data_source.h>
#include <datasrc/iterator.h>
+#include <datasrc/sqlite3_accessor.h>
#include <testutils/dnsmessage_test.h>
@@ -35,6 +38,139 @@ using namespace isc::dns;
namespace {
+// Imaginary zone IDs used in the mock accessor below.
+const int READONLY_ZONE_ID = 42;
+const int WRITABLE_ZONE_ID = 4200;
+
+// Commonly used test data
+const char* const TEST_RECORDS[][5] = {
+ // some plain data
+ {"www.example.org.", "A", "3600", "", "192.0.2.1"},
+ {"www.example.org.", "AAAA", "3600", "", "2001:db8::1"},
+ {"www.example.org.", "AAAA", "3600", "", "2001:db8::2"},
+
+ {"www2.example.org.", "A", "3600", "", "192.0.2.1"},
+ {"www2.example.org.","AAAA", "3600", "", "2001:db8::1"},
+ {"www2.example.org.", "A", "3600", "", "192.0.2.2"},
+
+ {"cname.example.org.", "CNAME", "3600", "", "www.example.org."},
+
+ // some DNSSEC-'signed' data
+ {"signed1.example.org.", "A", "3600", "", "192.0.2.1"},
+ {"signed1.example.org.", "RRSIG", "3600", "", "A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
+
+ {"signed1.example.org.", "RRSIG", "3600", "", "A 5 3 3600 20000101000000 20000201000000 12346 example.org. FAKEFAKEFAKE"},
+ {"signed1.example.org.", "AAAA", "3600", "", "2001:db8::1"},
+ {"signed1.example.org.", "AAAA", "3600", "", "2001:db8::2"},
+ {"signed1.example.org.", "RRSIG", "3600", "", "AAAA 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
+
+ {"signedcname1.example.org.", "CNAME", "3600", "", "www.example.org."},
+ {"signedcname1.example.org.", "RRSIG", "3600", "", "CNAME 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
+
+ // special case might fail; sig is for cname, which isn't there (should be ignored)
+ // (ignoring of 'normal' other type is done above by www.)
+ {"acnamesig1.example.org.", "A", "3600", "", "192.0.2.1"},
+ {"acnamesig1.example.org.", "RRSIG", "3600", "", "A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
+ {"acnamesig1.example.org.", "RRSIG", "3600", "", "CNAME 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
+
+ // let's pretend we have a database that is not careful
+ // about the order in which it returns data
+ {"signed2.example.org.", "RRSIG", "3600", "", "A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
+ {"signed2.example.org.", "AAAA", "3600", "", "2001:db8::2"},
+ {"signed2.example.org.", "RRSIG", "3600", "", "A 5 3 3600 20000101000000 20000201000000 12346 example.org. FAKEFAKEFAKE"},
+ {"signed2.example.org.", "A", "3600", "", "192.0.2.1"},
+ {"signed2.example.org.", "RRSIG", "3600", "", "AAAA 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
+ {"signed2.example.org.", "AAAA", "3600", "", "2001:db8::1"},
+
+ {"signedcname2.example.org.", "RRSIG", "3600", "", "CNAME 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
+ {"signedcname2.example.org.", "CNAME", "3600", "", "www.example.org."},
+
+ {"acnamesig2.example.org.", "RRSIG", "3600", "", "CNAME 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
+ {"acnamesig2.example.org.", "A", "3600", "", "192.0.2.1"},
+ {"acnamesig2.example.org.", "RRSIG", "3600", "", "A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
+
+ {"acnamesig3.example.org.", "RRSIG", "3600", "", "CNAME 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
+ {"acnamesig3.example.org.", "RRSIG", "3600", "", "A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
+ {"acnamesig3.example.org.", "A", "3600", "", "192.0.2.1"},
+
+ {"ttldiff1.example.org.", "A", "3600", "", "192.0.2.1"},
+ {"ttldiff1.example.org.", "A", "360", "", "192.0.2.2"},
+
+ {"ttldiff2.example.org.", "A", "360", "", "192.0.2.1"},
+ {"ttldiff2.example.org.", "A", "3600", "", "192.0.2.2"},
+
+ // also add some intentionally bad data
+ {"badcname1.example.org.", "A", "3600", "", "192.0.2.1"},
+ {"badcname1.example.org.", "CNAME", "3600", "", "www.example.org."},
+
+ {"badcname2.example.org.", "CNAME", "3600", "", "www.example.org."},
+ {"badcname2.example.org.", "A", "3600", "", "192.0.2.1"},
+
+ {"badcname3.example.org.", "CNAME", "3600", "", "www.example.org."},
+ {"badcname3.example.org.", "CNAME", "3600", "", "www.example2.org."},
+
+ {"badrdata.example.org.", "A", "3600", "", "bad"},
+
+ {"badtype.example.org.", "BAD_TYPE", "3600", "", "192.0.2.1"},
+
+ {"badttl.example.org.", "A", "badttl", "", "192.0.2.1"},
+
+ {"badsig.example.org.", "A", "badttl", "", "192.0.2.1"},
+ {"badsig.example.org.", "RRSIG", "3600", "", "A 5 3 3600 somebaddata 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
+
+ {"badsigtype.example.org.", "A", "3600", "", "192.0.2.1"},
+ {"badsigtype.example.org.", "RRSIG", "3600", "TXT", "A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
+
+ // Data for testing delegation (with NS and DNAME)
+ {"delegation.example.org.", "NS", "3600", "", "ns.example.com."},
+ {"delegation.example.org.", "NS", "3600", "",
+ "ns.delegation.example.org."},
+ {"delegation.example.org.", "RRSIG", "3600", "", "NS 5 3 3600 "
+ "20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
+ {"ns.delegation.example.org.", "A", "3600", "", "192.0.2.1"},
+ {"deep.below.delegation.example.org.", "A", "3600", "", "192.0.2.1"},
+
+ {"dname.example.org.", "A", "3600", "", "192.0.2.1"},
+ {"dname.example.org.", "DNAME", "3600", "", "dname.example.com."},
+ {"dname.example.org.", "RRSIG", "3600", "",
+ "DNAME 5 3 3600 20000101000000 20000201000000 12345 "
+ "example.org. FAKEFAKEFAKE"},
+
+ {"below.dname.example.org.", "A", "3600", "", "192.0.2.1"},
+
+ // Broken NS
+ {"brokenns1.example.org.", "A", "3600", "", "192.0.2.1"},
+ {"brokenns1.example.org.", "NS", "3600", "", "ns.example.com."},
+
+ {"brokenns2.example.org.", "NS", "3600", "", "ns.example.com."},
+ {"brokenns2.example.org.", "A", "3600", "", "192.0.2.1"},
+
+ // Now double DNAME, to test failure mode
+ {"baddname.example.org.", "DNAME", "3600", "", "dname1.example.com."},
+ {"baddname.example.org.", "DNAME", "3600", "", "dname2.example.com."},
+
+ // Put some data into apex (including NS) so we can check our NS
+ // doesn't break anything
+ {"example.org.", "NS", "3600", "", "ns.example.com."},
+ {"example.org.", "A", "3600", "", "192.0.2.1"},
+ {"example.org.", "RRSIG", "3600", "", "NS 5 3 3600 20000101000000 "
+ "20000201000000 12345 example.org. FAKEFAKEFAKE"},
+
+ // This is because of empty domain test
+ {"a.b.example.org.", "A", "3600", "", "192.0.2.1"},
+
+ // Something for wildcards
+ {"*.wild.example.org.", "A", "3600", "", "192.0.2.5"},
+ {"*.wild.example.org.", "RRSIG", "3600", "A", "A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
+ {"cancel.here.wild.example.org.", "AAAA", "3600", "", "2001:db8::5"},
+ {"delegatedwild.example.org.", "NS", "3600", "", "ns.example.com."},
+ {"*.delegatedwild.example.org.", "A", "3600", "", "192.0.2.5"},
+ {"wild.*.foo.example.org.", "A", "3600", "", "192.0.2.5"},
+ {"wild.*.foo.*.bar.example.org.", "A", "3600", "", "192.0.2.5"},
+
+ {NULL, NULL, NULL, NULL, NULL},
+};
+
/*
* An accessor with minimum implementation, keeping the original
* "NotImplemented" methods.
@@ -44,49 +180,151 @@ public:
NopAccessor() : database_name_("mock_database")
{ }
- virtual std::pair<bool, int> getZone(const Name& name) const {
- if (name == Name("example.org")) {
- return (std::pair<bool, int>(true, 42));
- } else if (name == Name("null.example.org")) {
+ virtual std::pair<bool, int> getZone(const std::string& name) const {
+ if (name == "example.org.") {
+ return (std::pair<bool, int>(true, READONLY_ZONE_ID));
+ } else if (name == "null.example.org.") {
return (std::pair<bool, int>(true, 13));
- } else if (name == Name("empty.example.org")) {
+ } else if (name == "empty.example.org.") {
return (std::pair<bool, int>(true, 0));
- } else if (name == Name("bad.example.org")) {
+ } else if (name == "bad.example.org.") {
return (std::pair<bool, int>(true, -1));
} else {
return (std::pair<bool, int>(false, 0));
}
}
+ virtual shared_ptr<DatabaseAccessor> clone() {
+ return (shared_ptr<DatabaseAccessor>()); // bogus data, but unused
+ }
+
+ virtual std::pair<bool, int> startUpdateZone(const std::string&, bool) {
+ // return dummy value. unused anyway.
+ return (pair<bool, int>(true, 0));
+ }
+ virtual void commitUpdateZone() {}
+ virtual void rollbackUpdateZone() {}
+ virtual void addRecordToZone(const string (&)[ADD_COLUMN_COUNT]) {}
+ virtual void deleteRecordInZone(const string (&)[DEL_PARAM_COUNT]) {}
+
virtual const std::string& getDBName() const {
return (database_name_);
}
- // These are just to compile, they won't be called
- virtual void searchForRecords(int, const std::string&) { }
- virtual bool getNextRecord(string*, size_t) {
- return (false);
+ virtual IteratorContextPtr getRecords(const std::string&, int, bool)
+ const
+ {
+ isc_throw(isc::NotImplemented,
+ "This database datasource can't be iterated");
+ }
+
+ virtual IteratorContextPtr getAllRecords(int) const {
+ isc_throw(isc::NotImplemented,
+ "This database datasource can't be iterated");
}
- virtual void resetSearch() { }
+
private:
const std::string database_name_;
};
/*
- * A virtual database connection that pretends it contains single zone --
+ * A virtual database accessor that pretends it contains single zone --
* example.org.
*
* It has the same getZone method as NopConnection, but it provides
* implementation of the optional functionality.
*/
class MockAccessor : public NopAccessor {
+ // Type of mock database "row"s
+ typedef std::map<std::string, std::vector< std::vector<std::string> > >
+ Domains;
+
public:
- MockAccessor() : cur_record(0), search_running_(false)
- {
+ MockAccessor() : rollbacked_(false) {
+ readonly_records_ = &readonly_records_master_;
+ update_records_ = &update_records_master_;
+ empty_records_ = &empty_records_master_;
fillData();
}
+
+ virtual shared_ptr<DatabaseAccessor> clone() {
+ shared_ptr<MockAccessor> cloned_accessor(new MockAccessor());
+ cloned_accessor->readonly_records_ = &readonly_records_master_;
+ cloned_accessor->update_records_ = &update_records_master_;
+ cloned_accessor->empty_records_ = &empty_records_master_;
+ latest_clone_ = cloned_accessor;
+ return (cloned_accessor);
+ }
+
private:
+ class MockNameIteratorContext : public IteratorContext {
+ public:
+ MockNameIteratorContext(const MockAccessor& mock_accessor, int zone_id,
+ const std::string& name, bool subdomains) :
+ searched_name_(name), cur_record_(0)
+ {
+ // 'hardcoded' names to trigger exceptions
+ // On these names some exceptions are thrown, to test the robustness
+ // of the find() method.
+ if (searched_name_ == "dsexception.in.search.") {
+ isc_throw(DataSourceError, "datasource exception on search");
+ } else if (searched_name_ == "iscexception.in.search.") {
+ isc_throw(isc::Exception, "isc exception on search");
+ } else if (searched_name_ == "basicexception.in.search.") {
+ throw std::exception();
+ }
+
+ cur_record_ = 0;
+ const Domains& cur_records = mock_accessor.getMockRecords(zone_id);
+ if (cur_records.count(name) > 0) {
+ // we're not aiming for efficiency in this test, simply
+ // copy the relevant vector from records
+ cur_name = cur_records.find(name)->second;
+ } else if (subdomains) {
+ cur_name.clear();
+ // Just walk everything and check if it is a subdomain.
+ // If it is, just copy all data from there.
+ for (Domains::const_iterator i(cur_records.begin());
+ i != cur_records.end(); ++i) {
+ const Name local(i->first);
+ if (local.compare(Name(name)).getRelation() ==
+ isc::dns::NameComparisonResult::SUBDOMAIN) {
+ cur_name.insert(cur_name.end(), i->second.begin(),
+ i->second.end());
+ }
+ }
+ } else {
+ cur_name.clear();
+ }
+ }
+
+ virtual bool getNext(std::string (&columns)[COLUMN_COUNT]) {
+ if (searched_name_ == "dsexception.in.getnext.") {
+ isc_throw(DataSourceError, "datasource exception on getnextrecord");
+ } else if (searched_name_ == "iscexception.in.getnext.") {
+ isc_throw(isc::Exception, "isc exception on getnextrecord");
+ } else if (searched_name_ == "basicexception.in.getnext.") {
+ throw std::exception();
+ }
+
+ if (cur_record_ < cur_name.size()) {
+ for (size_t i = 0; i < COLUMN_COUNT; ++i) {
+ columns[i] = cur_name[cur_record_][i];
+ }
+ cur_record_++;
+ return (true);
+ } else {
+ return (false);
+ }
+ }
+
+ private:
+ const std::string searched_name_;
+ int cur_record_;
+ std::vector< std::vector<std::string> > cur_name;
+ };
+
class MockIteratorContext : public IteratorContext {
private:
int step;
@@ -94,10 +332,7 @@ private:
MockIteratorContext() :
step(0)
{ }
- virtual bool getNext(string data[], size_t size) {
- if (size != DatabaseAccessor::COLUMN_COUNT) {
- isc_throw(DataSourceError, "Wrong column count in getNextRecord");
- }
+ virtual bool getNext(string (&data)[COLUMN_COUNT]) {
switch (step ++) {
case 0:
data[DatabaseAccessor::NAME_COLUMN] = "example.org";
@@ -140,10 +375,7 @@ private:
};
class EmptyIteratorContext : public IteratorContext {
public:
- virtual bool getNext(string[], size_t size) {
- if (size != DatabaseAccessor::COLUMN_COUNT) {
- isc_throw(DataSourceError, "Wrong column count in getNextRecord");
- }
+ virtual bool getNext(string(&)[COLUMN_COUNT]) {
return (false);
}
};
@@ -154,10 +386,7 @@ private:
BadIteratorContext() :
step(0)
{ }
- virtual bool getNext(string data[], size_t size) {
- if (size != DatabaseAccessor::COLUMN_COUNT) {
- isc_throw(DataSourceError, "Wrong column count in getNextRecord");
- }
+ virtual bool getNext(string (&data)[COLUMN_COUNT]) {
switch (step ++) {
case 0:
data[DatabaseAccessor::NAME_COLUMN] = "x.example.org";
@@ -180,8 +409,8 @@ private:
}
};
public:
- virtual IteratorContextPtr getAllRecords(const Name&, int id) const {
- if (id == 42) {
+ virtual IteratorContextPtr getAllRecords(int id) const {
+ if (id == READONLY_ZONE_ID) {
return (IteratorContextPtr(new MockIteratorContext()));
} else if (id == 13) {
return (IteratorContextPtr());
@@ -194,110 +423,181 @@ public:
}
}
- virtual void searchForRecords(int zone_id, const std::string& name) {
- search_running_ = true;
-
- // 'hardcoded' name to trigger exceptions (for testing
- // the error handling of find() (the other on is below in
- // if the name is "exceptiononsearch" it'll raise an exception here
- if (name == "dsexception.in.search.") {
- isc_throw(DataSourceError, "datasource exception on search");
- } else if (name == "iscexception.in.search.") {
- isc_throw(isc::Exception, "isc exception on search");
- } else if (name == "basicexception.in.search.") {
- throw std::exception();
- }
- searched_name_ = name;
-
- // we're not aiming for efficiency in this test, simply
- // copy the relevant vector from records
- cur_record = 0;
- if (zone_id == 42) {
- if (records.count(name) > 0) {
- cur_name = records.find(name)->second;
- } else {
- cur_name.clear();
- }
+ virtual IteratorContextPtr getRecords(const std::string& name, int id,
+ bool subdomains) const
+ {
+ if (id == READONLY_ZONE_ID || id == WRITABLE_ZONE_ID) {
+ return (IteratorContextPtr(
+ new MockNameIteratorContext(*this, id, name,
+ subdomains)));
} else {
- cur_name.clear();
+ isc_throw(isc::Unexpected, "Unknown zone ID");
}
- };
+ }
- virtual bool getNextRecord(std::string columns[], size_t column_count) {
- if (searched_name_ == "dsexception.in.getnext.") {
- isc_throw(DataSourceError, "datasource exception on getnextrecord");
- } else if (searched_name_ == "iscexception.in.getnext.") {
- isc_throw(isc::Exception, "isc exception on getnextrecord");
- } else if (searched_name_ == "basicexception.in.getnext.") {
- throw std::exception();
+ virtual pair<bool, int> startUpdateZone(const std::string& zone_name,
+ bool replace)
+ {
+ const pair<bool, int> zone_info = getZone(zone_name);
+ if (!zone_info.first) {
+ return (pair<bool, int>(false, 0));
}
- if (column_count != DatabaseAccessor::COLUMN_COUNT) {
- isc_throw(DataSourceError, "Wrong column count in getNextRecord");
- }
- if (cur_record < cur_name.size()) {
- for (size_t i = 0; i < column_count; ++i) {
- columns[i] = cur_name[cur_record][i];
- }
- cur_record++;
- return (true);
+ // Prepare the record set for update. If replacing the existing one,
+ // we use an empty set; otherwise we use a writable copy of the
+ // original.
+ if (replace) {
+ update_records_->clear();
} else {
- resetSearch();
- return (false);
+ *update_records_ = *readonly_records_;
}
- };
- virtual void resetSearch() {
- search_running_ = false;
+ return (pair<bool, int>(true, WRITABLE_ZONE_ID));
+ }
+ virtual void commitUpdateZone() {
+ *readonly_records_ = *update_records_;
+ }
+ virtual void rollbackUpdateZone() {
+ // Special hook: if something with a name of "throw.example.org"
+ // has been added, trigger an imaginary unexpected event with an
+ // exception.
+ if (update_records_->count("throw.example.org.") > 0) {
+ isc_throw(DataSourceError, "unexpected failure in rollback");
+ }
+
+ rollbacked_ = true;
+ }
+ virtual void addRecordToZone(const string (&columns)[ADD_COLUMN_COUNT]) {
+ // Copy the current value to cur_name. If it doesn't exist,
+ // operator[] will create a new one.
+ cur_name_ = (*update_records_)[columns[DatabaseAccessor::ADD_NAME]];
+
+ vector<string> record_columns;
+ record_columns.push_back(columns[DatabaseAccessor::ADD_TYPE]);
+ record_columns.push_back(columns[DatabaseAccessor::ADD_TTL]);
+ record_columns.push_back(columns[DatabaseAccessor::ADD_SIGTYPE]);
+ record_columns.push_back(columns[DatabaseAccessor::ADD_RDATA]);
+ record_columns.push_back(columns[DatabaseAccessor::ADD_NAME]);
+
+ // copy back the added entry
+ cur_name_.push_back(record_columns);
+ (*update_records_)[columns[DatabaseAccessor::ADD_NAME]] = cur_name_;
+
+ // remember this one so that test cases can check it.
+ copy(columns, columns + DatabaseAccessor::ADD_COLUMN_COUNT,
+ columns_lastadded_);
+ }
+
+ // Helper predicate class used in deleteRecordInZone().
+ struct deleteMatch {
+ deleteMatch(const string& type, const string& rdata) :
+ type_(type), rdata_(rdata)
+ {}
+ bool operator()(const vector<string>& row) const {
+ return (row[0] == type_ && row[3] == rdata_);
+ }
+ const string& type_;
+ const string& rdata_;
};
- bool searchRunning() const {
- return (search_running_);
+ virtual void deleteRecordInZone(const string (¶ms)[DEL_PARAM_COUNT]) {
+ vector<vector<string> >& records =
+ (*update_records_)[params[DatabaseAccessor::DEL_NAME]];
+ records.erase(remove_if(records.begin(), records.end(),
+ deleteMatch(
+ params[DatabaseAccessor::DEL_TYPE],
+ params[DatabaseAccessor::DEL_RDATA])),
+ records.end());
+ if (records.empty()) {
+ (*update_records_).erase(params[DatabaseAccessor::DEL_NAME]);
+ }
+ }
+
+ //
+ // Helper methods to keep track of some update related activities
+ //
+ bool isRollbacked() const {
+ return (rollbacked_);
}
+
+ const string* getLastAdded() const {
+ return (columns_lastadded_);
+ }
+
+ // This allows the test code to get the accessor used in an update context
+ shared_ptr<const MockAccessor> getLatestClone() const {
+ return (latest_clone_);
+ }
+
private:
- std::map<std::string, std::vector< std::vector<std::string> > > records;
- // used as internal index for getNextRecord()
- size_t cur_record;
+ // The following member variables are storage and/or update work space
+ // of the test zone. The "master"s are the real objects that contain
+ // the data, and they are shared among all accessors cloned from
+ // an initially created one. The pointer members allow the sharing.
+ // "readonly" is for normal lookups. "update" is the workspace for
+ // updates. When update starts it will be initialized either as an
+ // empty set (when replacing the entire zone) or as a copy of the
+ // "readonly" one. "empty" is a sentinel to produce negative results.
+ Domains readonly_records_master_;
+ Domains* readonly_records_;
+ Domains update_records_master_;
+ Domains* update_records_;
+ const Domains empty_records_master_;
+ const Domains* empty_records_;
+
+ // used as temporary storage during the building of the fake data
+
// used as temporary storage after searchForRecord() and during
// getNextRecord() calls, as well as during the building of the
// fake data
- std::vector< std::vector<std::string> > cur_name;
+ std::vector< std::vector<std::string> > cur_name_;
- // This boolean is used to make sure find() calls resetSearch
- // when it encounters an error
- bool search_running_;
+ // The columns that were most recently added via addRecordToZone()
+ string columns_lastadded_[ADD_COLUMN_COUNT];
- // We store the name passed to searchForRecords, so we can
- // hardcode some exceptions into getNextRecord
- std::string searched_name_;
+ // Whether rollback operation has been performed for the database.
+ // Not useful except for purely testing purpose.
+ bool rollbacked_;
+
+ // Remember the mock accessor that was last cloned
+ boost::shared_ptr<MockAccessor> latest_clone_;
+
+ const Domains& getMockRecords(int zone_id) const {
+ if (zone_id == READONLY_ZONE_ID) {
+ return (*readonly_records_);
+ } else if (zone_id == WRITABLE_ZONE_ID) {
+ return (*update_records_);
+ }
+ return (*empty_records_);
+ }
// Adds one record to the current name in the database
// The actual data will not be added to 'records' until
// addCurName() is called
- void addRecord(const std::string& name,
- const std::string& type,
+ void addRecord(const std::string& type,
+ const std::string& ttl,
const std::string& sigtype,
const std::string& rdata) {
std::vector<std::string> columns;
- columns.push_back(name);
columns.push_back(type);
+ columns.push_back(ttl);
columns.push_back(sigtype);
columns.push_back(rdata);
- cur_name.push_back(columns);
+ cur_name_.push_back(columns);
}
// Adds all records we just built with calls to addRecords
- // to the actual fake database. This will clear cur_name,
+ // to the actual fake database. This will clear cur_name_,
// so we can immediately start adding new records.
void addCurName(const std::string& name) {
- ASSERT_EQ(0, records.count(name));
+ ASSERT_EQ(0, readonly_records_->count(name));
// Append the name to all of them
for (std::vector<std::vector<std::string> >::iterator
- i(cur_name.begin()); i != cur_name.end(); ++ i) {
+ i(cur_name_.begin()); i != cur_name_.end(); ++ i) {
i->push_back(name);
}
- records[name] = cur_name;
- cur_name.clear();
+ (*readonly_records_)[name] = cur_name_;
+ cur_name_.clear();
}
// Fills the database with zone data.
@@ -311,165 +611,78 @@ private:
// might not come in 'normal' order)
// It shall immediately fail if you try to add the same name twice.
void fillData() {
- // some plain data
- addRecord("A", "3600", "", "192.0.2.1");
- addRecord("AAAA", "3600", "", "2001:db8::1");
- addRecord("AAAA", "3600", "", "2001:db8::2");
- addCurName("www.example.org.");
-
- addRecord("A", "3600", "", "192.0.2.1");
- addRecord("AAAA", "3600", "", "2001:db8::1");
- addRecord("A", "3600", "", "192.0.2.2");
- addCurName("www2.example.org.");
-
- addRecord("CNAME", "3600", "", "www.example.org.");
- addCurName("cname.example.org.");
-
- // some DNSSEC-'signed' data
- addRecord("A", "3600", "", "192.0.2.1");
- addRecord("RRSIG", "3600", "", "A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
- addRecord("RRSIG", "3600", "", "A 5 3 3600 20000101000000 20000201000000 12346 example.org. FAKEFAKEFAKE");
- addRecord("AAAA", "3600", "", "2001:db8::1");
- addRecord("AAAA", "3600", "", "2001:db8::2");
- addRecord("RRSIG", "3600", "", "AAAA 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
- addCurName("signed1.example.org.");
- addRecord("CNAME", "3600", "", "www.example.org.");
- addRecord("RRSIG", "3600", "", "CNAME 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
- addCurName("signedcname1.example.org.");
- // special case might fail; sig is for cname, which isn't there (should be ignored)
- // (ignoring of 'normal' other type is done above by www.)
- addRecord("A", "3600", "", "192.0.2.1");
- addRecord("RRSIG", "3600", "", "A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
- addRecord("RRSIG", "3600", "", "CNAME 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
- addCurName("acnamesig1.example.org.");
-
- // let's pretend we have a database that is not careful
- // about the order in which it returns data
- addRecord("RRSIG", "3600", "", "A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
- addRecord("AAAA", "3600", "", "2001:db8::2");
- addRecord("RRSIG", "3600", "", "A 5 3 3600 20000101000000 20000201000000 12346 example.org. FAKEFAKEFAKE");
- addRecord("A", "3600", "", "192.0.2.1");
- addRecord("RRSIG", "3600", "", "AAAA 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
- addRecord("AAAA", "3600", "", "2001:db8::1");
- addCurName("signed2.example.org.");
- addRecord("RRSIG", "3600", "", "CNAME 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
- addRecord("CNAME", "3600", "", "www.example.org.");
- addCurName("signedcname2.example.org.");
-
- addRecord("RRSIG", "3600", "", "CNAME 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
- addRecord("A", "3600", "", "192.0.2.1");
- addRecord("RRSIG", "3600", "", "A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
- addCurName("acnamesig2.example.org.");
-
- addRecord("RRSIG", "3600", "", "CNAME 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
- addRecord("RRSIG", "3600", "", "A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
- addRecord("A", "3600", "", "192.0.2.1");
- addCurName("acnamesig3.example.org.");
-
- addRecord("A", "3600", "", "192.0.2.1");
- addRecord("A", "360", "", "192.0.2.2");
- addCurName("ttldiff1.example.org.");
- addRecord("A", "360", "", "192.0.2.1");
- addRecord("A", "3600", "", "192.0.2.2");
- addCurName("ttldiff2.example.org.");
-
- // also add some intentionally bad data
- addRecord("A", "3600", "", "192.0.2.1");
- addRecord("CNAME", "3600", "", "www.example.org.");
- addCurName("badcname1.example.org.");
-
- addRecord("CNAME", "3600", "", "www.example.org.");
- addRecord("A", "3600", "", "192.0.2.1");
- addCurName("badcname2.example.org.");
-
- addRecord("CNAME", "3600", "", "www.example.org.");
- addRecord("CNAME", "3600", "", "www.example2.org.");
- addCurName("badcname3.example.org.");
-
- addRecord("A", "3600", "", "bad");
- addCurName("badrdata.example.org.");
-
- addRecord("BAD_TYPE", "3600", "", "192.0.2.1");
- addCurName("badtype.example.org.");
-
- addRecord("A", "badttl", "", "192.0.2.1");
- addCurName("badttl.example.org.");
-
- addRecord("A", "badttl", "", "192.0.2.1");
- addRecord("RRSIG", "3600", "", "A 5 3 3600 somebaddata 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
- addCurName("badsig.example.org.");
-
- addRecord("A", "3600", "", "192.0.2.1");
- addRecord("RRSIG", "3600", "TXT", "A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
- addCurName("badsigtype.example.org.");
-
- // Data for testing delegation (with NS and DNAME)
- addRecord("NS", "3600", "", "ns.example.com.");
- addRecord("NS", "3600", "", "ns.delegation.example.org.");
- addRecord("RRSIG", "3600", "", "NS 5 3 3600 20000101000000 "
- "20000201000000 12345 example.org. FAKEFAKEFAKE");
- addCurName("delegation.example.org.");
- addRecord("A", "3600", "", "192.0.2.1");
- addCurName("ns.delegation.example.org.");
- addRecord("A", "3600", "", "192.0.2.1");
- addCurName("deep.below.delegation.example.org.");
-
- addRecord("A", "3600", "", "192.0.2.1");
- addRecord("DNAME", "3600", "", "dname.example.com.");
- addRecord("RRSIG", "3600", "", "DNAME 5 3 3600 20000101000000 "
- "20000201000000 12345 example.org. FAKEFAKEFAKE");
- addCurName("dname.example.org.");
- addRecord("A", "3600", "", "192.0.2.1");
- addCurName("below.dname.example.org.");
-
- // Broken NS
- addRecord("A", "3600", "", "192.0.2.1");
- addRecord("NS", "3600", "", "ns.example.com.");
- addCurName("brokenns1.example.org.");
- addRecord("NS", "3600", "", "ns.example.com.");
- addRecord("A", "3600", "", "192.0.2.1");
- addCurName("brokenns2.example.org.");
-
- // Now double DNAME, to test failure mode
- addRecord("DNAME", "3600", "", "dname1.example.com.");
- addRecord("DNAME", "3600", "", "dname2.example.com.");
- addCurName("baddname.example.org.");
-
- // Put some data into apex (including NS) so we can check our NS
- // doesn't break anything
- addRecord("NS", "3600", "", "ns.example.com.");
- addRecord("A", "3600", "", "192.0.2.1");
- addRecord("RRSIG", "3600", "", "NS 5 3 3600 20000101000000 "
- "20000201000000 12345 example.org. FAKEFAKEFAKE");
- addCurName("example.org.");
+ const char* prev_name = NULL;
+ for (int i = 0; TEST_RECORDS[i][0] != NULL; ++i) {
+ if (prev_name != NULL &&
+ strcmp(prev_name, TEST_RECORDS[i][0]) != 0) {
+ addCurName(prev_name);
+ }
+ prev_name = TEST_RECORDS[i][0];
+ addRecord(TEST_RECORDS[i][1], TEST_RECORDS[i][2],
+ TEST_RECORDS[i][3], TEST_RECORDS[i][4]);
+ }
+ addCurName(prev_name);
}
};
+// This tests the default getRecords behaviour, throwing NotImplemented
+TEST(DatabaseConnectionTest, getRecords) {
+ EXPECT_THROW(NopAccessor().getRecords(".", 1, false),
+ isc::NotImplemented);
+}
+
// This tests the default getAllRecords behaviour, throwing NotImplemented
TEST(DatabaseConnectionTest, getAllRecords) {
// The parameters don't matter
- EXPECT_THROW(NopAccessor().getAllRecords(Name("."), 1),
+ EXPECT_THROW(NopAccessor().getAllRecords(1),
isc::NotImplemented);
}
+// This test fixture is templated so that we can share (most of) the test
+// cases with different types of data sources. Note that in test cases
+// we need to use 'this' to refer to member variables of the test class.
+template <typename ACCESSOR_TYPE>
class DatabaseClientTest : public ::testing::Test {
public:
- DatabaseClientTest() {
+ DatabaseClientTest() : zname_("example.org"), qname_("www.example.org"),
+ qclass_(RRClass::IN()), qtype_(RRType::A()),
+ rrttl_(3600)
+ {
createClient();
+
+ // set up the commonly used finder.
+ DataSourceClient::FindResult zone(client_->findZone(zname_));
+ assert(zone.code == result::SUCCESS);
+ finder_ = dynamic_pointer_cast<DatabaseClient::Finder>(
+ zone.zone_finder);
+
+ // Test IN/A RDATA to be added in update tests. Intentionally using
+ // different data than the initial data configured in the MockAccessor.
+ rrset_.reset(new RRset(qname_, qclass_, qtype_, rrttl_));
+ rrset_->addRdata(rdata::createRdata(rrset_->getType(),
+ rrset_->getClass(), "192.0.2.2"));
+
+ // And its RRSIG. Also different from the configured one.
+ rrsigset_.reset(new RRset(qname_, qclass_, RRType::RRSIG(),
+ rrttl_));
+ rrsigset_->addRdata(rdata::createRdata(rrsigset_->getType(),
+ rrsigset_->getClass(),
+ "A 5 3 0 20000101000000 "
+ "20000201000000 0 example.org. "
+ "FAKEFAKEFAKE"));
}
+
/*
* We initialize the client from a function, so we can call it multiple
* times per test.
*/
void createClient() {
- current_database_ = new MockAccessor();
- client_.reset(new DatabaseClient(shared_ptr<DatabaseAccessor>(
- current_database_)));
+ current_accessor_ = new ACCESSOR_TYPE();
+ is_mock_ = (dynamic_cast<MockAccessor*>(current_accessor_) != NULL);
+ client_.reset(new DatabaseClient(qclass_,
+ shared_ptr<ACCESSOR_TYPE>(
+ current_accessor_)));
}
- // Will be deleted by client_, just keep the current value for comparison.
- MockAccessor* current_database_;
- shared_ptr<DatabaseClient> client_;
- const std::string database_name_;
/**
* Check the zone finder is a valid one and references the zone ID and
@@ -481,90 +694,195 @@ public:
dynamic_pointer_cast<DatabaseClient::Finder>(zone.zone_finder));
ASSERT_NE(shared_ptr<DatabaseClient::Finder>(), finder) <<
"Wrong type of finder";
- EXPECT_EQ(42, finder->zone_id());
- EXPECT_EQ(current_database_, &finder->database());
+ if (is_mock_) {
+ EXPECT_EQ(READONLY_ZONE_ID, finder->zone_id());
+ }
+ EXPECT_EQ(current_accessor_, &finder->getAccessor());
}
shared_ptr<DatabaseClient::Finder> getFinder() {
- DataSourceClient::FindResult zone(
- client_->findZone(Name("example.org")));
+ DataSourceClient::FindResult zone(client_->findZone(zname_));
EXPECT_EQ(result::SUCCESS, zone.code);
shared_ptr<DatabaseClient::Finder> finder(
dynamic_pointer_cast<DatabaseClient::Finder>(zone.zone_finder));
- EXPECT_EQ(42, finder->zone_id());
- EXPECT_FALSE(current_database_->searchRunning());
+ if (is_mock_) {
+ EXPECT_EQ(READONLY_ZONE_ID, finder->zone_id());
+ }
return (finder);
}
+ // Helper methods for update tests
+ bool isRollbacked(bool expected = false) const {
+ if (is_mock_) {
+ const MockAccessor& mock_accessor =
+ dynamic_cast<const MockAccessor&>(*update_accessor_);
+ return (mock_accessor.isRollbacked());
+ } else {
+ return (expected);
+ }
+ }
+
+ void checkLastAdded(const char* const expected[]) const {
+ if (is_mock_) {
+ const MockAccessor* mock_accessor =
+ dynamic_cast<const MockAccessor*>(current_accessor_);
+ for (int i = 0; i < DatabaseAccessor::ADD_COLUMN_COUNT; ++i) {
+ EXPECT_EQ(expected[i],
+ mock_accessor->getLatestClone()->getLastAdded()[i]);
+ }
+ }
+ }
+
+ void setUpdateAccessor() {
+ if (is_mock_) {
+ const MockAccessor* mock_accessor =
+ dynamic_cast<const MockAccessor*>(current_accessor_);
+ update_accessor_ = mock_accessor->getLatestClone();
+ }
+ }
+
+ // Some tests only work for MockAccessor. We remember whether our accessor
+ // is of that type.
+ bool is_mock_;
+
+ // Will be deleted by client_, just keep the current value for comparison.
+ ACCESSOR_TYPE* current_accessor_;
+ shared_ptr<DatabaseClient> client_;
+ const std::string database_name_;
+
+ // The zone finder of the test zone commonly used in various tests.
+ shared_ptr<DatabaseClient::Finder> finder_;
+
+ // Some shortcut variables for commonly used test parameters
+ const Name zname_; // the zone name stored in the test data source
+ const Name qname_; // commonly used name to be found
+ const RRClass qclass_; // commonly used RR class used with qname
+ const RRType qtype_; // commonly used RR type used with qname
+ const RRTTL rrttl_; // commonly used RR TTL
+ RRsetPtr rrset_; // for adding/deleting an RRset
+ RRsetPtr rrsigset_; // for adding/deleting an RRset
+
+ // update related objects to be tested
+ ZoneUpdaterPtr updater_;
+ shared_ptr<const DatabaseAccessor> update_accessor_;
+
+ // placeholders
+ const std::vector<std::string> empty_rdatas_; // for NXRRSET/NXDOMAIN
std::vector<std::string> expected_rdatas_;
std::vector<std::string> expected_sig_rdatas_;
};
-TEST_F(DatabaseClientTest, zoneNotFound) {
- DataSourceClient::FindResult zone(client_->findZone(Name("example.com")));
+class TestSQLite3Accessor : public SQLite3Accessor {
+public:
+ TestSQLite3Accessor() : SQLite3Accessor(
+ TEST_DATA_BUILDDIR "/rwtest.sqlite3.copied",
+ RRClass::IN())
+ {
+ startUpdateZone("example.org.", true);
+ string columns[ADD_COLUMN_COUNT];
+ for (int i = 0; TEST_RECORDS[i][0] != NULL; ++i) {
+ columns[ADD_NAME] = TEST_RECORDS[i][0];
+ columns[ADD_REV_NAME] = Name(columns[ADD_NAME]).reverse().toText();
+ columns[ADD_TYPE] = TEST_RECORDS[i][1];
+ columns[ADD_TTL] = TEST_RECORDS[i][2];
+ columns[ADD_SIGTYPE] = TEST_RECORDS[i][3];
+ columns[ADD_RDATA] = TEST_RECORDS[i][4];
+
+ addRecordToZone(columns);
+ }
+ commitUpdateZone();
+ }
+};
+
+// The following two lines instantiate test cases with concrete accessor
+// classes to be tested.
+typedef ::testing::Types<MockAccessor, TestSQLite3Accessor> TestAccessorTypes;
+TYPED_TEST_CASE(DatabaseClientTest, TestAccessorTypes);
+
+// In some cases the entire test fixture is for the mock accessor only.
+// We use the usual TEST_F for them with the corresponding specialized class
+// to make the code simpler.
+typedef DatabaseClientTest<MockAccessor> MockDatabaseClientTest;
+
+TYPED_TEST(DatabaseClientTest, zoneNotFound) {
+ DataSourceClient::FindResult zone(
+ this->client_->findZone(Name("example.com")));
EXPECT_EQ(result::NOTFOUND, zone.code);
}
-TEST_F(DatabaseClientTest, exactZone) {
- DataSourceClient::FindResult zone(client_->findZone(Name("example.org")));
+TYPED_TEST(DatabaseClientTest, exactZone) {
+ DataSourceClient::FindResult zone(
+ this->client_->findZone(Name("example.org")));
EXPECT_EQ(result::SUCCESS, zone.code);
- checkZoneFinder(zone);
+ this->checkZoneFinder(zone);
}
-TEST_F(DatabaseClientTest, superZone) {
- DataSourceClient::FindResult zone(client_->findZone(Name(
+TYPED_TEST(DatabaseClientTest, superZone) {
+ DataSourceClient::FindResult zone(this->client_->findZone(Name(
"sub.example.org")));
EXPECT_EQ(result::PARTIALMATCH, zone.code);
- checkZoneFinder(zone);
+ this->checkZoneFinder(zone);
}
-TEST_F(DatabaseClientTest, noAccessorException) {
+// This test doesn't depend on derived accessor class, so we use TEST().
+TEST(GenericDatabaseClientTest, noAccessorException) {
// We need a dummy variable here; some compiler would regard it a mere
// declaration instead of an instantiation and make the test fail.
- EXPECT_THROW(DatabaseClient dummy((shared_ptr<DatabaseAccessor>())),
+ EXPECT_THROW(DatabaseClient dummy(RRClass::IN(),
+ shared_ptr<DatabaseAccessor>()),
isc::InvalidParameter);
}
// If the zone doesn't exist, exception is thrown
-TEST_F(DatabaseClientTest, noZoneIterator) {
- EXPECT_THROW(client_->getIterator(Name("example.com")), DataSourceError);
+TYPED_TEST(DatabaseClientTest, noZoneIterator) {
+ EXPECT_THROW(this->client_->getIterator(Name("example.com")),
+ DataSourceError);
}
// If the zone doesn't exist and iteration is not implemented, it still throws
// the exception it doesn't exist
-TEST_F(DatabaseClientTest, noZoneNotImplementedIterator) {
- EXPECT_THROW(DatabaseClient(boost::shared_ptr<DatabaseAccessor>(
- new NopAccessor())).getIterator(Name("example.com")),
+TEST(GenericDatabaseClientTest, noZoneNotImplementedIterator) {
+ EXPECT_THROW(DatabaseClient(RRClass::IN(),
+ boost::shared_ptr<DatabaseAccessor>(
+ new NopAccessor())).getIterator(
+ Name("example.com")),
DataSourceError);
}
-TEST_F(DatabaseClientTest, notImplementedIterator) {
- EXPECT_THROW(DatabaseClient(shared_ptr<DatabaseAccessor>(
+TEST(GenericDatabaseClientTest, notImplementedIterator) {
+ EXPECT_THROW(DatabaseClient(RRClass::IN(), shared_ptr<DatabaseAccessor>(
new NopAccessor())).getIterator(Name("example.org")),
isc::NotImplemented);
}
// Pretend a bug in the connection and pass NULL as the context
-// Should not crash, but gracefully throw
-TEST_F(DatabaseClientTest, nullIteratorContext) {
- EXPECT_THROW(client_->getIterator(Name("null.example.org")),
+// Should not crash, but gracefully throw. Works for the mock accessor only.
+TEST_F(MockDatabaseClientTest, nullIteratorContext) {
+ EXPECT_THROW(this->client_->getIterator(Name("null.example.org")),
isc::Unexpected);
}
-// It doesn't crash or anything if the zone is completely empty
-TEST_F(DatabaseClientTest, emptyIterator) {
- ZoneIteratorPtr it(client_->getIterator(Name("empty.example.org")));
+// It doesn't crash or anything if the zone is completely empty.
+// Works for the mock accessor only.
+TEST_F(MockDatabaseClientTest, emptyIterator) {
+ ZoneIteratorPtr it(this->client_->getIterator(Name("empty.example.org")));
EXPECT_EQ(ConstRRsetPtr(), it->getNextRRset());
// This is past the end, it should throw
EXPECT_THROW(it->getNextRRset(), isc::Unexpected);
}
-// Iterate trough a zone
-TEST_F(DatabaseClientTest, iterator) {
- ZoneIteratorPtr it(client_->getIterator(Name("example.org")));
+// Iterate through a zone
+TYPED_TEST(DatabaseClientTest, iterator) {
+ ZoneIteratorPtr it(this->client_->getIterator(Name("example.org")));
ConstRRsetPtr rrset(it->getNextRRset());
ASSERT_NE(ConstRRsetPtr(), rrset);
+
+ // The rest of the checks work only for the mock accessor.
+ if (!this->is_mock_) {
+ return;
+ }
+
EXPECT_EQ(Name("example.org"), rrset->getName());
EXPECT_EQ(RRClass::IN(), rrset->getClass());
EXPECT_EQ(RRType::SOA(), rrset->getType());
@@ -607,10 +925,10 @@ TEST_F(DatabaseClientTest, iterator) {
}
// This has inconsistent TTL in the set (the rest, like nonsense in
-// the data is handled in rdata itself).
-TEST_F(DatabaseClientTest, badIterator) {
+// the data is handled in rdata itself). Works for the mock accessor only.
+TEST_F(MockDatabaseClientTest, badIterator) {
// It should not throw, but get the lowest one of them
- ZoneIteratorPtr it(client_->getIterator(Name("bad.example.org")));
+ ZoneIteratorPtr it(this->client_->getIterator(Name("bad.example.org")));
EXPECT_EQ(it->getNextRRset()->getTTL(), isc::dns::RRTTL(300));
}
@@ -634,7 +952,7 @@ checkRRset(isc::dns::ConstRRsetPtr rrset,
}
void
-doFindTest(shared_ptr<DatabaseClient::Finder> finder,
+doFindTest(ZoneFinder& finder,
const isc::dns::Name& name,
const isc::dns::RRType& type,
const isc::dns::RRType& expected_type,
@@ -647,16 +965,16 @@ doFindTest(shared_ptr<DatabaseClient::Finder> finder,
{
SCOPED_TRACE("doFindTest " + name.toText() + " " + type.toText());
ZoneFinder::FindResult result =
- finder->find(name, type, NULL, options);
+ finder.find(name, type, NULL, options);
ASSERT_EQ(expected_result, result.code) << name << " " << type;
- if (expected_rdatas.size() > 0) {
+ if (!expected_rdatas.empty()) {
checkRRset(result.rrset, expected_name != Name(".") ? expected_name :
- name, finder->getClass(), expected_type, expected_ttl,
+ name, finder.getClass(), expected_type, expected_ttl,
expected_rdatas);
- if (expected_sig_rdatas.size() > 0) {
+ if (!expected_sig_rdatas.empty()) {
checkRRset(result.rrset->getRRsig(), expected_name != Name(".") ?
- expected_name : name, finder->getClass(),
+ expected_name : name, finder.getClass(),
isc::dns::RRType::RRSIG(), expected_ttl,
expected_sig_rdatas);
} else {
@@ -667,509 +985,1161 @@ doFindTest(shared_ptr<DatabaseClient::Finder> finder,
}
}
-TEST_F(DatabaseClientTest, find) {
- shared_ptr<DatabaseClient::Finder> finder(getFinder());
-
- expected_rdatas_.clear();
- expected_sig_rdatas_.clear();
- expected_rdatas_.push_back("192.0.2.1");
- doFindTest(finder, isc::dns::Name("www.example.org."),
- isc::dns::RRType::A(), isc::dns::RRType::A(),
- isc::dns::RRTTL(3600),
- ZoneFinder::SUCCESS,
- expected_rdatas_, expected_sig_rdatas_);
- EXPECT_FALSE(current_database_->searchRunning());
-
- expected_rdatas_.clear();
- expected_sig_rdatas_.clear();
- expected_rdatas_.push_back("192.0.2.1");
- expected_rdatas_.push_back("192.0.2.2");
- doFindTest(finder, isc::dns::Name("www2.example.org."),
- isc::dns::RRType::A(), isc::dns::RRType::A(),
- isc::dns::RRTTL(3600),
- ZoneFinder::SUCCESS,
- expected_rdatas_, expected_sig_rdatas_);
- EXPECT_FALSE(current_database_->searchRunning());
-
- expected_rdatas_.clear();
- expected_sig_rdatas_.clear();
- expected_rdatas_.push_back("2001:db8::1");
- expected_rdatas_.push_back("2001:db8::2");
- doFindTest(finder, isc::dns::Name("www.example.org."),
+TYPED_TEST(DatabaseClientTest, find) {
+ shared_ptr<DatabaseClient::Finder> finder(this->getFinder());
+
+ this->expected_rdatas_.clear();
+ this->expected_sig_rdatas_.clear();
+ this->expected_rdatas_.push_back("192.0.2.1");
+ doFindTest(*finder, isc::dns::Name("www.example.org."),
+ this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
+ this->expected_rdatas_, this->expected_sig_rdatas_);
+
+ this->expected_rdatas_.clear();
+ this->expected_sig_rdatas_.clear();
+ this->expected_rdatas_.push_back("192.0.2.1");
+ this->expected_rdatas_.push_back("192.0.2.2");
+ doFindTest(*finder, isc::dns::Name("www2.example.org."),
+ this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
+ this->expected_rdatas_, this->expected_sig_rdatas_);
+
+ this->expected_rdatas_.clear();
+ this->expected_sig_rdatas_.clear();
+ this->expected_rdatas_.push_back("2001:db8::1");
+ this->expected_rdatas_.push_back("2001:db8::2");
+ doFindTest(*finder, isc::dns::Name("www.example.org."),
isc::dns::RRType::AAAA(), isc::dns::RRType::AAAA(),
- isc::dns::RRTTL(3600),
+ this->rrttl_,
ZoneFinder::SUCCESS,
- expected_rdatas_, expected_sig_rdatas_);
- EXPECT_FALSE(current_database_->searchRunning());
+ this->expected_rdatas_, this->expected_sig_rdatas_);
- expected_rdatas_.clear();
- expected_sig_rdatas_.clear();
- doFindTest(finder, isc::dns::Name("www.example.org."),
+ this->expected_rdatas_.clear();
+ this->expected_sig_rdatas_.clear();
+ doFindTest(*finder, isc::dns::Name("www.example.org."),
isc::dns::RRType::TXT(), isc::dns::RRType::TXT(),
- isc::dns::RRTTL(3600),
+ this->rrttl_,
ZoneFinder::NXRRSET,
- expected_rdatas_, expected_sig_rdatas_);
- EXPECT_FALSE(current_database_->searchRunning());
-
- expected_rdatas_.clear();
- expected_sig_rdatas_.clear();
- expected_rdatas_.push_back("www.example.org.");
- doFindTest(finder, isc::dns::Name("cname.example.org."),
- isc::dns::RRType::A(), isc::dns::RRType::CNAME(),
- isc::dns::RRTTL(3600),
- ZoneFinder::CNAME,
- expected_rdatas_, expected_sig_rdatas_);
- EXPECT_FALSE(current_database_->searchRunning());
-
- expected_rdatas_.clear();
- expected_sig_rdatas_.clear();
- expected_rdatas_.push_back("www.example.org.");
- doFindTest(finder, isc::dns::Name("cname.example.org."),
+ this->expected_rdatas_, this->expected_sig_rdatas_);
+
+ this->expected_rdatas_.clear();
+ this->expected_sig_rdatas_.clear();
+ this->expected_rdatas_.push_back("www.example.org.");
+ doFindTest(*finder, isc::dns::Name("cname.example.org."),
+ this->qtype_, isc::dns::RRType::CNAME(), this->rrttl_,
+ ZoneFinder::CNAME, this->expected_rdatas_,
+ this->expected_sig_rdatas_);
+
+ this->expected_rdatas_.clear();
+ this->expected_sig_rdatas_.clear();
+ this->expected_rdatas_.push_back("www.example.org.");
+ doFindTest(*finder, isc::dns::Name("cname.example.org."),
isc::dns::RRType::CNAME(), isc::dns::RRType::CNAME(),
- isc::dns::RRTTL(3600),
- ZoneFinder::SUCCESS,
- expected_rdatas_, expected_sig_rdatas_);
- EXPECT_FALSE(current_database_->searchRunning());
-
- expected_rdatas_.clear();
- expected_sig_rdatas_.clear();
- doFindTest(finder, isc::dns::Name("doesnotexist.example.org."),
- isc::dns::RRType::A(), isc::dns::RRType::A(),
- isc::dns::RRTTL(3600),
- ZoneFinder::NXDOMAIN,
- expected_rdatas_, expected_sig_rdatas_);
- EXPECT_FALSE(current_database_->searchRunning());
-
- expected_rdatas_.clear();
- expected_sig_rdatas_.clear();
- expected_rdatas_.push_back("192.0.2.1");
- expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
- expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 20000201000000 12346 example.org. FAKEFAKEFAKE");
- doFindTest(finder, isc::dns::Name("signed1.example.org."),
- isc::dns::RRType::A(), isc::dns::RRType::A(),
- isc::dns::RRTTL(3600),
- ZoneFinder::SUCCESS,
- expected_rdatas_, expected_sig_rdatas_);
- EXPECT_FALSE(current_database_->searchRunning());
-
- expected_rdatas_.clear();
- expected_sig_rdatas_.clear();
- expected_rdatas_.push_back("2001:db8::1");
- expected_rdatas_.push_back("2001:db8::2");
- expected_sig_rdatas_.push_back("AAAA 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
- doFindTest(finder, isc::dns::Name("signed1.example.org."),
+ this->rrttl_, ZoneFinder::SUCCESS, this->expected_rdatas_,
+ this->expected_sig_rdatas_);
+
+ this->expected_rdatas_.clear();
+ this->expected_sig_rdatas_.clear();
+ doFindTest(*finder, isc::dns::Name("doesnotexist.example.org."),
+ this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::NXDOMAIN,
+ this->expected_rdatas_, this->expected_sig_rdatas_);
+
+ this->expected_rdatas_.clear();
+ this->expected_sig_rdatas_.clear();
+ this->expected_rdatas_.push_back("192.0.2.1");
+ this->expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
+ this->expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 20000201000000 12346 example.org. FAKEFAKEFAKE");
+ doFindTest(*finder, isc::dns::Name("signed1.example.org."),
+ this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
+ this->expected_rdatas_, this->expected_sig_rdatas_);
+
+ this->expected_rdatas_.clear();
+ this->expected_sig_rdatas_.clear();
+ this->expected_rdatas_.push_back("2001:db8::1");
+ this->expected_rdatas_.push_back("2001:db8::2");
+ this->expected_sig_rdatas_.push_back("AAAA 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
+ doFindTest(*finder, isc::dns::Name("signed1.example.org."),
isc::dns::RRType::AAAA(), isc::dns::RRType::AAAA(),
- isc::dns::RRTTL(3600),
- ZoneFinder::SUCCESS,
- expected_rdatas_, expected_sig_rdatas_);
- EXPECT_FALSE(current_database_->searchRunning());
-
- expected_rdatas_.clear();
- expected_sig_rdatas_.clear();
- doFindTest(finder, isc::dns::Name("signed1.example.org."),
- isc::dns::RRType::TXT(), isc::dns::RRType::TXT(),
- isc::dns::RRTTL(3600),
- ZoneFinder::NXRRSET,
- expected_rdatas_, expected_sig_rdatas_);
- EXPECT_FALSE(current_database_->searchRunning());
-
- expected_rdatas_.clear();
- expected_sig_rdatas_.clear();
- expected_rdatas_.push_back("www.example.org.");
- expected_sig_rdatas_.push_back("CNAME 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
- doFindTest(finder, isc::dns::Name("signedcname1.example.org."),
- isc::dns::RRType::A(), isc::dns::RRType::CNAME(),
- isc::dns::RRTTL(3600),
- ZoneFinder::CNAME,
- expected_rdatas_, expected_sig_rdatas_);
- EXPECT_FALSE(current_database_->searchRunning());
-
- expected_rdatas_.clear();
- expected_sig_rdatas_.clear();
- expected_rdatas_.push_back("192.0.2.1");
- expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
- expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 20000201000000 12346 example.org. FAKEFAKEFAKE");
- doFindTest(finder, isc::dns::Name("signed2.example.org."),
- isc::dns::RRType::A(), isc::dns::RRType::A(),
- isc::dns::RRTTL(3600),
- ZoneFinder::SUCCESS,
- expected_rdatas_, expected_sig_rdatas_);
- EXPECT_FALSE(current_database_->searchRunning());
-
- expected_rdatas_.clear();
- expected_sig_rdatas_.clear();
- expected_rdatas_.push_back("2001:db8::2");
- expected_rdatas_.push_back("2001:db8::1");
- expected_sig_rdatas_.push_back("AAAA 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
- doFindTest(finder, isc::dns::Name("signed2.example.org."),
+ this->rrttl_, ZoneFinder::SUCCESS, this->expected_rdatas_,
+ this->expected_sig_rdatas_);
+
+ this->expected_rdatas_.clear();
+ this->expected_sig_rdatas_.clear();
+ doFindTest(*finder, isc::dns::Name("signed1.example.org."),
+ isc::dns::RRType::TXT(), isc::dns::RRType::TXT(), this->rrttl_,
+ ZoneFinder::NXRRSET, this->expected_rdatas_,
+ this->expected_sig_rdatas_);
+
+ this->expected_rdatas_.clear();
+ this->expected_sig_rdatas_.clear();
+ this->expected_rdatas_.push_back("www.example.org.");
+ this->expected_sig_rdatas_.push_back("CNAME 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
+ doFindTest(*finder, isc::dns::Name("signedcname1.example.org."),
+ this->qtype_, isc::dns::RRType::CNAME(), this->rrttl_,
+ ZoneFinder::CNAME, this->expected_rdatas_,
+ this->expected_sig_rdatas_);
+
+ this->expected_rdatas_.clear();
+ this->expected_sig_rdatas_.clear();
+ this->expected_rdatas_.push_back("192.0.2.1");
+ this->expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
+ this->expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 20000201000000 12346 example.org. FAKEFAKEFAKE");
+ doFindTest(*finder, isc::dns::Name("signed2.example.org."),
+ this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
+ this->expected_rdatas_, this->expected_sig_rdatas_);
+
+ this->expected_rdatas_.clear();
+ this->expected_sig_rdatas_.clear();
+ this->expected_rdatas_.push_back("2001:db8::2");
+ this->expected_rdatas_.push_back("2001:db8::1");
+ this->expected_sig_rdatas_.push_back("AAAA 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
+ doFindTest(*finder, isc::dns::Name("signed2.example.org."),
isc::dns::RRType::AAAA(), isc::dns::RRType::AAAA(),
- isc::dns::RRTTL(3600),
- ZoneFinder::SUCCESS,
- expected_rdatas_, expected_sig_rdatas_);
- EXPECT_FALSE(current_database_->searchRunning());
-
- expected_rdatas_.clear();
- expected_sig_rdatas_.clear();
- doFindTest(finder, isc::dns::Name("signed2.example.org."),
- isc::dns::RRType::TXT(), isc::dns::RRType::TXT(),
- isc::dns::RRTTL(3600),
- ZoneFinder::NXRRSET,
- expected_rdatas_, expected_sig_rdatas_);
- EXPECT_FALSE(current_database_->searchRunning());
-
- expected_rdatas_.clear();
- expected_sig_rdatas_.clear();
- expected_rdatas_.push_back("www.example.org.");
- expected_sig_rdatas_.push_back("CNAME 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
- doFindTest(finder, isc::dns::Name("signedcname2.example.org."),
- isc::dns::RRType::A(), isc::dns::RRType::CNAME(),
- isc::dns::RRTTL(3600),
- ZoneFinder::CNAME,
- expected_rdatas_, expected_sig_rdatas_);
- EXPECT_FALSE(current_database_->searchRunning());
-
-
- expected_rdatas_.clear();
- expected_sig_rdatas_.clear();
- expected_rdatas_.push_back("192.0.2.1");
- expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
- doFindTest(finder, isc::dns::Name("acnamesig1.example.org."),
- isc::dns::RRType::A(), isc::dns::RRType::A(),
- isc::dns::RRTTL(3600),
- ZoneFinder::SUCCESS,
- expected_rdatas_, expected_sig_rdatas_);
- EXPECT_FALSE(current_database_->searchRunning());
-
- expected_rdatas_.clear();
- expected_sig_rdatas_.clear();
- expected_rdatas_.push_back("192.0.2.1");
- expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
- doFindTest(finder, isc::dns::Name("acnamesig2.example.org."),
- isc::dns::RRType::A(), isc::dns::RRType::A(),
- isc::dns::RRTTL(3600),
- ZoneFinder::SUCCESS,
- expected_rdatas_, expected_sig_rdatas_);
- EXPECT_FALSE(current_database_->searchRunning());
-
- expected_rdatas_.clear();
- expected_sig_rdatas_.clear();
- expected_rdatas_.push_back("192.0.2.1");
- expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
- doFindTest(finder, isc::dns::Name("acnamesig3.example.org."),
- isc::dns::RRType::A(), isc::dns::RRType::A(),
- isc::dns::RRTTL(3600),
- ZoneFinder::SUCCESS,
- expected_rdatas_, expected_sig_rdatas_);
- EXPECT_FALSE(current_database_->searchRunning());
-
- expected_rdatas_.clear();
- expected_sig_rdatas_.clear();
- expected_rdatas_.push_back("192.0.2.1");
- expected_rdatas_.push_back("192.0.2.2");
- doFindTest(finder, isc::dns::Name("ttldiff1.example.org."),
- isc::dns::RRType::A(), isc::dns::RRType::A(),
- isc::dns::RRTTL(360),
- ZoneFinder::SUCCESS,
- expected_rdatas_, expected_sig_rdatas_);
- EXPECT_FALSE(current_database_->searchRunning());
-
- expected_rdatas_.clear();
- expected_sig_rdatas_.clear();
- expected_rdatas_.push_back("192.0.2.1");
- expected_rdatas_.push_back("192.0.2.2");
- doFindTest(finder, isc::dns::Name("ttldiff2.example.org."),
- isc::dns::RRType::A(), isc::dns::RRType::A(),
- isc::dns::RRTTL(360),
- ZoneFinder::SUCCESS,
- expected_rdatas_, expected_sig_rdatas_);
- EXPECT_FALSE(current_database_->searchRunning());
-
+ this->rrttl_, ZoneFinder::SUCCESS, this->expected_rdatas_,
+ this->expected_sig_rdatas_);
+
+ this->expected_rdatas_.clear();
+ this->expected_sig_rdatas_.clear();
+ doFindTest(*finder, isc::dns::Name("signed2.example.org."),
+ isc::dns::RRType::TXT(), isc::dns::RRType::TXT(), this->rrttl_,
+ ZoneFinder::NXRRSET, this->expected_rdatas_,
+ this->expected_sig_rdatas_);
+
+ this->expected_rdatas_.clear();
+ this->expected_sig_rdatas_.clear();
+ this->expected_rdatas_.push_back("www.example.org.");
+ this->expected_sig_rdatas_.push_back("CNAME 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
+ doFindTest(*finder, isc::dns::Name("signedcname2.example.org."),
+ this->qtype_, isc::dns::RRType::CNAME(), this->rrttl_,
+ ZoneFinder::CNAME, this->expected_rdatas_,
+ this->expected_sig_rdatas_);
+
+ this->expected_rdatas_.clear();
+ this->expected_sig_rdatas_.clear();
+ this->expected_rdatas_.push_back("192.0.2.1");
+ this->expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
+ doFindTest(*finder, isc::dns::Name("acnamesig1.example.org."),
+ this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
+ this->expected_rdatas_, this->expected_sig_rdatas_);
+
+ this->expected_rdatas_.clear();
+ this->expected_sig_rdatas_.clear();
+ this->expected_rdatas_.push_back("192.0.2.1");
+ this->expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
+ doFindTest(*finder, isc::dns::Name("acnamesig2.example.org."),
+ this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
+ this->expected_rdatas_, this->expected_sig_rdatas_);
+
+ this->expected_rdatas_.clear();
+ this->expected_sig_rdatas_.clear();
+ this->expected_rdatas_.push_back("192.0.2.1");
+ this->expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
+ doFindTest(*finder, isc::dns::Name("acnamesig3.example.org."),
+ this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
+ this->expected_rdatas_, this->expected_sig_rdatas_);
+
+ this->expected_rdatas_.clear();
+ this->expected_sig_rdatas_.clear();
+ this->expected_rdatas_.push_back("192.0.2.1");
+ this->expected_rdatas_.push_back("192.0.2.2");
+ doFindTest(*finder, isc::dns::Name("ttldiff1.example.org."),
+ this->qtype_, this->qtype_, isc::dns::RRTTL(360),
+ ZoneFinder::SUCCESS, this->expected_rdatas_,
+ this->expected_sig_rdatas_);
+
+ this->expected_rdatas_.clear();
+ this->expected_sig_rdatas_.clear();
+ this->expected_rdatas_.push_back("192.0.2.1");
+ this->expected_rdatas_.push_back("192.0.2.2");
+ doFindTest(*finder, isc::dns::Name("ttldiff2.example.org."),
+ this->qtype_, this->qtype_, isc::dns::RRTTL(360),
+ ZoneFinder::SUCCESS, this->expected_rdatas_,
+ this->expected_sig_rdatas_);
EXPECT_THROW(finder->find(isc::dns::Name("badcname1.example.org."),
- isc::dns::RRType::A(),
+ this->qtype_,
NULL, ZoneFinder::FIND_DEFAULT),
DataSourceError);
- EXPECT_FALSE(current_database_->searchRunning());
EXPECT_THROW(finder->find(isc::dns::Name("badcname2.example.org."),
- isc::dns::RRType::A(),
+ this->qtype_,
NULL, ZoneFinder::FIND_DEFAULT),
DataSourceError);
- EXPECT_FALSE(current_database_->searchRunning());
EXPECT_THROW(finder->find(isc::dns::Name("badcname3.example.org."),
- isc::dns::RRType::A(),
+ this->qtype_,
NULL, ZoneFinder::FIND_DEFAULT),
DataSourceError);
- EXPECT_FALSE(current_database_->searchRunning());
EXPECT_THROW(finder->find(isc::dns::Name("badrdata.example.org."),
- isc::dns::RRType::A(),
+ this->qtype_,
NULL, ZoneFinder::FIND_DEFAULT),
DataSourceError);
- EXPECT_FALSE(current_database_->searchRunning());
EXPECT_THROW(finder->find(isc::dns::Name("badtype.example.org."),
- isc::dns::RRType::A(),
+ this->qtype_,
NULL, ZoneFinder::FIND_DEFAULT),
DataSourceError);
- EXPECT_FALSE(current_database_->searchRunning());
EXPECT_THROW(finder->find(isc::dns::Name("badttl.example.org."),
- isc::dns::RRType::A(),
+ this->qtype_,
NULL, ZoneFinder::FIND_DEFAULT),
DataSourceError);
- EXPECT_FALSE(current_database_->searchRunning());
EXPECT_THROW(finder->find(isc::dns::Name("badsig.example.org."),
- isc::dns::RRType::A(),
+ this->qtype_,
NULL, ZoneFinder::FIND_DEFAULT),
DataSourceError);
- EXPECT_FALSE(current_database_->searchRunning());
// Trigger the hardcoded exceptions and see if find() has cleaned up
- EXPECT_THROW(finder->find(isc::dns::Name("dsexception.in.search."),
- isc::dns::RRType::A(),
- NULL, ZoneFinder::FIND_DEFAULT),
- DataSourceError);
- EXPECT_FALSE(current_database_->searchRunning());
- EXPECT_THROW(finder->find(isc::dns::Name("iscexception.in.search."),
- isc::dns::RRType::A(),
- NULL, ZoneFinder::FIND_DEFAULT),
- DataSourceError);
- EXPECT_FALSE(current_database_->searchRunning());
- EXPECT_THROW(finder->find(isc::dns::Name("basicexception.in.search."),
- isc::dns::RRType::A(),
- NULL, ZoneFinder::FIND_DEFAULT),
- std::exception);
- EXPECT_FALSE(current_database_->searchRunning());
-
- EXPECT_THROW(finder->find(isc::dns::Name("dsexception.in.getnext."),
- isc::dns::RRType::A(),
- NULL, ZoneFinder::FIND_DEFAULT),
- DataSourceError);
- EXPECT_FALSE(current_database_->searchRunning());
- EXPECT_THROW(finder->find(isc::dns::Name("iscexception.in.getnext."),
- isc::dns::RRType::A(),
- NULL, ZoneFinder::FIND_DEFAULT),
- DataSourceError);
- EXPECT_FALSE(current_database_->searchRunning());
- EXPECT_THROW(finder->find(isc::dns::Name("basicexception.in.getnext."),
- isc::dns::RRType::A(),
- NULL, ZoneFinder::FIND_DEFAULT),
- std::exception);
- EXPECT_FALSE(current_database_->searchRunning());
+ if (this->is_mock_) {
+ EXPECT_THROW(finder->find(isc::dns::Name("dsexception.in.search."),
+ this->qtype_,
+ NULL, ZoneFinder::FIND_DEFAULT),
+ DataSourceError);
+ EXPECT_THROW(finder->find(isc::dns::Name("iscexception.in.search."),
+ this->qtype_,
+ NULL, ZoneFinder::FIND_DEFAULT),
+ isc::Exception);
+ EXPECT_THROW(finder->find(isc::dns::Name("basicexception.in.search."),
+ this->qtype_,
+ NULL, ZoneFinder::FIND_DEFAULT),
+ std::exception);
+ EXPECT_THROW(finder->find(isc::dns::Name("dsexception.in.getnext."),
+ this->qtype_,
+ NULL, ZoneFinder::FIND_DEFAULT),
+ DataSourceError);
+ EXPECT_THROW(finder->find(isc::dns::Name("iscexception.in.getnext."),
+ this->qtype_,
+ NULL, ZoneFinder::FIND_DEFAULT),
+ isc::Exception);
+ EXPECT_THROW(finder->find(isc::dns::Name("basicexception.in.getnext."),
+ this->qtype_,
+ NULL, ZoneFinder::FIND_DEFAULT),
+ std::exception);
+ }
// This RRSIG has the wrong sigtype field, which should be
// an error if we decide to keep using that field
// Right now the field is ignored, so it does not error
- expected_rdatas_.clear();
- expected_sig_rdatas_.clear();
- expected_rdatas_.push_back("192.0.2.1");
- expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
- doFindTest(finder, isc::dns::Name("badsigtype.example.org."),
- isc::dns::RRType::A(), isc::dns::RRType::A(),
- isc::dns::RRTTL(3600),
- ZoneFinder::SUCCESS,
- expected_rdatas_, expected_sig_rdatas_);
- EXPECT_FALSE(current_database_->searchRunning());
+ this->expected_rdatas_.clear();
+ this->expected_sig_rdatas_.clear();
+ this->expected_rdatas_.push_back("192.0.2.1");
+ this->expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE");
+ doFindTest(*finder, isc::dns::Name("badsigtype.example.org."),
+ this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
+ this->expected_rdatas_, this->expected_sig_rdatas_);
}
-TEST_F(DatabaseClientTest, findDelegation) {
- shared_ptr<DatabaseClient::Finder> finder(getFinder());
+TYPED_TEST(DatabaseClientTest, findDelegation) {
+ shared_ptr<DatabaseClient::Finder> finder(this->getFinder());
// The apex should not be considered delegation point and we can access
// data
- expected_rdatas_.clear();
- expected_sig_rdatas_.clear();
- expected_rdatas_.push_back("192.0.2.1");
- doFindTest(finder, isc::dns::Name("example.org."),
- isc::dns::RRType::A(), isc::dns::RRType::A(),
- isc::dns::RRTTL(3600), ZoneFinder::SUCCESS, expected_rdatas_,
- expected_sig_rdatas_);
- EXPECT_FALSE(current_database_->searchRunning());
-
- expected_rdatas_.clear();
- expected_rdatas_.push_back("ns.example.com.");
- expected_sig_rdatas_.push_back("NS 5 3 3600 20000101000000 20000201000000 "
+ this->expected_rdatas_.clear();
+ this->expected_sig_rdatas_.clear();
+ this->expected_rdatas_.push_back("192.0.2.1");
+ doFindTest(*finder, isc::dns::Name("example.org."),
+ this->qtype_, this->qtype_,
+ this->rrttl_, ZoneFinder::SUCCESS, this->expected_rdatas_,
+ this->expected_sig_rdatas_);
+
+ this->expected_rdatas_.clear();
+ this->expected_rdatas_.push_back("ns.example.com.");
+ this->expected_sig_rdatas_.push_back("NS 5 3 3600 20000101000000 20000201000000 "
"12345 example.org. FAKEFAKEFAKE");
- doFindTest(finder, isc::dns::Name("example.org."),
+ doFindTest(*finder, isc::dns::Name("example.org."),
isc::dns::RRType::NS(), isc::dns::RRType::NS(),
- isc::dns::RRTTL(3600), ZoneFinder::SUCCESS, expected_rdatas_,
- expected_sig_rdatas_);
- EXPECT_FALSE(current_database_->searchRunning());
+ this->rrttl_, ZoneFinder::SUCCESS, this->expected_rdatas_,
+ this->expected_sig_rdatas_);
// Check when we ask for something below delegation point, we get the NS
// (Both when the RRset there exists and doesn't)
- expected_rdatas_.clear();
- expected_sig_rdatas_.clear();
- expected_rdatas_.push_back("ns.example.com.");
- expected_rdatas_.push_back("ns.delegation.example.org.");
- expected_sig_rdatas_.push_back("NS 5 3 3600 20000101000000 20000201000000 "
+ this->expected_rdatas_.clear();
+ this->expected_sig_rdatas_.clear();
+ this->expected_rdatas_.push_back("ns.example.com.");
+ this->expected_rdatas_.push_back("ns.delegation.example.org.");
+ this->expected_sig_rdatas_.push_back("NS 5 3 3600 20000101000000 20000201000000 "
"12345 example.org. FAKEFAKEFAKE");
- doFindTest(finder, isc::dns::Name("ns.delegation.example.org."),
- isc::dns::RRType::A(), isc::dns::RRType::NS(),
- isc::dns::RRTTL(3600), ZoneFinder::DELEGATION, expected_rdatas_,
- expected_sig_rdatas_,
+ doFindTest(*finder, isc::dns::Name("ns.delegation.example.org."),
+ this->qtype_, isc::dns::RRType::NS(),
+ this->rrttl_, ZoneFinder::DELEGATION, this->expected_rdatas_,
+ this->expected_sig_rdatas_,
isc::dns::Name("delegation.example.org."));
- EXPECT_FALSE(current_database_->searchRunning());
- doFindTest(finder, isc::dns::Name("ns.delegation.example.org."),
+ doFindTest(*finder, isc::dns::Name("ns.delegation.example.org."),
isc::dns::RRType::AAAA(), isc::dns::RRType::NS(),
- isc::dns::RRTTL(3600), ZoneFinder::DELEGATION, expected_rdatas_,
- expected_sig_rdatas_,
+ this->rrttl_, ZoneFinder::DELEGATION, this->expected_rdatas_,
+ this->expected_sig_rdatas_,
isc::dns::Name("delegation.example.org."));
- doFindTest(finder, isc::dns::Name("deep.below.delegation.example.org."),
+ doFindTest(*finder, isc::dns::Name("deep.below.delegation.example.org."),
isc::dns::RRType::AAAA(), isc::dns::RRType::NS(),
- isc::dns::RRTTL(3600), ZoneFinder::DELEGATION, expected_rdatas_,
- expected_sig_rdatas_,
+ this->rrttl_, ZoneFinder::DELEGATION, this->expected_rdatas_,
+ this->expected_sig_rdatas_,
isc::dns::Name("delegation.example.org."));
- EXPECT_FALSE(current_database_->searchRunning());
// Even when we check directly at the delegation point, we should get
// the NS
- doFindTest(finder, isc::dns::Name("delegation.example.org."),
+ doFindTest(*finder, isc::dns::Name("delegation.example.org."),
isc::dns::RRType::AAAA(), isc::dns::RRType::NS(),
- isc::dns::RRTTL(3600), ZoneFinder::DELEGATION, expected_rdatas_,
- expected_sig_rdatas_);
- EXPECT_FALSE(current_database_->searchRunning());
+ this->rrttl_, ZoneFinder::DELEGATION, this->expected_rdatas_,
+ this->expected_sig_rdatas_);
// And when we ask direcly for the NS, we should still get delegation
- doFindTest(finder, isc::dns::Name("delegation.example.org."),
+ doFindTest(*finder, isc::dns::Name("delegation.example.org."),
isc::dns::RRType::NS(), isc::dns::RRType::NS(),
- isc::dns::RRTTL(3600), ZoneFinder::DELEGATION, expected_rdatas_,
- expected_sig_rdatas_);
- EXPECT_FALSE(current_database_->searchRunning());
+ this->rrttl_, ZoneFinder::DELEGATION, this->expected_rdatas_,
+ this->expected_sig_rdatas_);
// Now test delegation. If it is below the delegation point, we should get
// the DNAME (the one with data under DNAME is invalid zone, but we test
// the behaviour anyway just to make sure)
- expected_rdatas_.clear();
- expected_rdatas_.push_back("dname.example.com.");
- expected_sig_rdatas_.clear();
- expected_sig_rdatas_.push_back("DNAME 5 3 3600 20000101000000 "
+ this->expected_rdatas_.clear();
+ this->expected_rdatas_.push_back("dname.example.com.");
+ this->expected_sig_rdatas_.clear();
+ this->expected_sig_rdatas_.push_back("DNAME 5 3 3600 20000101000000 "
"20000201000000 12345 example.org. "
"FAKEFAKEFAKE");
- doFindTest(finder, isc::dns::Name("below.dname.example.org."),
- isc::dns::RRType::A(), isc::dns::RRType::DNAME(),
- isc::dns::RRTTL(3600), ZoneFinder::DNAME, expected_rdatas_,
- expected_sig_rdatas_, isc::dns::Name("dname.example.org."));
- EXPECT_FALSE(current_database_->searchRunning());
- doFindTest(finder, isc::dns::Name("below.dname.example.org."),
+ doFindTest(*finder, isc::dns::Name("below.dname.example.org."),
+ this->qtype_, isc::dns::RRType::DNAME(),
+ this->rrttl_, ZoneFinder::DNAME, this->expected_rdatas_,
+ this->expected_sig_rdatas_, isc::dns::Name("dname.example.org."));
+ doFindTest(*finder, isc::dns::Name("below.dname.example.org."),
isc::dns::RRType::AAAA(), isc::dns::RRType::DNAME(),
- isc::dns::RRTTL(3600), ZoneFinder::DNAME, expected_rdatas_,
- expected_sig_rdatas_, isc::dns::Name("dname.example.org."));
- EXPECT_FALSE(current_database_->searchRunning());
- doFindTest(finder, isc::dns::Name("really.deep.below.dname.example.org."),
+ this->rrttl_, ZoneFinder::DNAME, this->expected_rdatas_,
+ this->expected_sig_rdatas_, isc::dns::Name("dname.example.org."));
+ doFindTest(*finder, isc::dns::Name("really.deep.below.dname.example.org."),
isc::dns::RRType::AAAA(), isc::dns::RRType::DNAME(),
- isc::dns::RRTTL(3600), ZoneFinder::DNAME, expected_rdatas_,
- expected_sig_rdatas_, isc::dns::Name("dname.example.org."));
- EXPECT_FALSE(current_database_->searchRunning());
+ this->rrttl_, ZoneFinder::DNAME, this->expected_rdatas_,
+ this->expected_sig_rdatas_, isc::dns::Name("dname.example.org."));
// Asking direcly for DNAME should give SUCCESS
- doFindTest(finder, isc::dns::Name("dname.example.org."),
+ doFindTest(*finder, isc::dns::Name("dname.example.org."),
isc::dns::RRType::DNAME(), isc::dns::RRType::DNAME(),
- isc::dns::RRTTL(3600), ZoneFinder::SUCCESS, expected_rdatas_,
- expected_sig_rdatas_);
+ this->rrttl_, ZoneFinder::SUCCESS, this->expected_rdatas_,
+ this->expected_sig_rdatas_);
// But we don't delegate at DNAME point
- expected_rdatas_.clear();
- expected_rdatas_.push_back("192.0.2.1");
- expected_sig_rdatas_.clear();
- doFindTest(finder, isc::dns::Name("dname.example.org."),
- isc::dns::RRType::A(), isc::dns::RRType::A(),
- isc::dns::RRTTL(3600), ZoneFinder::SUCCESS, expected_rdatas_,
- expected_sig_rdatas_);
- EXPECT_FALSE(current_database_->searchRunning());
- expected_rdatas_.clear();
- doFindTest(finder, isc::dns::Name("dname.example.org."),
+ this->expected_rdatas_.clear();
+ this->expected_rdatas_.push_back("192.0.2.1");
+ this->expected_sig_rdatas_.clear();
+ doFindTest(*finder, isc::dns::Name("dname.example.org."),
+ this->qtype_, this->qtype_,
+ this->rrttl_, ZoneFinder::SUCCESS, this->expected_rdatas_,
+ this->expected_sig_rdatas_);
+ this->expected_rdatas_.clear();
+ doFindTest(*finder, isc::dns::Name("dname.example.org."),
isc::dns::RRType::AAAA(), isc::dns::RRType::AAAA(),
- isc::dns::RRTTL(3600), ZoneFinder::NXRRSET, expected_rdatas_,
- expected_sig_rdatas_);
- EXPECT_FALSE(current_database_->searchRunning());
+ this->rrttl_, ZoneFinder::NXRRSET, this->expected_rdatas_,
+ this->expected_sig_rdatas_);
// This is broken dname, it contains two targets
EXPECT_THROW(finder->find(isc::dns::Name("below.baddname.example.org."),
- isc::dns::RRType::A(), NULL,
+ this->qtype_, NULL,
ZoneFinder::FIND_DEFAULT),
DataSourceError);
- EXPECT_FALSE(current_database_->searchRunning());
// Broken NS - it lives together with something else
- EXPECT_FALSE(current_database_->searchRunning());
EXPECT_THROW(finder->find(isc::dns::Name("brokenns1.example.org."),
- isc::dns::RRType::A(), NULL,
+ this->qtype_, NULL,
ZoneFinder::FIND_DEFAULT),
DataSourceError);
- EXPECT_FALSE(current_database_->searchRunning());
EXPECT_THROW(finder->find(isc::dns::Name("brokenns2.example.org."),
- isc::dns::RRType::A(), NULL,
+ this->qtype_, NULL,
ZoneFinder::FIND_DEFAULT),
DataSourceError);
- EXPECT_FALSE(current_database_->searchRunning());
}
-// Glue-OK mode. Just go trough NS delegations down (but not trough
+TYPED_TEST(DatabaseClientTest, emptyDomain) {
+ shared_ptr<DatabaseClient::Finder> finder(this->getFinder());
+
+ // This domain doesn't exist, but a subdomain of it does.
+ // Therefore we should pretend the domain is there, but contains no RRsets
+ doFindTest(*finder, isc::dns::Name("b.example.org."), this->qtype_,
+ this->qtype_, this->rrttl_, ZoneFinder::NXRRSET,
+ this->expected_rdatas_, this->expected_sig_rdatas_);
+}
+
+// Glue-OK mode. Just go through NS delegations down (but not through
// DNAME) and pretend it is not there.
-TEST_F(DatabaseClientTest, glueOK) {
- shared_ptr<DatabaseClient::Finder> finder(getFinder());
+TYPED_TEST(DatabaseClientTest, glueOK) {
+ shared_ptr<DatabaseClient::Finder> finder(this->getFinder());
- expected_rdatas_.clear();
- expected_sig_rdatas_.clear();
- doFindTest(finder, isc::dns::Name("ns.delegation.example.org."),
+ this->expected_rdatas_.clear();
+ this->expected_sig_rdatas_.clear();
+ doFindTest(*finder, isc::dns::Name("ns.delegation.example.org."),
isc::dns::RRType::AAAA(), isc::dns::RRType::AAAA(),
- isc::dns::RRTTL(3600), ZoneFinder::NXRRSET,
- expected_rdatas_, expected_sig_rdatas_,
+ this->rrttl_, ZoneFinder::NXRRSET,
+ this->expected_rdatas_, this->expected_sig_rdatas_,
isc::dns::Name("ns.delegation.example.org."),
ZoneFinder::FIND_GLUE_OK);
- doFindTest(finder, isc::dns::Name("nothere.delegation.example.org."),
+ doFindTest(*finder, isc::dns::Name("nothere.delegation.example.org."),
isc::dns::RRType::AAAA(), isc::dns::RRType::AAAA(),
- isc::dns::RRTTL(3600), ZoneFinder::NXDOMAIN,
- expected_rdatas_, expected_sig_rdatas_,
+ this->rrttl_, ZoneFinder::NXDOMAIN,
+ this->expected_rdatas_, this->expected_sig_rdatas_,
isc::dns::Name("nothere.delegation.example.org."),
ZoneFinder::FIND_GLUE_OK);
- expected_rdatas_.push_back("192.0.2.1");
- doFindTest(finder, isc::dns::Name("ns.delegation.example.org."),
- isc::dns::RRType::A(), isc::dns::RRType::A(),
- isc::dns::RRTTL(3600), ZoneFinder::SUCCESS,
- expected_rdatas_, expected_sig_rdatas_,
+ this->expected_rdatas_.push_back("192.0.2.1");
+ doFindTest(*finder, isc::dns::Name("ns.delegation.example.org."),
+ this->qtype_, this->qtype_,
+ this->rrttl_, ZoneFinder::SUCCESS,
+ this->expected_rdatas_, this->expected_sig_rdatas_,
isc::dns::Name("ns.delegation.example.org."),
ZoneFinder::FIND_GLUE_OK);
- expected_rdatas_.clear();
- expected_rdatas_.push_back("ns.example.com.");
- expected_rdatas_.push_back("ns.delegation.example.org.");
- expected_sig_rdatas_.clear();
- expected_sig_rdatas_.push_back("NS 5 3 3600 20000101000000 "
+ this->expected_rdatas_.clear();
+ this->expected_rdatas_.push_back("ns.example.com.");
+ this->expected_rdatas_.push_back("ns.delegation.example.org.");
+ this->expected_sig_rdatas_.clear();
+ this->expected_sig_rdatas_.push_back("NS 5 3 3600 20000101000000 "
"20000201000000 12345 example.org. "
"FAKEFAKEFAKE");
// When we request the NS, it should be SUCCESS, not DELEGATION
// (different in GLUE_OK)
- doFindTest(finder, isc::dns::Name("delegation.example.org."),
+ doFindTest(*finder, isc::dns::Name("delegation.example.org."),
isc::dns::RRType::NS(), isc::dns::RRType::NS(),
- isc::dns::RRTTL(3600), ZoneFinder::SUCCESS,
- expected_rdatas_, expected_sig_rdatas_,
+ this->rrttl_, ZoneFinder::SUCCESS,
+ this->expected_rdatas_, this->expected_sig_rdatas_,
isc::dns::Name("delegation.example.org."),
ZoneFinder::FIND_GLUE_OK);
- expected_rdatas_.clear();
- expected_rdatas_.push_back("dname.example.com.");
- expected_sig_rdatas_.clear();
- expected_sig_rdatas_.push_back("DNAME 5 3 3600 20000101000000 "
+ this->expected_rdatas_.clear();
+ this->expected_rdatas_.push_back("dname.example.com.");
+ this->expected_sig_rdatas_.clear();
+ this->expected_sig_rdatas_.push_back("DNAME 5 3 3600 20000101000000 "
"20000201000000 12345 example.org. "
"FAKEFAKEFAKE");
- doFindTest(finder, isc::dns::Name("below.dname.example.org."),
- isc::dns::RRType::A(), isc::dns::RRType::DNAME(),
- isc::dns::RRTTL(3600), ZoneFinder::DNAME, expected_rdatas_,
- expected_sig_rdatas_, isc::dns::Name("dname.example.org."),
- ZoneFinder::FIND_GLUE_OK);
- EXPECT_FALSE(current_database_->searchRunning());
- doFindTest(finder, isc::dns::Name("below.dname.example.org."),
+ doFindTest(*finder, isc::dns::Name("below.dname.example.org."),
+ this->qtype_, isc::dns::RRType::DNAME(),
+ this->rrttl_, ZoneFinder::DNAME, this->expected_rdatas_,
+ this->expected_sig_rdatas_,
+ isc::dns::Name("dname.example.org."), ZoneFinder::FIND_GLUE_OK);
+ doFindTest(*finder, isc::dns::Name("below.dname.example.org."),
isc::dns::RRType::AAAA(), isc::dns::RRType::DNAME(),
- isc::dns::RRTTL(3600), ZoneFinder::DNAME, expected_rdatas_,
- expected_sig_rdatas_, isc::dns::Name("dname.example.org."),
+ this->rrttl_, ZoneFinder::DNAME, this->expected_rdatas_,
+ this->expected_sig_rdatas_,
+ isc::dns::Name("dname.example.org."), ZoneFinder::FIND_GLUE_OK);
+}
+
+TYPED_TEST(DatabaseClientTest, wildcard) {
+ shared_ptr<DatabaseClient::Finder> finder(this->getFinder());
+
+ // First, simple wildcard match
+ // Check also that the RRSIG is added from the wildcard (not modified)
+ this->expected_rdatas_.push_back("192.0.2.5");
+ this->expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 "
+ "20000201000000 12345 example.org. "
+ "FAKEFAKEFAKE");
+ doFindTest(*finder, isc::dns::Name("a.wild.example.org"),
+ this->qtype_, this->qtype_, this->rrttl_,
+ ZoneFinder::SUCCESS, this->expected_rdatas_,
+ this->expected_sig_rdatas_);
+ doFindTest(*finder, isc::dns::Name("b.a.wild.example.org"),
+ this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
+ this->expected_rdatas_, this->expected_sig_rdatas_);
+ this->expected_rdatas_.clear();
+ this->expected_sig_rdatas_.clear();
+ doFindTest(*finder, isc::dns::Name("a.wild.example.org"),
+ isc::dns::RRType::AAAA(), isc::dns::RRType::AAAA(),
+ this->rrttl_, ZoneFinder::NXRRSET, this->expected_rdatas_,
+ this->expected_sig_rdatas_);
+ doFindTest(*finder, isc::dns::Name("b.a.wild.example.org"),
+ isc::dns::RRType::AAAA(), isc::dns::RRType::AAAA(),
+ this->rrttl_, ZoneFinder::NXRRSET, this->expected_rdatas_,
+ this->expected_sig_rdatas_);
+
+ // Direct request for this wildcard
+ this->expected_rdatas_.push_back("192.0.2.5");
+ this->expected_sig_rdatas_.push_back("A 5 3 3600 20000101000000 "
+ "20000201000000 12345 example.org. "
+ "FAKEFAKEFAKE");
+ doFindTest(*finder, isc::dns::Name("*.wild.example.org"),
+ this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
+ this->expected_rdatas_, this->expected_sig_rdatas_);
+ this->expected_rdatas_.clear();
+ this->expected_sig_rdatas_.clear();
+ doFindTest(*finder, isc::dns::Name("*.wild.example.org"),
+ isc::dns::RRType::AAAA(), isc::dns::RRType::AAAA(),
+ this->rrttl_, ZoneFinder::NXRRSET, this->expected_rdatas_,
+ this->expected_sig_rdatas_);
+ // This is nonsense, but check it doesn't match by some stupid accident
+ doFindTest(*finder, isc::dns::Name("a.*.wild.example.org"),
+ this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::NXDOMAIN,
+ this->expected_rdatas_, this->expected_sig_rdatas_);
+ // These should be canceled, since it is below a domain which exitsts
+ doFindTest(*finder, isc::dns::Name("nothing.here.wild.example.org"),
+ this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::NXDOMAIN,
+ this->expected_rdatas_, this->expected_sig_rdatas_);
+ doFindTest(*finder, isc::dns::Name("cancel.here.wild.example.org"),
+ this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::NXRRSET,
+ this->expected_rdatas_, this->expected_sig_rdatas_);
+ doFindTest(*finder,
+ isc::dns::Name("below.cancel.here.wild.example.org"),
+ this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::NXDOMAIN,
+ this->expected_rdatas_, this->expected_sig_rdatas_);
+ // And this should be just plain empty non-terminal domain, check
+ // the wildcard doesn't hurt it
+ doFindTest(*finder, isc::dns::Name("here.wild.example.org"),
+ this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::NXRRSET,
+ this->expected_rdatas_, this->expected_sig_rdatas_);
+ // Also make sure that the wildcard doesn't hurt the original data
+ // below the wildcard
+ this->expected_rdatas_.push_back("2001:db8::5");
+ doFindTest(*finder, isc::dns::Name("cancel.here.wild.example.org"),
+ isc::dns::RRType::AAAA(), isc::dns::RRType::AAAA(),
+ this->rrttl_, ZoneFinder::SUCCESS,
+ this->expected_rdatas_, this->expected_sig_rdatas_);
+ this->expected_rdatas_.clear();
+
+ // How wildcard go together with delegation
+ this->expected_rdatas_.push_back("ns.example.com.");
+ doFindTest(*finder, isc::dns::Name("below.delegatedwild.example.org"),
+ this->qtype_, isc::dns::RRType::NS(), this->rrttl_,
+ ZoneFinder::DELEGATION, this->expected_rdatas_,
+ this->expected_sig_rdatas_,
+ isc::dns::Name("delegatedwild.example.org"));
+ // FIXME: This doesn't look logically OK, GLUE_OK should make it transparent,
+ // so the match should either work or be canceled, but return NXDOMAIN
+ doFindTest(*finder, isc::dns::Name("below.delegatedwild.example.org"),
+ this->qtype_, isc::dns::RRType::NS(), this->rrttl_,
+ ZoneFinder::DELEGATION, this->expected_rdatas_,
+ this->expected_sig_rdatas_,
+ isc::dns::Name("delegatedwild.example.org"),
ZoneFinder::FIND_GLUE_OK);
- EXPECT_FALSE(current_database_->searchRunning());
+
+ this->expected_rdatas_.clear();
+ this->expected_rdatas_.push_back("192.0.2.5");
+ // These are direct matches
+ const char* positive_names[] = {
+ "wild.*.foo.example.org.",
+ "wild.*.foo.*.bar.example.org.",
+ NULL
+ };
+ for (const char** name(positive_names); *name != NULL; ++ name) {
+ doFindTest(*finder, isc::dns::Name(*name), this->qtype_,
+ this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
+ this->expected_rdatas_,
+ this->expected_sig_rdatas_);
+ }
+
+ // These are wildcard matches against empty nonterminal asterisk
+ this->expected_rdatas_.clear();
+ const char* negative_names[] = {
+ "a.foo.example.org.",
+ "*.foo.example.org.",
+ "foo.example.org.",
+ "wild.bar.foo.example.org.",
+ "baz.foo.*.bar.example.org",
+ "baz.foo.baz.bar.example.org",
+ "*.foo.baz.bar.example.org",
+ "*.foo.*.bar.example.org",
+ "foo.*.bar.example.org",
+ "*.bar.example.org",
+ "bar.example.org",
+ NULL
+ };
+ for (const char** name(negative_names); *name != NULL; ++ name) {
+ doFindTest(*finder, isc::dns::Name(*name), this->qtype_,
+ this->qtype_, this->rrttl_, ZoneFinder::NXRRSET,
+ this->expected_rdatas_, this->expected_sig_rdatas_);
+ }
}
-TEST_F(DatabaseClientTest, getOrigin) {
- DataSourceClient::FindResult zone(client_->findZone(Name("example.org")));
+TYPED_TEST(DatabaseClientTest, getOrigin) {
+ DataSourceClient::FindResult zone(this->client_->findZone(this->zname_));
ASSERT_EQ(result::SUCCESS, zone.code);
shared_ptr<DatabaseClient::Finder> finder(
dynamic_pointer_cast<DatabaseClient::Finder>(zone.zone_finder));
- EXPECT_EQ(42, finder->zone_id());
- EXPECT_EQ(isc::dns::Name("example.org"), finder->getOrigin());
+ if (this->is_mock_) {
+ EXPECT_EQ(READONLY_ZONE_ID, finder->zone_id());
+ }
+ EXPECT_EQ(this->zname_, finder->getOrigin());
+}
+
+TYPED_TEST(DatabaseClientTest, updaterFinder) {
+ this->updater_ = this->client_->getUpdater(this->zname_, false);
+ ASSERT_TRUE(this->updater_);
+
+ // If this update isn't replacing the zone, the finder should work
+ // just like the normal find() case.
+ if (this->is_mock_) {
+ DatabaseClient::Finder& finder = dynamic_cast<DatabaseClient::Finder&>(
+ this->updater_->getFinder());
+ EXPECT_EQ(WRITABLE_ZONE_ID, finder.zone_id());
+ }
+ this->expected_rdatas_.clear();
+ this->expected_rdatas_.push_back("192.0.2.1");
+ doFindTest(this->updater_->getFinder(), this->qname_,
+ this->qtype_, this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
+ this->expected_rdatas_, this->empty_rdatas_);
+
+ // When replacing the zone, the updater's finder shouldn't see anything
+ // in the zone until something is added.
+ this->updater_.reset();
+ this->updater_ = this->client_->getUpdater(this->zname_, true);
+ ASSERT_TRUE(this->updater_);
+ if (this->is_mock_) {
+ DatabaseClient::Finder& finder = dynamic_cast<DatabaseClient::Finder&>(
+ this->updater_->getFinder());
+ EXPECT_EQ(WRITABLE_ZONE_ID, finder.zone_id());
+ }
+ doFindTest(this->updater_->getFinder(), this->qname_, this->qtype_,
+ this->qtype_, this->rrttl_, ZoneFinder::NXDOMAIN,
+ this->empty_rdatas_, this->empty_rdatas_);
}
+TYPED_TEST(DatabaseClientTest, flushZone) {
+ // A simple update case: flush the entire zone
+ shared_ptr<DatabaseClient::Finder> finder(this->getFinder());
+
+ // Before update, the name exists.
+ EXPECT_EQ(ZoneFinder::SUCCESS, finder->find(this->qname_,
+ this->qtype_).code);
+
+ // start update in the replace mode. the normal finder should still
+ // be able to see the record, but the updater's finder shouldn't.
+ this->updater_ = this->client_->getUpdater(this->zname_, true);
+ this->setUpdateAccessor();
+ EXPECT_EQ(ZoneFinder::SUCCESS,
+ finder->find(this->qname_, this->qtype_).code);
+ EXPECT_EQ(ZoneFinder::NXDOMAIN,
+ this->updater_->getFinder().find(this->qname_,
+ this->qtype_).code);
+
+ // commit the update. now the normal finder shouldn't see it.
+ this->updater_->commit();
+ EXPECT_EQ(ZoneFinder::NXDOMAIN, finder->find(this->qname_,
+ this->qtype_).code);
+
+ // Check rollback wasn't accidentally performed.
+ EXPECT_FALSE(this->isRollbacked());
+}
+
+TYPED_TEST(DatabaseClientTest, updateCancel) {
+ // similar to the previous test, but destruct the updater before commit.
+
+ ZoneFinderPtr finder = this->client_->findZone(this->zname_).zone_finder;
+ EXPECT_EQ(ZoneFinder::SUCCESS, finder->find(this->qname_,
+ this->qtype_).code);
+
+ this->updater_ = this->client_->getUpdater(this->zname_, true);
+ this->setUpdateAccessor();
+ EXPECT_EQ(ZoneFinder::NXDOMAIN,
+ this->updater_->getFinder().find(this->qname_,
+ this->qtype_).code);
+ // DB should not have been rolled back yet.
+ EXPECT_FALSE(this->isRollbacked());
+ this->updater_.reset(); // destruct without commit
+
+ // reset() should have triggered rollback (although it doesn't affect
+ // anything to the mock accessor implementation except for the result of
+ // isRollbacked())
+ EXPECT_TRUE(this->isRollbacked(true));
+ EXPECT_EQ(ZoneFinder::SUCCESS, finder->find(this->qname_,
+ this->qtype_).code);
+}
+
+TYPED_TEST(DatabaseClientTest, exceptionFromRollback) {
+ this->updater_ = this->client_->getUpdater(this->zname_, true);
+
+ this->rrset_.reset(new RRset(Name("throw.example.org"), this->qclass_,
+ this->qtype_, this->rrttl_));
+ this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
+ this->rrset_->getClass(),
+ "192.0.2.1"));
+ this->updater_->addRRset(*this->rrset_);
+ // destruct without commit. The added name will result in an exception
+ // in the MockAccessor's rollback method. It shouldn't be propagated.
+ EXPECT_NO_THROW(this->updater_.reset());
+}
+
+TYPED_TEST(DatabaseClientTest, duplicateCommit) {
+ // duplicate commit. should result in exception.
+ this->updater_ = this->client_->getUpdater(this->zname_, true);
+ this->updater_->commit();
+ EXPECT_THROW(this->updater_->commit(), DataSourceError);
+}
+
+TYPED_TEST(DatabaseClientTest, addRRsetToNewZone) {
+ // Add a single RRset to a fresh empty zone
+ this->updater_ = this->client_->getUpdater(this->zname_, true);
+ this->updater_->addRRset(*this->rrset_);
+
+ this->expected_rdatas_.clear();
+ this->expected_rdatas_.push_back("192.0.2.2");
+ {
+ SCOPED_TRACE("add RRset");
+ doFindTest(this->updater_->getFinder(), this->qname_, this->qtype_,
+ this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
+ this->expected_rdatas_, this->empty_rdatas_);
+ }
+
+ // Similar to the previous case, but with RRSIG
+ this->updater_.reset();
+ this->updater_ = this->client_->getUpdater(this->zname_, true);
+ this->updater_->addRRset(*this->rrset_);
+ this->updater_->addRRset(*this->rrsigset_);
+
+ // confirm the expected columns were passed to the accessor (if checkable).
+ const char* const rrsig_added[] = {
+ "www.example.org.", "org.example.www.", "3600", "RRSIG", "A",
+ "A 5 3 0 20000101000000 20000201000000 0 example.org. FAKEFAKEFAKE"
+ };
+ this->checkLastAdded(rrsig_added);
+
+ this->expected_sig_rdatas_.clear();
+ this->expected_sig_rdatas_.push_back(
+ rrsig_added[DatabaseAccessor::ADD_RDATA]);
+ {
+ SCOPED_TRACE("add RRset with RRSIG");
+ doFindTest(this->updater_->getFinder(), this->qname_, this->qtype_,
+ this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
+ this->expected_rdatas_, this->expected_sig_rdatas_);
+ }
+
+ // Add the non RRSIG RRset again, to see the attempt of adding RRSIG
+ // causes any unexpected effect, in particular, whether the SIGTYPE
+ // field might remain.
+ this->updater_->addRRset(*this->rrset_);
+ const char* const rrset_added[] = {
+ "www.example.org.", "org.example.www.", "3600", "A", "", "192.0.2.2"
+ };
+ this->checkLastAdded(rrset_added);
+}
+
+TYPED_TEST(DatabaseClientTest, addRRsetToCurrentZone) {
+ // Similar to the previous test, but not replacing the existing data.
+ shared_ptr<DatabaseClient::Finder> finder(this->getFinder());
+
+ this->updater_ = this->client_->getUpdater(this->zname_, false);
+ this->updater_->addRRset(*this->rrset_);
+
+ // We should see both old and new data.
+ this->expected_rdatas_.clear();
+ this->expected_rdatas_.push_back("192.0.2.1");
+ this->expected_rdatas_.push_back("192.0.2.2");
+ {
+ SCOPED_TRACE("add RRset");
+ doFindTest(this->updater_->getFinder(), this->qname_, this->qtype_,
+ this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
+ this->expected_rdatas_, this->empty_rdatas_);
+ }
+ this->updater_->commit();
+ {
+ SCOPED_TRACE("add RRset after commit");
+ doFindTest(*finder, this->qname_, this->qtype_, this->qtype_,
+ this->rrttl_, ZoneFinder::SUCCESS, this->expected_rdatas_,
+ this->empty_rdatas_);
+ }
+}
+
+TYPED_TEST(DatabaseClientTest, addMultipleRRs) {
+ // Similar to the previous case, but the added RRset contains multiple
+ // RRs.
+ this->updater_ = this->client_->getUpdater(this->zname_, false);
+ this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
+ this->rrset_->getClass(),
+ "192.0.2.3"));
+ this->updater_->addRRset(*this->rrset_);
+ this->expected_rdatas_.clear();
+ this->expected_rdatas_.push_back("192.0.2.1");
+ this->expected_rdatas_.push_back("192.0.2.2");
+ this->expected_rdatas_.push_back("192.0.2.3");
+ {
+ SCOPED_TRACE("add multiple RRs");
+ doFindTest(this->updater_->getFinder(), this->qname_, this->qtype_,
+ this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
+ this->expected_rdatas_, this->empty_rdatas_);
+ }
+}
+
+TYPED_TEST(DatabaseClientTest, addRRsetOfLargerTTL) {
+ // Similar to the previous one, but the TTL of the added RRset is larger
+ // than that of the existing record. The finder should use the smaller
+ // one.
+ this->updater_ = this->client_->getUpdater(this->zname_, false);
+ this->rrset_->setTTL(RRTTL(7200));
+ this->updater_->addRRset(*this->rrset_);
+
+ this->expected_rdatas_.clear();
+ this->expected_rdatas_.push_back("192.0.2.1");
+ this->expected_rdatas_.push_back("192.0.2.2");
+ {
+ SCOPED_TRACE("add RRset of larger TTL");
+ doFindTest(this->updater_->getFinder(), this->qname_, this->qtype_,
+ this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
+ this->expected_rdatas_, this->empty_rdatas_);
+ }
+}
+
+TYPED_TEST(DatabaseClientTest, addRRsetOfSmallerTTL) {
+ // Similar to the previous one, but the added RRset has a smaller TTL.
+ // The added TTL should be used by the finder.
+ this->updater_ = this->client_->getUpdater(this->zname_, false);
+ this->rrset_->setTTL(RRTTL(1800));
+ this->updater_->addRRset(*this->rrset_);
+
+ this->expected_rdatas_.clear();
+ this->expected_rdatas_.push_back("192.0.2.1");
+ this->expected_rdatas_.push_back("192.0.2.2");
+ {
+ SCOPED_TRACE("add RRset of smaller TTL");
+ doFindTest(this->updater_->getFinder(), this->qname_, this->qtype_,
+ this->qtype_, RRTTL(1800), ZoneFinder::SUCCESS,
+ this->expected_rdatas_, this->empty_rdatas_);
+ }
+}
+
+TYPED_TEST(DatabaseClientTest, addSameRR) {
+ // Add the same RR as that is already in the data source.
+ // Currently the add interface doesn't try to suppress the duplicate,
+ // neither does the finder. We may want to revisit it in future versions.
+
+ this->updater_ = this->client_->getUpdater(this->zname_, false);
+ this->rrset_.reset(new RRset(this->qname_, this->qclass_, this->qtype_,
+ this->rrttl_));
+ this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
+ this->rrset_->getClass(),
+ "192.0.2.1"));
+ this->updater_->addRRset(*this->rrset_);
+ this->expected_rdatas_.clear();
+ this->expected_rdatas_.push_back("192.0.2.1");
+ this->expected_rdatas_.push_back("192.0.2.1");
+ {
+ SCOPED_TRACE("add same RR");
+ doFindTest(this->updater_->getFinder(), this->qname_, this->qtype_,
+ this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
+ this->expected_rdatas_, this->empty_rdatas_);
+ }
+}
+
+TYPED_TEST(DatabaseClientTest, addDeviantRR) {
+ this->updater_ = this->client_->getUpdater(this->zname_, false);
+
+ // RR class mismatch. This should be detected and rejected.
+ this->rrset_.reset(new RRset(this->qname_, RRClass::CH(), RRType::TXT(),
+ this->rrttl_));
+ this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
+ this->rrset_->getClass(),
+ "test text"));
+ EXPECT_THROW(this->updater_->addRRset(*this->rrset_), DataSourceError);
+
+ // Out-of-zone owner name. At a higher level this should be rejected,
+ // but it doesn't happen in this interface.
+ this->rrset_.reset(new RRset(Name("example.com"), this->qclass_,
+ this->qtype_, this->rrttl_));
+ this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
+ this->rrset_->getClass(),
+ "192.0.2.100"));
+ this->updater_->addRRset(*this->rrset_);
+
+ this->expected_rdatas_.clear();
+ this->expected_rdatas_.push_back("192.0.2.100");
+ {
+ // Note: with the find() implementation being more strict about
+ // zone cuts, this test may fail. Then the test should be updated.
+ SCOPED_TRACE("add out-of-zone RR");
+ doFindTest(this->updater_->getFinder(), Name("example.com"),
+ this->qtype_, this->qtype_, this->rrttl_,
+ ZoneFinder::SUCCESS, this->expected_rdatas_,
+ this->empty_rdatas_);
+ }
+}
+
+TYPED_TEST(DatabaseClientTest, addEmptyRRset) {
+ this->updater_ = this->client_->getUpdater(this->zname_, false);
+ this->rrset_.reset(new RRset(this->qname_, this->qclass_, this->qtype_,
+ this->rrttl_));
+ EXPECT_THROW(this->updater_->addRRset(*this->rrset_), DataSourceError);
+}
+
+TYPED_TEST(DatabaseClientTest, addAfterCommit) {
+ this->updater_ = this->client_->getUpdater(this->zname_, false);
+ this->updater_->commit();
+ EXPECT_THROW(this->updater_->addRRset(*this->rrset_), DataSourceError);
+}
+
+TYPED_TEST(DatabaseClientTest, addRRsetWithRRSIG) {
+ this->updater_ = this->client_->getUpdater(this->zname_, false);
+ this->rrset_->addRRsig(*this->rrsigset_);
+ EXPECT_THROW(this->updater_->addRRset(*this->rrset_), DataSourceError);
+}
+
+TYPED_TEST(DatabaseClientTest, deleteRRset) {
+ shared_ptr<DatabaseClient::Finder> finder(this->getFinder());
+
+ this->rrset_.reset(new RRset(this->qname_, this->qclass_, this->qtype_,
+ this->rrttl_));
+ this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
+ this->rrset_->getClass(),
+ "192.0.2.1"));
+
+ // Delete one RR from an RRset
+ this->updater_ = this->client_->getUpdater(this->zname_, false);
+ this->updater_->deleteRRset(*this->rrset_);
+
+ // Delete the only RR of a name
+ this->rrset_.reset(new RRset(Name("cname.example.org"), this->qclass_,
+ RRType::CNAME(), this->rrttl_));
+ this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
+ this->rrset_->getClass(),
+ "www.example.org"));
+ this->updater_->deleteRRset(*this->rrset_);
+
+ // The this->updater_ finder should immediately see the deleted results.
+ {
+ SCOPED_TRACE("delete RRset");
+ doFindTest(this->updater_->getFinder(), this->qname_, this->qtype_,
+ this->qtype_, this->rrttl_, ZoneFinder::NXRRSET,
+ this->empty_rdatas_, this->empty_rdatas_);
+ doFindTest(this->updater_->getFinder(), Name("cname.example.org"),
+ this->qtype_, this->qtype_, this->rrttl_,
+ ZoneFinder::NXDOMAIN, this->empty_rdatas_,
+ this->empty_rdatas_);
+ }
+
+ // before committing the change, the original finder should see the
+ // original record.
+ {
+ SCOPED_TRACE("delete RRset before commit");
+ this->expected_rdatas_.push_back("192.0.2.1");
+ doFindTest(*finder, this->qname_, this->qtype_, this->qtype_,
+ this->rrttl_, ZoneFinder::SUCCESS, this->expected_rdatas_,
+ this->empty_rdatas_);
+
+ this->expected_rdatas_.clear();
+ this->expected_rdatas_.push_back("www.example.org.");
+ doFindTest(*finder, Name("cname.example.org"), this->qtype_,
+ RRType::CNAME(), this->rrttl_, ZoneFinder::CNAME,
+ this->expected_rdatas_, this->empty_rdatas_);
+ }
+
+ // once committed, the record should be removed from the original finder's
+ // view, too.
+ this->updater_->commit();
+ {
+ SCOPED_TRACE("delete RRset after commit");
+ doFindTest(*finder, this->qname_, this->qtype_, this->qtype_,
+ this->rrttl_, ZoneFinder::NXRRSET, this->empty_rdatas_,
+ this->empty_rdatas_);
+ doFindTest(*finder, Name("cname.example.org"), this->qtype_,
+ this->qtype_, this->rrttl_, ZoneFinder::NXDOMAIN,
+ this->empty_rdatas_, this->empty_rdatas_);
+ }
+}
+
+TYPED_TEST(DatabaseClientTest, deleteRRsetToNXDOMAIN) {
+ // similar to the previous case, but it removes the only record of the
+ // given name. a subsequent find() should result in NXDOMAIN.
+ this->rrset_.reset(new RRset(Name("cname.example.org"), this->qclass_,
+ RRType::CNAME(), this->rrttl_));
+ this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
+ this->rrset_->getClass(),
+ "www.example.org"));
+
+ this->updater_ = this->client_->getUpdater(this->zname_, false);
+ this->updater_->deleteRRset(*this->rrset_);
+ {
+ SCOPED_TRACE("delete RRset to NXDOMAIN");
+ doFindTest(this->updater_->getFinder(), Name("cname.example.org"),
+ this->qtype_, this->qtype_, this->rrttl_,
+ ZoneFinder::NXDOMAIN, this->empty_rdatas_,
+ this->empty_rdatas_);
+ }
+}
+
+TYPED_TEST(DatabaseClientTest, deleteMultipleRRs) {
+ this->rrset_.reset(new RRset(this->qname_, this->qclass_, RRType::AAAA(),
+ this->rrttl_));
+ this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
+ this->rrset_->getClass(),
+ "2001:db8::1"));
+ this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
+ this->rrset_->getClass(),
+ "2001:db8::2"));
+
+ this->updater_ = this->client_->getUpdater(this->zname_, false);
+ this->updater_->deleteRRset(*this->rrset_);
+
+ {
+ SCOPED_TRACE("delete multiple RRs");
+ doFindTest(this->updater_->getFinder(), this->qname_, RRType::AAAA(),
+ this->qtype_, this->rrttl_, ZoneFinder::NXRRSET,
+ this->empty_rdatas_, this->empty_rdatas_);
+ }
+}
+
+TYPED_TEST(DatabaseClientTest, partialDelete) {
+ this->rrset_.reset(new RRset(this->qname_, this->qclass_, RRType::AAAA(),
+ this->rrttl_));
+ this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
+ this->rrset_->getClass(),
+ "2001:db8::1"));
+ // This does not exist in the test data source:
+ this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
+ this->rrset_->getClass(),
+ "2001:db8::3"));
+
+ // deleteRRset should succeed "silently", and subsequent find() should
+ // find the remaining RR.
+ this->updater_ = this->client_->getUpdater(this->zname_, false);
+ this->updater_->deleteRRset(*this->rrset_);
+ {
+ SCOPED_TRACE("partial delete");
+ this->expected_rdatas_.push_back("2001:db8::2");
+ doFindTest(this->updater_->getFinder(), this->qname_, RRType::AAAA(),
+ RRType::AAAA(), this->rrttl_, ZoneFinder::SUCCESS,
+ this->expected_rdatas_, this->empty_rdatas_);
+ }
+}
+
+TYPED_TEST(DatabaseClientTest, deleteNoMatch) {
+ // similar to the previous test, but there's not even a match in the
+ // specified RRset. Essentially there's no difference in the result.
+ this->updater_ = this->client_->getUpdater(this->zname_, false);
+ this->updater_->deleteRRset(*this->rrset_);
+ {
+ SCOPED_TRACE("delete no match");
+ this->expected_rdatas_.push_back("192.0.2.1");
+ doFindTest(this->updater_->getFinder(), this->qname_, this->qtype_,
+ this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
+ this->expected_rdatas_, this->empty_rdatas_);
+ }
+}
+
+TYPED_TEST(DatabaseClientTest, deleteWithDifferentTTL) {
+ // Our delete interface simply ignores TTL (may change in a future version)
+ this->rrset_.reset(new RRset(this->qname_, this->qclass_, this->qtype_,
+ RRTTL(1800)));
+ this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
+ this->rrset_->getClass(),
+ "192.0.2.1"));
+ this->updater_ = this->client_->getUpdater(this->zname_, false);
+ this->updater_->deleteRRset(*this->rrset_);
+ {
+ SCOPED_TRACE("delete RRset with a different TTL");
+ doFindTest(this->updater_->getFinder(), this->qname_, this->qtype_,
+ this->qtype_, this->rrttl_, ZoneFinder::NXRRSET,
+ this->empty_rdatas_, this->empty_rdatas_);
+ }
+}
+
+TYPED_TEST(DatabaseClientTest, deleteDeviantRR) {
+ this->updater_ = this->client_->getUpdater(this->zname_, false);
+
+ // RR class mismatch. This should be detected and rejected.
+ this->rrset_.reset(new RRset(this->qname_, RRClass::CH(), RRType::TXT(),
+ this->rrttl_));
+ this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
+ this->rrset_->getClass(),
+ "test text"));
+ EXPECT_THROW(this->updater_->deleteRRset(*this->rrset_), DataSourceError);
+
+ // Out-of-zone owner name. At a higher level this should be rejected,
+ // but it doesn't happen in this interface.
+ this->rrset_.reset(new RRset(Name("example.com"), this->qclass_,
+ this->qtype_, this->rrttl_));
+ this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
+ this->rrset_->getClass(),
+ "192.0.2.100"));
+ EXPECT_NO_THROW(this->updater_->deleteRRset(*this->rrset_));
+}
+
+TYPED_TEST(DatabaseClientTest, deleteAfterCommit) {
+ this->updater_ = this->client_->getUpdater(this->zname_, false);
+ this->updater_->commit();
+ EXPECT_THROW(this->updater_->deleteRRset(*this->rrset_), DataSourceError);
+}
+
+TYPED_TEST(DatabaseClientTest, deleteEmptyRRset) {
+ this->updater_ = this->client_->getUpdater(this->zname_, false);
+ this->rrset_.reset(new RRset(this->qname_, this->qclass_, this->qtype_,
+ this->rrttl_));
+ EXPECT_THROW(this->updater_->deleteRRset(*this->rrset_), DataSourceError);
+}
+
+TYPED_TEST(DatabaseClientTest, deleteRRsetWithRRSIG) {
+ this->updater_ = this->client_->getUpdater(this->zname_, false);
+ this->rrset_->addRRsig(*this->rrsigset_);
+ EXPECT_THROW(this->updater_->deleteRRset(*this->rrset_), DataSourceError);
+}
+
+TYPED_TEST(DatabaseClientTest, compoundUpdate) {
+ // This test case performs an arbitrary chosen add/delete operations
+ // in a single update transaction. Essentially there is nothing new to
+ // test here, but there may be some bugs that was overlooked and can
+ // only happen in the compound update scenario, so we test it.
+
+ this->updater_ = this->client_->getUpdater(this->zname_, false);
+
+ // add a new RR to an existing RRset
+ this->updater_->addRRset(*this->rrset_);
+ this->expected_rdatas_.clear();
+ this->expected_rdatas_.push_back("192.0.2.1");
+ this->expected_rdatas_.push_back("192.0.2.2");
+ doFindTest(this->updater_->getFinder(), this->qname_, this->qtype_,
+ this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
+ this->expected_rdatas_, this->empty_rdatas_);
+
+ // delete an existing RR
+ this->rrset_.reset(new RRset(Name("www.example.org"), this->qclass_,
+ this->qtype_, this->rrttl_));
+ this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
+ this->rrset_->getClass(),
+ "192.0.2.1"));
+ this->updater_->deleteRRset(*this->rrset_);
+ this->expected_rdatas_.clear();
+ this->expected_rdatas_.push_back("192.0.2.2");
+ doFindTest(this->updater_->getFinder(), this->qname_, this->qtype_,
+ this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
+ this->expected_rdatas_, this->empty_rdatas_);
+
+ // re-add it
+ this->updater_->addRRset(*this->rrset_);
+ this->expected_rdatas_.push_back("192.0.2.1");
+ doFindTest(this->updater_->getFinder(), this->qname_, this->qtype_,
+ this->qtype_, this->rrttl_, ZoneFinder::SUCCESS,
+ this->expected_rdatas_, this->empty_rdatas_);
+
+ // add a new RR with a new name
+ const Name newname("newname.example.org");
+ const RRType newtype(RRType::AAAA());
+ doFindTest(this->updater_->getFinder(), newname, newtype, newtype,
+ this->rrttl_, ZoneFinder::NXDOMAIN, this->empty_rdatas_,
+ this->empty_rdatas_);
+ this->rrset_.reset(new RRset(newname, this->qclass_, newtype,
+ this->rrttl_));
+ this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
+ this->rrset_->getClass(),
+ "2001:db8::10"));
+ this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
+ this->rrset_->getClass(),
+ "2001:db8::11"));
+ this->updater_->addRRset(*this->rrset_);
+ this->expected_rdatas_.clear();
+ this->expected_rdatas_.push_back("2001:db8::10");
+ this->expected_rdatas_.push_back("2001:db8::11");
+ doFindTest(this->updater_->getFinder(), newname, newtype, newtype,
+ this->rrttl_, ZoneFinder::SUCCESS, this->expected_rdatas_,
+ this->empty_rdatas_);
+
+ // delete one RR from the previous set
+ this->rrset_.reset(new RRset(newname, this->qclass_, newtype,
+ this->rrttl_));
+ this->rrset_->addRdata(rdata::createRdata(this->rrset_->getType(),
+ this->rrset_->getClass(),
+ "2001:db8::11"));
+ this->updater_->deleteRRset(*this->rrset_);
+ this->expected_rdatas_.clear();
+ this->expected_rdatas_.push_back("2001:db8::10");
+ doFindTest(this->updater_->getFinder(), newname, newtype, newtype,
+ this->rrttl_, ZoneFinder::SUCCESS, this->expected_rdatas_,
+ this->empty_rdatas_);
+
+ // Commit the changes, confirm the entire changes applied.
+ this->updater_->commit();
+ shared_ptr<DatabaseClient::Finder> finder(this->getFinder());
+ this->expected_rdatas_.clear();
+ this->expected_rdatas_.push_back("192.0.2.2");
+ this->expected_rdatas_.push_back("192.0.2.1");
+ doFindTest(*finder, this->qname_, this->qtype_, this->qtype_, this->rrttl_,
+ ZoneFinder::SUCCESS, this->expected_rdatas_,
+ this->empty_rdatas_);
+
+ this->expected_rdatas_.clear();
+ this->expected_rdatas_.push_back("2001:db8::10");
+ doFindTest(*finder, newname, newtype, newtype, this->rrttl_,
+ ZoneFinder::SUCCESS, this->expected_rdatas_,
+ this->empty_rdatas_);
+}
}
diff --git a/src/lib/datasrc/tests/memory_datasrc_unittest.cc b/src/lib/datasrc/tests/memory_datasrc_unittest.cc
index f47032f..a926935 100644
--- a/src/lib/datasrc/tests/memory_datasrc_unittest.cc
+++ b/src/lib/datasrc/tests/memory_datasrc_unittest.cc
@@ -197,6 +197,11 @@ TEST_F(InMemoryClientTest, getZoneCount) {
EXPECT_EQ(2, memory_client.getZoneCount());
}
+TEST_F(InMemoryClientTest, startUpdateZone) {
+ EXPECT_THROW(memory_client.getUpdater(Name("example.org"), false),
+ isc::NotImplemented);
+}
+
// A helper callback of masterLoad() used in InMemoryZoneFinderTest.
void
setRRset(RRsetPtr rrset, vector<RRsetPtr*>::iterator& it) {
@@ -1097,5 +1102,4 @@ TEST_F(InMemoryZoneFinderTest, getFileName) {
EXPECT_EQ(TEST_DATA_DIR "/root.zone", zone_finder_.getFileName());
EXPECT_TRUE(rootzone.getFileName().empty());
}
-
}
diff --git a/src/lib/datasrc/tests/sqlite3_accessor_unittest.cc b/src/lib/datasrc/tests/sqlite3_accessor_unittest.cc
index a631449..8b423f8 100644
--- a/src/lib/datasrc/tests/sqlite3_accessor_unittest.cc
+++ b/src/lib/datasrc/tests/sqlite3_accessor_unittest.cc
@@ -11,6 +11,10 @@
// 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 <algorithm>
+#include <vector>
+
#include <datasrc/sqlite3_accessor.h>
#include <datasrc/data_source.h>
@@ -19,8 +23,12 @@
#include <gtest/gtest.h>
#include <boost/scoped_ptr.hpp>
+#include <fstream>
+#include <sqlite3.h>
+using namespace std;
using namespace isc::datasrc;
+using boost::shared_ptr;
using isc::data::ConstElementPtr;
using isc::data::Element;
using isc::dns::RRClass;
@@ -35,6 +43,7 @@ std::string SQLITE_DBFILE_EXAMPLE_ROOT = TEST_DATA_DIR "/test-root.sqlite3";
std::string SQLITE_DBNAME_EXAMPLE_ROOT = "sqlite3_test-root.sqlite3";
std::string SQLITE_DBFILE_BROKENDB = TEST_DATA_DIR "/brokendb.sqlite3";
std::string SQLITE_DBFILE_MEMORY = ":memory:";
+std::string SQLITE_DBFILE_EXAMPLE_ORG = TEST_DATA_DIR "/example.org.sqlite3";
// The following file must be non existent and must be non"creatable";
// the sqlite3 library will try to create a new DB file if it doesn't exist,
@@ -42,116 +51,169 @@ std::string SQLITE_DBFILE_MEMORY = ":memory:";
// The "nodir", a non existent directory, is inserted for this purpose.
std::string SQLITE_DBFILE_NOTEXIST = TEST_DATA_DIR "/nodir/notexist";
+// new db file, we don't need this to be a std::string, and given the
+// raw calls we use it in a const char* is more convenient
+const char* SQLITE_NEW_DBFILE = TEST_DATA_BUILDDIR "/newdb.sqlite3";
+
// Opening works (the content is tested in different tests)
TEST(SQLite3Open, common) {
- EXPECT_NO_THROW(SQLite3Database db(SQLITE_DBFILE_EXAMPLE,
- RRClass::IN()));
+ EXPECT_NO_THROW(SQLite3Accessor accessor(SQLITE_DBFILE_EXAMPLE,
+ RRClass::IN()));
}
// The file can't be opened
TEST(SQLite3Open, notExist) {
- EXPECT_THROW(SQLite3Database db(SQLITE_DBFILE_NOTEXIST,
- RRClass::IN()), SQLite3Error);
+ EXPECT_THROW(SQLite3Accessor accessor(SQLITE_DBFILE_NOTEXIST,
+ RRClass::IN()), SQLite3Error);
}
// It rejects broken DB
TEST(SQLite3Open, brokenDB) {
- EXPECT_THROW(SQLite3Database db(SQLITE_DBFILE_BROKENDB,
- RRClass::IN()), SQLite3Error);
+ EXPECT_THROW(SQLite3Accessor accessor(SQLITE_DBFILE_BROKENDB,
+ RRClass::IN()), SQLite3Error);
}
// Test we can create the schema on the fly
TEST(SQLite3Open, memoryDB) {
- EXPECT_NO_THROW(SQLite3Database db(SQLITE_DBFILE_MEMORY,
- RRClass::IN()));
+ EXPECT_NO_THROW(SQLite3Accessor accessor(SQLITE_DBFILE_MEMORY,
+ RRClass::IN()));
}
// Test fixture for querying the db
-class SQLite3Access : public ::testing::Test {
+class SQLite3AccessorTest : public ::testing::Test {
public:
- SQLite3Access() {
+ SQLite3AccessorTest() {
initAccessor(SQLITE_DBFILE_EXAMPLE, RRClass::IN());
}
// So it can be re-created with different data
void initAccessor(const std::string& filename, const RRClass& rrclass) {
- db.reset(new SQLite3Database(filename, rrclass));
+ accessor.reset(new SQLite3Accessor(filename, rrclass));
}
- // The tested db
- boost::shared_ptr<SQLite3Database> db;
+ // The tested accessor
+ boost::shared_ptr<SQLite3Accessor> accessor;
};
// This zone exists in the data, so it should be found
-TEST_F(SQLite3Access, getZone) {
- std::pair<bool, int> result(db->getZone(Name("example.com")));
+TEST_F(SQLite3AccessorTest, getZone) {
+ std::pair<bool, int> result(accessor->getZone("example.com."));
EXPECT_TRUE(result.first);
EXPECT_EQ(1, result.second);
}
// But it should find only the zone, nothing below it
-TEST_F(SQLite3Access, subZone) {
- EXPECT_FALSE(db->getZone(Name("sub.example.com")).first);
+TEST_F(SQLite3AccessorTest, subZone) {
+ EXPECT_FALSE(accessor->getZone("sub.example.com.").first);
}
// This zone is not there at all
-TEST_F(SQLite3Access, noZone) {
- EXPECT_FALSE(db->getZone(Name("example.org")).first);
+TEST_F(SQLite3AccessorTest, noZone) {
+ EXPECT_FALSE(accessor->getZone("example.org.").first);
}
// This zone is there, but in different class
-TEST_F(SQLite3Access, noClass) {
+TEST_F(SQLite3AccessorTest, noClass) {
initAccessor(SQLITE_DBFILE_EXAMPLE, RRClass::CH());
- EXPECT_FALSE(db->getZone(Name("example.com")).first);
+ EXPECT_FALSE(accessor->getZone("example.com.").first);
}
// This tests the iterator context
-TEST_F(SQLite3Access, iterator) {
+TEST_F(SQLite3AccessorTest, iterator) {
// Our test zone is conveniently small, but not empty
- initAccessor(SQLITE_DBFILE_EXAMPLE2, RRClass::IN());
+ initAccessor(SQLITE_DBFILE_EXAMPLE_ORG, RRClass::IN());
+
+ const std::pair<bool, int> zone_info(accessor->getZone("example.org."));
+ ASSERT_TRUE(zone_info.first);
// Get the iterator context
DatabaseAccessor::IteratorContextPtr
- context(db->getAllRecords(Name("example2.com"), 1));
- ASSERT_NE(DatabaseAccessor::IteratorContextPtr(),
- context);
+ context(accessor->getAllRecords(zone_info.second));
+ ASSERT_NE(DatabaseAccessor::IteratorContextPtr(), context);
- const size_t size(5);
- std::string data[size];
+ std::string data[DatabaseAccessor::COLUMN_COUNT];
// Get and check the first and only record
- EXPECT_TRUE(context->getNext(data, size));
- EXPECT_EQ("example2.com.", data[4]);
- EXPECT_EQ("SOA", data[0]);
- EXPECT_EQ("master.example2.com. admin.example2.com. "
- "1234 3600 1800 2419200 7200", data[3]);
- EXPECT_EQ("3600", data[1]);
- // Check there's no other
- EXPECT_FALSE(context->getNext(data, size));
-}
-
-TEST_F(SQLite3Access, iteratorColumnCount) {
- // Our test zone is conveniently small, but not empty
- initAccessor(SQLITE_DBFILE_EXAMPLE2, RRClass::IN());
+ EXPECT_TRUE(context->getNext(data));
+ EXPECT_EQ("DNAME", data[DatabaseAccessor::TYPE_COLUMN]);
+ EXPECT_EQ("3600", data[DatabaseAccessor::TTL_COLUMN]);
+ EXPECT_EQ("dname.example.info.", data[DatabaseAccessor::RDATA_COLUMN]);
+ EXPECT_EQ("dname.example.org.", data[DatabaseAccessor::NAME_COLUMN]);
+
+ EXPECT_TRUE(context->getNext(data));
+ EXPECT_EQ("DNAME", data[DatabaseAccessor::TYPE_COLUMN]);
+ EXPECT_EQ("3600", data[DatabaseAccessor::TTL_COLUMN]);
+ EXPECT_EQ("dname2.example.info.", data[DatabaseAccessor::RDATA_COLUMN]);
+ EXPECT_EQ("dname2.foo.example.org.", data[DatabaseAccessor::NAME_COLUMN]);
+
+ EXPECT_TRUE(context->getNext(data));
+ EXPECT_EQ("MX", data[DatabaseAccessor::TYPE_COLUMN]);
+ EXPECT_EQ("3600", data[DatabaseAccessor::TTL_COLUMN]);
+ EXPECT_EQ("10 mail.example.org.", data[DatabaseAccessor::RDATA_COLUMN]);
+ EXPECT_EQ("example.org.", data[DatabaseAccessor::NAME_COLUMN]);
+
+ EXPECT_TRUE(context->getNext(data));
+ EXPECT_EQ("NS", data[DatabaseAccessor::TYPE_COLUMN]);
+ EXPECT_EQ("3600", data[DatabaseAccessor::TTL_COLUMN]);
+ EXPECT_EQ("ns1.example.org.", data[DatabaseAccessor::RDATA_COLUMN]);
+ EXPECT_EQ("example.org.", data[DatabaseAccessor::NAME_COLUMN]);
+
+ EXPECT_TRUE(context->getNext(data));
+ EXPECT_EQ("NS", data[DatabaseAccessor::TYPE_COLUMN]);
+ EXPECT_EQ("3600", data[DatabaseAccessor::TTL_COLUMN]);
+ EXPECT_EQ("ns2.example.org.", data[DatabaseAccessor::RDATA_COLUMN]);
+ EXPECT_EQ("example.org.", data[DatabaseAccessor::NAME_COLUMN]);
+
+ EXPECT_TRUE(context->getNext(data));
+ EXPECT_EQ("NS", data[DatabaseAccessor::TYPE_COLUMN]);
+ EXPECT_EQ("3600", data[DatabaseAccessor::TTL_COLUMN]);
+ EXPECT_EQ("ns3.example.org.", data[DatabaseAccessor::RDATA_COLUMN]);
+ EXPECT_EQ("example.org.", data[DatabaseAccessor::NAME_COLUMN]);
+
+ EXPECT_TRUE(context->getNext(data));
+ EXPECT_EQ("SOA", data[DatabaseAccessor::TYPE_COLUMN]);
+ EXPECT_EQ("3600", data[DatabaseAccessor::TTL_COLUMN]);
+ EXPECT_EQ("ns1.example.org. admin.example.org. "
+ "1234 3600 1800 2419200 7200",
+ data[DatabaseAccessor::RDATA_COLUMN]);
+ EXPECT_EQ("example.org.", data[DatabaseAccessor::NAME_COLUMN]);
+
+ EXPECT_TRUE(context->getNext(data));
+ EXPECT_EQ("A", data[DatabaseAccessor::TYPE_COLUMN]);
+ EXPECT_EQ("3600", data[DatabaseAccessor::TTL_COLUMN]);
+ EXPECT_EQ("192.0.2.10", data[DatabaseAccessor::RDATA_COLUMN]);
+ EXPECT_EQ("mail.example.org.", data[DatabaseAccessor::NAME_COLUMN]);
+
+ EXPECT_TRUE(context->getNext(data));
+ EXPECT_EQ("A", data[DatabaseAccessor::TYPE_COLUMN]);
+ EXPECT_EQ("3600", data[DatabaseAccessor::TTL_COLUMN]);
+ EXPECT_EQ("192.0.2.101", data[DatabaseAccessor::RDATA_COLUMN]);
+ EXPECT_EQ("ns.sub.example.org.", data[DatabaseAccessor::NAME_COLUMN]);
+
+ EXPECT_TRUE(context->getNext(data));
+ EXPECT_EQ("NS", data[DatabaseAccessor::TYPE_COLUMN]);
+ EXPECT_EQ("3600", data[DatabaseAccessor::TTL_COLUMN]);
+ EXPECT_EQ("ns.sub.example.org.", data[DatabaseAccessor::RDATA_COLUMN]);
+ EXPECT_EQ("sub.example.org.", data[DatabaseAccessor::NAME_COLUMN]);
+
+ EXPECT_TRUE(context->getNext(data));
+ EXPECT_EQ("A", data[DatabaseAccessor::TYPE_COLUMN]);
+ EXPECT_EQ("3600", data[DatabaseAccessor::TTL_COLUMN]);
+ EXPECT_EQ("192.0.2.1", data[DatabaseAccessor::RDATA_COLUMN]);
+ EXPECT_EQ("www.example.org.", data[DatabaseAccessor::NAME_COLUMN]);
- // Get the iterator context
- DatabaseAccessor::IteratorContextPtr
- context(db->getAllRecords(Name("example2.com"), 1));
- ASSERT_NE(DatabaseAccessor::IteratorContextPtr(),
- context);
+ // Check there's no other
+ EXPECT_FALSE(context->getNext(data));
- EXPECT_THROW(context->getNext(NULL, 0), DataSourceError);
- std::string data[6];
- EXPECT_THROW(context->getNext(data, 4), DataSourceError);
- EXPECT_THROW(context->getNext(data, 6), DataSourceError);
- EXPECT_NO_THROW(context->getNext(data, 5));
+ // And make sure calling it again won't cause problems.
+ EXPECT_FALSE(context->getNext(data));
}
TEST(SQLite3Open, getDBNameExample2) {
- SQLite3Database db(SQLITE_DBFILE_EXAMPLE2, RRClass::IN());
- EXPECT_EQ(SQLITE_DBNAME_EXAMPLE2, db.getDBName());
+ SQLite3Accessor accessor(SQLITE_DBFILE_EXAMPLE2, RRClass::IN());
+ EXPECT_EQ(SQLITE_DBNAME_EXAMPLE2, accessor.getDBName());
}
TEST(SQLite3Open, getDBNameExampleROOT) {
- SQLite3Database db(SQLITE_DBFILE_EXAMPLE_ROOT, RRClass::IN());
- EXPECT_EQ(SQLITE_DBNAME_EXAMPLE_ROOT, db.getDBName());
+ SQLite3Accessor accessor(SQLITE_DBFILE_EXAMPLE_ROOT, RRClass::IN());
+ EXPECT_EQ(SQLITE_DBNAME_EXAMPLE_ROOT, accessor.getDBName());
}
// Simple function to cound the number of records for
@@ -164,109 +226,94 @@ checkRecordRow(const std::string columns[],
const std::string& field3,
const std::string& field4)
{
- EXPECT_EQ(field0, columns[0]);
- EXPECT_EQ(field1, columns[1]);
- EXPECT_EQ(field2, columns[2]);
- EXPECT_EQ(field3, columns[3]);
- EXPECT_EQ(field4, columns[4]);
+ EXPECT_EQ(field0, columns[DatabaseAccessor::TYPE_COLUMN]);
+ EXPECT_EQ(field1, columns[DatabaseAccessor::TTL_COLUMN]);
+ EXPECT_EQ(field2, columns[DatabaseAccessor::SIGTYPE_COLUMN]);
+ EXPECT_EQ(field3, columns[DatabaseAccessor::RDATA_COLUMN]);
+ EXPECT_EQ(field4, columns[DatabaseAccessor::NAME_COLUMN]);
}
-TEST_F(SQLite3Access, getRecords) {
- const std::pair<bool, int> zone_info(db->getZone(Name("example.com")));
+TEST_F(SQLite3AccessorTest, getRecords) {
+ const std::pair<bool, int> zone_info(accessor->getZone("example.com."));
ASSERT_TRUE(zone_info.first);
const int zone_id = zone_info.second;
ASSERT_EQ(1, zone_id);
- const size_t column_count = DatabaseAccessor::COLUMN_COUNT;
- std::string columns[column_count];
-
- // without search, getNext() should return false
- EXPECT_FALSE(db->getNextRecord(columns, column_count));
- checkRecordRow(columns, "", "", "", "", "");
-
- db->searchForRecords(zone_id, "foo.bar.");
- EXPECT_FALSE(db->getNextRecord(columns, column_count));
- checkRecordRow(columns, "", "", "", "", "");
+ std::string columns[DatabaseAccessor::COLUMN_COUNT];
- db->searchForRecords(zone_id, "");
- EXPECT_FALSE(db->getNextRecord(columns, column_count));
+ DatabaseAccessor::IteratorContextPtr
+ context(accessor->getRecords("foo.bar", 1));
+ ASSERT_NE(DatabaseAccessor::IteratorContextPtr(),
+ context);
+ EXPECT_FALSE(context->getNext(columns));
checkRecordRow(columns, "", "", "", "", "");
- // Should error on a bad number of columns
- EXPECT_THROW(db->getNextRecord(columns, 4), DataSourceError);
- EXPECT_THROW(db->getNextRecord(columns, 6), DataSourceError);
-
// now try some real searches
- db->searchForRecords(zone_id, "foo.example.com.");
- ASSERT_TRUE(db->getNextRecord(columns, column_count));
+ context = accessor->getRecords("foo.example.com.", zone_id);
+ ASSERT_TRUE(context->getNext(columns));
checkRecordRow(columns, "CNAME", "3600", "",
- "cnametest.example.org.", "foo.example.com.");
- ASSERT_TRUE(db->getNextRecord(columns, column_count));
+ "cnametest.example.org.", "");
+ ASSERT_TRUE(context->getNext(columns));
checkRecordRow(columns, "RRSIG", "3600", "CNAME",
"CNAME 5 3 3600 20100322084538 20100220084538 33495 "
- "example.com. FAKEFAKEFAKEFAKE", "foo.example.com.");
- ASSERT_TRUE(db->getNextRecord(columns, column_count));
+ "example.com. FAKEFAKEFAKEFAKE", "");
+ ASSERT_TRUE(context->getNext(columns));
checkRecordRow(columns, "NSEC", "7200", "",
- "mail.example.com. CNAME RRSIG NSEC", "foo.example.com.");
- ASSERT_TRUE(db->getNextRecord(columns, column_count));
+ "mail.example.com. CNAME RRSIG NSEC", "");
+ ASSERT_TRUE(context->getNext(columns));
checkRecordRow(columns, "RRSIG", "7200", "NSEC",
"NSEC 5 3 7200 20100322084538 20100220084538 33495 "
- "example.com. FAKEFAKEFAKEFAKE", "foo.example.com.");
- EXPECT_FALSE(db->getNextRecord(columns, column_count));
+ "example.com. FAKEFAKEFAKEFAKE", "");
+ EXPECT_FALSE(context->getNext(columns));
+
// with no more records, the array should not have been modified
checkRecordRow(columns, "RRSIG", "7200", "NSEC",
"NSEC 5 3 7200 20100322084538 20100220084538 33495 "
- "example.com. FAKEFAKEFAKEFAKE", "foo.example.com.");
+ "example.com. FAKEFAKEFAKEFAKE", "");
- db->searchForRecords(zone_id, "example.com.");
- ASSERT_TRUE(db->getNextRecord(columns, column_count));
+ context = accessor->getRecords("example.com.", zone_id);
+ ASSERT_TRUE(context->getNext(columns));
checkRecordRow(columns, "SOA", "3600", "",
"master.example.com. admin.example.com. "
- "1234 3600 1800 2419200 7200", "example.com.");
- ASSERT_TRUE(db->getNextRecord(columns, column_count));
+ "1234 3600 1800 2419200 7200", "");
+ ASSERT_TRUE(context->getNext(columns));
checkRecordRow(columns, "RRSIG", "3600", "SOA",
"SOA 5 2 3600 20100322084538 20100220084538 "
- "33495 example.com. FAKEFAKEFAKEFAKE", "example.com.");
- ASSERT_TRUE(db->getNextRecord(columns, column_count));
- checkRecordRow(columns, "NS", "1200", "", "dns01.example.com.",
- "example.com.");
- ASSERT_TRUE(db->getNextRecord(columns, column_count));
- checkRecordRow(columns, "NS", "3600", "", "dns02.example.com.",
- "example.com.");
- ASSERT_TRUE(db->getNextRecord(columns, column_count));
- checkRecordRow(columns, "NS", "1800", "", "dns03.example.com.",
- "example.com.");
- ASSERT_TRUE(db->getNextRecord(columns, column_count));
+ "33495 example.com. FAKEFAKEFAKEFAKE", "");
+ ASSERT_TRUE(context->getNext(columns));
+ checkRecordRow(columns, "NS", "1200", "", "dns01.example.com.", "");
+ ASSERT_TRUE(context->getNext(columns));
+ checkRecordRow(columns, "NS", "3600", "", "dns02.example.com.", "");
+ ASSERT_TRUE(context->getNext(columns));
+ checkRecordRow(columns, "NS", "1800", "", "dns03.example.com.", "");
+ ASSERT_TRUE(context->getNext(columns));
checkRecordRow(columns, "RRSIG", "3600", "NS",
"NS 5 2 3600 20100322084538 20100220084538 "
- "33495 example.com. FAKEFAKEFAKEFAKE",
- "example.com.");
- ASSERT_TRUE(db->getNextRecord(columns, column_count));
- checkRecordRow(columns, "MX", "3600", "", "10 mail.example.com.",
- "example.com.");
- ASSERT_TRUE(db->getNextRecord(columns, column_count));
+ "33495 example.com. FAKEFAKEFAKEFAKE", "");
+ ASSERT_TRUE(context->getNext(columns));
+ checkRecordRow(columns, "MX", "3600", "", "10 mail.example.com.", "");
+ ASSERT_TRUE(context->getNext(columns));
checkRecordRow(columns, "MX", "3600", "",
- "20 mail.subzone.example.com.", "example.com.");
- ASSERT_TRUE(db->getNextRecord(columns, column_count));
+ "20 mail.subzone.example.com.", "");
+ ASSERT_TRUE(context->getNext(columns));
checkRecordRow(columns, "RRSIG", "3600", "MX",
"MX 5 2 3600 20100322084538 20100220084538 "
- "33495 example.com. FAKEFAKEFAKEFAKE", "example.com.");
- ASSERT_TRUE(db->getNextRecord(columns, column_count));
+ "33495 example.com. FAKEFAKEFAKEFAKE", "");
+ ASSERT_TRUE(context->getNext(columns));
checkRecordRow(columns, "NSEC", "7200", "",
- "cname-ext.example.com. NS SOA MX RRSIG NSEC DNSKEY",
- "example.com.");
- ASSERT_TRUE(db->getNextRecord(columns, column_count));
+ "cname-ext.example.com. NS SOA MX RRSIG NSEC DNSKEY", "");
+ ASSERT_TRUE(context->getNext(columns));
checkRecordRow(columns, "RRSIG", "7200", "NSEC",
"NSEC 5 2 7200 20100322084538 20100220084538 "
- "33495 example.com. FAKEFAKEFAKEFAKE", "example.com.");
- ASSERT_TRUE(db->getNextRecord(columns, column_count));
+ "33495 example.com. FAKEFAKEFAKEFAKE", "");
+ ASSERT_TRUE(context->getNext(columns));
checkRecordRow(columns, "DNSKEY", "3600", "",
"256 3 5 AwEAAcOUBllYc1hf7ND9uDy+Yz1BF3sI0m4q NGV7W"
"cTD0WEiuV7IjXgHE36fCmS9QsUxSSOV o1I/FMxI2PJVqTYHkX"
"FBS7AzLGsQYMU7UjBZ SotBJ6Imt5pXMu+lEDNy8TOUzG3xm7g"
- "0qcbW YF6qCEfvZoBtAqi5Rk7Mlrqs8agxYyMx", "example.com.");
- ASSERT_TRUE(db->getNextRecord(columns, column_count));
+ "0qcbW YF6qCEfvZoBtAqi5Rk7Mlrqs8agxYyMx", "");
+ ASSERT_TRUE(context->getNext(columns));
checkRecordRow(columns, "DNSKEY", "3600", "",
"257 3 5 AwEAAe5WFbxdCPq2jZrZhlMj7oJdff3W7syJ tbvzg"
"62tRx0gkoCDoBI9DPjlOQG0UAbj+xUV 4HQZJStJaZ+fHU5AwV"
@@ -275,20 +322,413 @@ TEST_F(SQLite3Access, getRecords) {
"qiODyNZYQ+ZrLmF0KIJ2yPN3iO6Zq 23TaOrVTjB7d1a/h31OD"
"fiHAxFHrkY3t3D5J R9Nsl/7fdRmSznwtcSDgLXBoFEYmw6p86"
"Acv RyoYNcL1SXjaKVLG5jyU3UR+LcGZT5t/0xGf oIK/aKwEN"
- "rsjcKZZj660b1M=", "example.com.");
- ASSERT_TRUE(db->getNextRecord(columns, column_count));
+ "rsjcKZZj660b1M=", "");
+ ASSERT_TRUE(context->getNext(columns));
checkRecordRow(columns, "RRSIG", "3600", "DNSKEY",
"DNSKEY 5 2 3600 20100322084538 20100220084538 "
- "4456 example.com. FAKEFAKEFAKEFAKE", "example.com.");
- ASSERT_TRUE(db->getNextRecord(columns, column_count));
+ "4456 example.com. FAKEFAKEFAKEFAKE", "");
+ ASSERT_TRUE(context->getNext(columns));
checkRecordRow(columns, "RRSIG", "3600", "DNSKEY",
"DNSKEY 5 2 3600 20100322084538 20100220084538 "
- "33495 example.com. FAKEFAKEFAKEFAKE", "example.com.");
- EXPECT_FALSE(db->getNextRecord(columns, column_count));
+ "33495 example.com. FAKEFAKEFAKEFAKE", "");
+ EXPECT_FALSE(context->getNext(columns));
// getnextrecord returning false should mean array is not altered
checkRecordRow(columns, "RRSIG", "3600", "DNSKEY",
"DNSKEY 5 2 3600 20100322084538 20100220084538 "
- "33495 example.com. FAKEFAKEFAKEFAKE", "example.com.");
+ "33495 example.com. FAKEFAKEFAKEFAKE", "");
+
+ // check that another getNext does not cause problems
+ EXPECT_FALSE(context->getNext(columns));
+
+ // Try searching for subdomain
+ // There's foo.bar.example.com in the data
+ context = accessor->getRecords("bar.example.com.", zone_id, true);
+ ASSERT_TRUE(context->getNext(columns));
+ checkRecordRow(columns, "A", "3600", "", "192.0.2.1", "");
+ EXPECT_FALSE(context->getNext(columns));
+ // But we shouldn't match mix.example.com here
+ context = accessor->getRecords("ix.example.com.", zone_id, true);
+ EXPECT_FALSE(context->getNext(columns));
+}
+
+// Test fixture for creating a db that automatically deletes it before start,
+// and when done
+class SQLite3Create : public ::testing::Test {
+public:
+ SQLite3Create() {
+ remove(SQLITE_NEW_DBFILE);
+ }
+
+ ~SQLite3Create() {
+ remove(SQLITE_NEW_DBFILE);
+ }
+};
+
+bool isReadable(const char* filename) {
+ return (std::ifstream(filename).is_open());
+}
+
+TEST_F(SQLite3Create, creationtest) {
+ ASSERT_FALSE(isReadable(SQLITE_NEW_DBFILE));
+ // Should simply be created
+ SQLite3Accessor accessor(SQLITE_NEW_DBFILE, RRClass::IN());
+ ASSERT_TRUE(isReadable(SQLITE_NEW_DBFILE));
+}
+
+TEST_F(SQLite3Create, emptytest) {
+ ASSERT_FALSE(isReadable(SQLITE_NEW_DBFILE));
+
+ // open one manualle
+ sqlite3* db;
+ ASSERT_EQ(SQLITE_OK, sqlite3_open(SQLITE_NEW_DBFILE, &db));
+
+ // empty, but not locked, so creating it now should work
+ SQLite3Accessor accessor2(SQLITE_NEW_DBFILE, RRClass::IN());
+
+ sqlite3_close(db);
+
+ // should work now that we closed it
+ SQLite3Accessor accessor3(SQLITE_NEW_DBFILE, RRClass::IN());
+}
+
+TEST_F(SQLite3Create, lockedtest) {
+ ASSERT_FALSE(isReadable(SQLITE_NEW_DBFILE));
+
+ // open one manually
+ sqlite3* db;
+ ASSERT_EQ(SQLITE_OK, sqlite3_open(SQLITE_NEW_DBFILE, &db));
+ sqlite3_exec(db, "BEGIN EXCLUSIVE TRANSACTION", NULL, NULL, NULL);
+
+ // should not be able to open it
+ EXPECT_THROW(SQLite3Accessor accessor2(SQLITE_NEW_DBFILE, RRClass::IN()),
+ SQLite3Error);
+
+ sqlite3_exec(db, "ROLLBACK TRANSACTION", NULL, NULL, NULL);
+
+ // should work now that we closed it
+ SQLite3Accessor accessor3(SQLITE_NEW_DBFILE, RRClass::IN());
+}
+
+TEST_F(SQLite3AccessorTest, clone) {
+ shared_ptr<DatabaseAccessor> cloned = accessor->clone();
+ EXPECT_EQ(accessor->getDBName(), cloned->getDBName());
+
+ // The cloned accessor should have a separate connection and search
+ // context, so it should be able to perform search in concurrent with
+ // the original accessor.
+ string columns1[DatabaseAccessor::COLUMN_COUNT];
+ string columns2[DatabaseAccessor::COLUMN_COUNT];
+
+ const std::pair<bool, int> zone_info1(
+ accessor->getZone("example.com."));
+ DatabaseAccessor::IteratorContextPtr iterator1 =
+ accessor->getRecords("foo.example.com.", zone_info1.second);
+ const std::pair<bool, int> zone_info2(
+ accessor->getZone("example.com."));
+ DatabaseAccessor::IteratorContextPtr iterator2 =
+ cloned->getRecords("foo.example.com.", zone_info2.second);
+
+ ASSERT_TRUE(iterator1->getNext(columns1));
+ checkRecordRow(columns1, "CNAME", "3600", "", "cnametest.example.org.",
+ "");
+
+ ASSERT_TRUE(iterator2->getNext(columns2));
+ checkRecordRow(columns2, "CNAME", "3600", "", "cnametest.example.org.",
+ "");
+}
+
+//
+// Commonly used data for update tests
+//
+const char* const common_expected_data[] = {
+ // Test record already stored in the tested sqlite3 DB file.
+ "foo.bar.example.com.", "com.example.bar.foo.", "3600", "A", "",
+ "192.0.2.1"
+};
+const char* const new_data[] = {
+ // Newly added data commonly used by some of the tests below
+ "newdata.example.com.", "com.example.newdata.", "3600", "A", "",
+ "192.0.2.1"
+};
+const char* const deleted_data[] = {
+ // Existing data to be removed commonly used by some of the tests below
+ "foo.bar.example.com.", "A", "192.0.2.1"
+};
+
+class SQLite3Update : public SQLite3AccessorTest {
+protected:
+ SQLite3Update() {
+ // Note: if "installing" the test file fails some of the subsequent
+ // tests would fail.
+ const char *install_cmd = INSTALL_PROG " " TEST_DATA_DIR
+ "/test.sqlite3 " TEST_DATA_BUILDDIR
+ "/test.sqlite3.copied";
+ if (system(install_cmd) != 0) {
+ // any exception will do, this is failure in test setup, but nice
+ // to show the command that fails, and shouldn't be caught
+ isc_throw(isc::Exception,
+ "Error setting up; command failed: " << install_cmd);
+ };
+ initAccessor(TEST_DATA_BUILDDIR "/test.sqlite3.copied", RRClass::IN());
+ zone_id = accessor->getZone("example.com.").second;
+ another_accessor.reset(new SQLite3Accessor(
+ TEST_DATA_BUILDDIR "/test.sqlite3.copied",
+ RRClass::IN()));
+ expected_stored.push_back(common_expected_data);
+ }
+
+ int zone_id;
+ std::string get_columns[DatabaseAccessor::COLUMN_COUNT];
+ std::string add_columns[DatabaseAccessor::ADD_COLUMN_COUNT];
+ std::string del_params[DatabaseAccessor::DEL_PARAM_COUNT];
+
+ vector<const char* const*> expected_stored; // placeholder for checkRecords
+ vector<const char* const*> empty_stored; // indicate no corresponding data
+
+ // Another accessor, emulating one running on a different process/thread
+ shared_ptr<SQLite3Accessor> another_accessor;
+ DatabaseAccessor::IteratorContextPtr iterator;
+};
+
+void
+checkRecords(SQLite3Accessor& accessor, int zone_id, const std::string& name,
+ vector<const char* const*> expected_rows)
+{
+ DatabaseAccessor::IteratorContextPtr iterator =
+ accessor.getRecords(name, zone_id);
+ std::string columns[DatabaseAccessor::COLUMN_COUNT];
+ vector<const char* const*>::const_iterator it = expected_rows.begin();
+ while (iterator->getNext(columns)) {
+ ASSERT_TRUE(it != expected_rows.end());
+ checkRecordRow(columns, (*it)[3], (*it)[2], (*it)[4], (*it)[5], "");
+ ++it;
+ }
+ EXPECT_TRUE(it == expected_rows.end());
+}
+
+TEST_F(SQLite3Update, emptyUpdate) {
+ // If we do nothing between start and commit, the zone content
+ // should be intact.
+
+ checkRecords(*accessor, zone_id, "foo.bar.example.com.", expected_stored);
+ zone_id = accessor->startUpdateZone("example.com.", false).second;
+ checkRecords(*accessor, zone_id, "foo.bar.example.com.", expected_stored);
+ accessor->commitUpdateZone();
+ checkRecords(*accessor, zone_id, "foo.bar.example.com.", expected_stored);
+}
+
+TEST_F(SQLite3Update, flushZone) {
+ // With 'replace' being true startUpdateZone() will flush the existing
+ // zone content.
+
+ checkRecords(*accessor, zone_id, "foo.bar.example.com.", expected_stored);
+ zone_id = accessor->startUpdateZone("example.com.", true).second;
+ checkRecords(*accessor, zone_id, "foo.bar.example.com.", empty_stored);
+ accessor->commitUpdateZone();
+ checkRecords(*accessor, zone_id, "foo.bar.example.com.", empty_stored);
+}
+
+TEST_F(SQLite3Update, readWhileUpdate) {
+ zone_id = accessor->startUpdateZone("example.com.", true).second;
+ checkRecords(*accessor, zone_id, "foo.bar.example.com.", empty_stored);
+
+ // Until commit is done, the other accessor should see the old data
+ checkRecords(*another_accessor, zone_id, "foo.bar.example.com.",
+ expected_stored);
+
+ // Once the changes are committed, the other accessor will see the new
+ // data.
+ accessor->commitUpdateZone();
+ checkRecords(*another_accessor, zone_id, "foo.bar.example.com.",
+ empty_stored);
+}
+
+TEST_F(SQLite3Update, rollback) {
+ zone_id = accessor->startUpdateZone("example.com.", true).second;
+ checkRecords(*accessor, zone_id, "foo.bar.example.com.", empty_stored);
+
+ // Rollback will revert the change made by startUpdateZone(, true).
+ accessor->rollbackUpdateZone();
+ checkRecords(*accessor, zone_id, "foo.bar.example.com.", expected_stored);
+}
+
+TEST_F(SQLite3Update, rollbackFailure) {
+ // This test emulates a rare scenario of making rollback attempt fail.
+ // The iterator is paused in the middle of getting records, which prevents
+ // the rollback operation at the end of the test.
+
+ string columns[DatabaseAccessor::COLUMN_COUNT];
+ iterator = accessor->getRecords("example.com.", zone_id);
+ EXPECT_TRUE(iterator->getNext(columns));
+
+ accessor->startUpdateZone("example.com.", true);
+ EXPECT_THROW(accessor->rollbackUpdateZone(), DataSourceError);
+}
+
+TEST_F(SQLite3Update, commitConflict) {
+ // Start reading the DB by another accessor. We should stop at a single
+ // call to getNextRecord() to keep holding the lock.
+ iterator = another_accessor->getRecords("foo.example.com.", zone_id);
+ EXPECT_TRUE(iterator->getNext(get_columns));
+
+ // Due to getNextRecord() above, the other accessor holds a DB lock,
+ // which will prevent commit.
+ zone_id = accessor->startUpdateZone("example.com.", true).second;
+ checkRecords(*accessor, zone_id, "foo.bar.example.com.", empty_stored);
+ EXPECT_THROW(accessor->commitUpdateZone(), DataSourceError);
+ accessor->rollbackUpdateZone(); // rollback should still succeed
+
+ checkRecords(*accessor, zone_id, "foo.bar.example.com.", expected_stored);
+}
+
+TEST_F(SQLite3Update, updateConflict) {
+ // Similar to the previous case, but this is a conflict with another
+ // update attempt. Note that these two accessors modify disjoint sets
+ // of data; sqlite3 only has a coarse-grained lock so we cannot allow
+ // these updates to run concurrently.
+ EXPECT_TRUE(another_accessor->startUpdateZone("sql1.example.com.",
+ true).first);
+ EXPECT_THROW(accessor->startUpdateZone("example.com.", true),
+ DataSourceError);
+ checkRecords(*accessor, zone_id, "foo.bar.example.com.", expected_stored);
+
+ // Once we rollback the other attempt of change, we should be able to
+ // start and commit the transaction using the main accessor.
+ another_accessor->rollbackUpdateZone();
+ accessor->startUpdateZone("example.com.", true);
+ accessor->commitUpdateZone();
+}
+
+TEST_F(SQLite3Update, duplicateUpdate) {
+ accessor->startUpdateZone("example.com.", false);
+ EXPECT_THROW(accessor->startUpdateZone("example.com.", false),
+ DataSourceError);
+}
+
+TEST_F(SQLite3Update, commitWithoutTransaction) {
+ EXPECT_THROW(accessor->commitUpdateZone(), DataSourceError);
+}
+
+TEST_F(SQLite3Update, rollbackWithoutTransaction) {
+ EXPECT_THROW(accessor->rollbackUpdateZone(), DataSourceError);
+}
+
+TEST_F(SQLite3Update, addRecord) {
+ // Before update, there should be no record for this name
+ checkRecords(*accessor, zone_id, "newdata.example.com.", empty_stored);
+
+ zone_id = accessor->startUpdateZone("example.com.", false).second;
+ copy(new_data, new_data + DatabaseAccessor::ADD_COLUMN_COUNT,
+ add_columns);
+ accessor->addRecordToZone(add_columns);
+
+ expected_stored.clear();
+ expected_stored.push_back(new_data);
+ checkRecords(*accessor, zone_id, "newdata.example.com.", expected_stored);
+
+ // Commit the change, and confirm the new data is still there.
+ accessor->commitUpdateZone();
+ checkRecords(*accessor, zone_id, "newdata.example.com.", expected_stored);
+}
+
+TEST_F(SQLite3Update, addThenRollback) {
+ zone_id = accessor->startUpdateZone("example.com.", false).second;
+ copy(new_data, new_data + DatabaseAccessor::ADD_COLUMN_COUNT,
+ add_columns);
+ accessor->addRecordToZone(add_columns);
+
+ expected_stored.clear();
+ expected_stored.push_back(new_data);
+ checkRecords(*accessor, zone_id, "newdata.example.com.", expected_stored);
+
+ accessor->rollbackUpdateZone();
+ checkRecords(*accessor, zone_id, "newdata.example.com.", empty_stored);
+}
+
+TEST_F(SQLite3Update, duplicateAdd) {
+ const char* const dup_data[] = {
+ "foo.bar.example.com.", "com.example.bar.foo.", "3600", "A", "",
+ "192.0.2.1"
+ };
+ expected_stored.clear();
+ expected_stored.push_back(dup_data);
+ checkRecords(*accessor, zone_id, "foo.bar.example.com.", expected_stored);
+
+ // Adding exactly the same data. As this backend is "dumb", another
+ // row of the same content will be inserted.
+ copy(dup_data, dup_data + DatabaseAccessor::ADD_COLUMN_COUNT,
+ add_columns);
+ zone_id = accessor->startUpdateZone("example.com.", false).second;
+ accessor->addRecordToZone(add_columns);
+ expected_stored.push_back(dup_data);
+ checkRecords(*accessor, zone_id, "foo.bar.example.com.", expected_stored);
+}
+
+TEST_F(SQLite3Update, invalidAdd) {
+ // An attempt of add before an explicit start of transaction
+ EXPECT_THROW(accessor->addRecordToZone(add_columns), DataSourceError);
+}
+
+TEST_F(SQLite3Update, deleteRecord) {
+ zone_id = accessor->startUpdateZone("example.com.", false).second;
+
+ checkRecords(*accessor, zone_id, "foo.bar.example.com.", expected_stored);
+
+ copy(deleted_data, deleted_data + DatabaseAccessor::DEL_PARAM_COUNT,
+ del_params);
+ accessor->deleteRecordInZone(del_params);
+ checkRecords(*accessor, zone_id, "foo.bar.example.com.", empty_stored);
+
+ // Commit the change, and confirm the deleted data still isn't there.
+ accessor->commitUpdateZone();
+ checkRecords(*accessor, zone_id, "foo.bar.example.com.", empty_stored);
}
+TEST_F(SQLite3Update, deleteThenRollback) {
+ zone_id = accessor->startUpdateZone("example.com.", false).second;
+
+ copy(deleted_data, deleted_data + DatabaseAccessor::DEL_PARAM_COUNT,
+ del_params);
+ accessor->deleteRecordInZone(del_params);
+ checkRecords(*accessor, zone_id, "foo.bar.example.com.", empty_stored);
+
+ // Rollback the change, and confirm the data still exists.
+ accessor->rollbackUpdateZone();
+ checkRecords(*accessor, zone_id, "foo.bar.example.com.", expected_stored);
+}
+
+TEST_F(SQLite3Update, deleteNonexistent) {
+ zone_id = accessor->startUpdateZone("example.com.", false).second;
+ copy(deleted_data, deleted_data + DatabaseAccessor::DEL_PARAM_COUNT,
+ del_params);
+
+ // Replace the name with a non existent one, then try to delete it.
+ // nothing should happen.
+ del_params[DatabaseAccessor::DEL_NAME] = "no-such-name.example.com.";
+ checkRecords(*accessor, zone_id, "no-such-name.example.com.",
+ empty_stored);
+ accessor->deleteRecordInZone(del_params);
+ checkRecords(*accessor, zone_id, "no-such-name.example.com.",
+ empty_stored);
+
+ // Name exists but the RR type is different. Delete attempt shouldn't
+ // delete only by name.
+ copy(deleted_data, deleted_data + DatabaseAccessor::DEL_PARAM_COUNT,
+ del_params);
+ del_params[DatabaseAccessor::DEL_TYPE] = "AAAA";
+ accessor->deleteRecordInZone(del_params);
+ checkRecords(*accessor, zone_id, "foo.bar.example.com.", expected_stored);
+
+ // Similar to the previous case, but RDATA is different.
+ copy(deleted_data, deleted_data + DatabaseAccessor::DEL_PARAM_COUNT,
+ del_params);
+ del_params[DatabaseAccessor::DEL_RDATA] = "192.0.2.2";
+ accessor->deleteRecordInZone(del_params);
+ checkRecords(*accessor, zone_id, "foo.bar.example.com.", expected_stored);
+}
+
+TEST_F(SQLite3Update, invalidDelete) {
+ // An attempt of delete before an explicit start of transaction
+ EXPECT_THROW(accessor->deleteRecordInZone(del_params), DataSourceError);
+}
} // end anonymous namespace
diff --git a/src/lib/datasrc/tests/testdata/Makefile.am b/src/lib/datasrc/tests/testdata/Makefile.am
new file mode 100644
index 0000000..64ae955
--- /dev/null
+++ b/src/lib/datasrc/tests/testdata/Makefile.am
@@ -0,0 +1,6 @@
+CLEANFILES = *.copied
+BUILT_SOURCES = rwtest.sqlite3.copied
+
+# We use install-sh with the -m option to make sure it's writable
+rwtest.sqlite3.copied: $(srcdir)/rwtest.sqlite3
+ $(top_srcdir)/install-sh -m 644 $(srcdir)/rwtest.sqlite3 $@
diff --git a/src/lib/datasrc/tests/testdata/rwtest.sqlite3 b/src/lib/datasrc/tests/testdata/rwtest.sqlite3
new file mode 100644
index 0000000..ce95a1d
Binary files /dev/null and b/src/lib/datasrc/tests/testdata/rwtest.sqlite3 differ
diff --git a/src/lib/datasrc/zone.h b/src/lib/datasrc/zone.h
index 0dacc5d..bb4f435 100644
--- a/src/lib/datasrc/zone.h
+++ b/src/lib/datasrc/zone.h
@@ -15,46 +15,39 @@
#ifndef __ZONE_H
#define __ZONE_H 1
-#include <datasrc/result.h>
+#include <dns/rrset.h>
#include <dns/rrsetlist.h>
+#include <datasrc/result.h>
+
namespace isc {
namespace datasrc {
-/// \brief The base class for a single authoritative zone
-///
-/// The \c Zone class is an abstract base class for representing
-/// a DNS zone as part of data source.
+/// \brief The base class to search a zone for RRsets
///
-/// At the moment this is provided mainly for making the \c ZoneTable class
-/// and the authoritative query logic testable, and only provides a minimal
-/// set of features.
-/// This is why this class is defined in the same header file, but it may
-/// have to move to a separate header file when we understand what is
-/// necessary for this class for actual operation.
+/// The \c ZoneFinder class is an abstract base class for representing
+/// an object that performs DNS lookups in a specific zone accessible via
+/// a data source. In general, different types of data sources (in-memory,
+/// database-based, etc) define their own derived classes of \c ZoneFinder,
+/// implementing ways to retrieve the required data through the common
+/// interfaces declared in the base class. Each concrete \c ZoneFinder
+/// object is therefore (conceptually) associated with a specific zone
+/// of one specific data source instance.
///
-/// The idea is to provide a specific derived zone class for each data
-/// source, beginning with in memory one. At that point the derived classes
-/// will have more specific features. For example, they will maintain
-/// information about the location of a zone file, whether it's loaded in
-/// memory, etc.
+/// The origin name and the RR class of the associated zone are available
+/// via the \c getOrigin() and \c getClass() methods, respectively.
///
-/// It's not yet clear how the derived zone classes work with various other
-/// data sources when we integrate these components, but one possibility is
-/// something like this:
-/// - If the underlying database such as some variant of SQL doesn't have an
-/// explicit representation of zones (as part of public interface), we can
-/// probably use a "default" zone class that simply encapsulates the
-/// corresponding data source and calls a common "find" like method.
-/// - Some data source may want to specialize it by inheritance as an
-/// optimization. For example, in the current schema design of the sqlite3
-/// data source, its (derived) zone class would contain the information of
-/// the "zone ID".
+/// The most important method of this class is \c find(), which performs
+/// the lookup for a given domain and type. See the description of the
+/// method for details.
///
-/// <b>Note:</b> Unlike some other abstract base classes we don't name the
-/// class beginning with "Abstract". This is because we want to have
-/// commonly used definitions such as \c Result and \c ZoneFinderPtr, and we
-/// want to make them look more intuitive.
+/// \note It's not clear whether we should request that a zone finder form a
+/// "transaction", that is, whether to ensure the finder is not susceptible
+/// to changes made by someone else than the creator of the finder. If we
+/// don't request that, for example, two different lookup results for the
+/// same name and type can be different if other threads or programs make
+/// updates to the zone between the lookups. We should revisit this point
+/// as we gain more experiences.
class ZoneFinder {
public:
/// Result codes of the \c find() method.
@@ -107,7 +100,11 @@ public:
/// performed on these values to express compound options.
enum FindOptions {
FIND_DEFAULT = 0, ///< The default options
- FIND_GLUE_OK = 1 ///< Allow search under a zone cut
+ FIND_GLUE_OK = 1, ///< Allow search under a zone cut
+ FIND_DNSSEC = 2 ///< Require DNSSEC data in the answer
+ ///< (RRSIG, NSEC, etc.). The implementation
+ ///< is allowed to include it even if it is
+ ///< not set.
};
///
@@ -201,14 +198,244 @@ public:
//@}
};
+/// \brief Operator to combine FindOptions
+///
+/// We would need to manually static-cast the options if we put or
+/// between them, which is undesired with bit-flag options. Therefore
+/// we hide the cast here, which is the simplest solution and it still
+/// provides reasonable level of type safety.
+inline ZoneFinder::FindOptions operator |(ZoneFinder::FindOptions a,
+ ZoneFinder::FindOptions b)
+{
+ return (static_cast<ZoneFinder::FindOptions>(static_cast<unsigned>(a) |
+ static_cast<unsigned>(b)));
+}
+
/// \brief A pointer-like type pointing to a \c ZoneFinder object.
typedef boost::shared_ptr<ZoneFinder> ZoneFinderPtr;
/// \brief A pointer-like type pointing to a \c ZoneFinder object.
typedef boost::shared_ptr<const ZoneFinder> ConstZoneFinderPtr;
-}
-}
+/// The base class to make updates to a single zone.
+///
+/// On construction, each derived class object will start a "transaction"
+/// for making updates to a specific zone (this means a constructor of
+/// a derived class would normally take parameters to identify the zone
+/// to be updated). The underlying realization of a "transaction" will differ
+/// for different derived classes; if it uses a general purpose database
+/// as a backend, it will involve performing some form of "begin transaction"
+/// statement for the database.
+///
+/// Updates (adding or deleting RRs) are made via \c addRRset() and
+/// \c deleteRRset() methods. Until the \c commit() method is called the
+/// changes are local to the updater object. For example, they won't be
+/// visible via a \c ZoneFinder object except the one returned by the
+/// updater's own \c getFinder() method. The \c commit() completes the
+/// transaction and makes the changes visible to others.
+///
+/// This class does not provide an explicit "rollback" interface. If
+/// something wrong or unexpected happens during the updates and the
+/// caller wants to cancel the intermediate updates, the caller should
+/// simply destruct the updater object without calling \c commit().
+/// The destructor is supposed to perform the "rollback" operation,
+/// depending on the internal details of the derived class.
+///
+/// \note This initial implementation provides a quite simple interface of
+/// adding and deleting RRs (see the description of the related methods).
+/// It may be revisited as we gain more experiences.
+class ZoneUpdater {
+protected:
+ /// The default constructor.
+ ///
+ /// This is intentionally defined as protected to ensure that this base
+ /// class is never instantiated directly.
+ ZoneUpdater() {}
+
+public:
+ /// The destructor
+ ///
+ /// Each derived class implementation must ensure that if \c commit()
+ /// has not been performed by the time of the call to it, then it
+ /// "rollbacks" the updates made via the updater so far.
+ virtual ~ZoneUpdater() {}
+
+ /// Return a finder for the zone being updated.
+ ///
+ /// The returned finder provides the functionalities of \c ZoneFinder
+ /// for the zone as updates are made via the updater. That is, before
+ /// making any update, the finder will be able to find all RRsets that
+ /// exist in the zone at the time the updater is created. If RRsets
+ /// are added or deleted via \c addRRset() or \c deleteRRset(),
+ /// this finder will find the added ones or miss the deleted ones
+ /// respectively.
+ ///
+ /// The finder returned by this method is effective only while the updates
+ /// are performed, i.e., from the construction of the corresponding
+ /// updater until \c commit() is performed or the updater is destructed
+ /// without commit. The result of a subsequent call to this method (or
+ /// the use of the result) after that is undefined.
+ ///
+ /// \return A reference to a \c ZoneFinder for the updated zone
+ virtual ZoneFinder& getFinder() = 0;
+
+ /// Add an RRset to a zone via the updater
+ ///
+ /// This may be revisited in a future version, but right now the intended
+ /// behavior of this method is simple: It "naively" adds the specified
+ /// RRset to the zone specified on creation of the updater.
+ /// It performs minimum level of validation on the specified RRset:
+ /// - Whether the RR class is identical to that for the zone to be updated
+ /// - Whether the RRset is not empty, i.e., it has at least one RDATA
+ /// - Whether the RRset is not associated with an RRSIG, i.e.,
+ /// whether \c getRRsig() on the RRset returns a NULL pointer.
+ ///
+ /// and otherwise does not check any oddity. For example, it doesn't
+ /// check whether the owner name of the specified RRset is a subdomain
+ /// of the zone's origin; it doesn't care whether or not there is already
+ /// an RRset of the same name and RR type in the zone, and if there is,
+ /// whether any of the existing RRs have duplicate RDATA with the added
+ /// ones. If these conditions matter the calling application must examine
+ /// the existing data beforehand using the \c ZoneFinder returned by
+ /// \c getFinder().
+ ///
+ /// The validation requirement on the associated RRSIG is temporary.
+ /// If we find it more reasonable and useful to allow adding a pair of
+ /// RRset and its RRSIG RRset as we gain experiences with the interface,
+ /// we may remove this restriction. Until then we explicitly check it
+ /// to prevent accidental misuse.
+ ///
+ /// Conceptually, on successful call to this method, the zone will have
+ /// the specified RRset, and if there is already an RRset of the same
+ /// name and RR type, these two sets will be "merged". "Merged" means
+ /// that a subsequent call to \c ZoneFinder::find() for the name and type
+ /// will result in success and the returned RRset will contain all
+ /// previously existing and newly added RDATAs with the TTL being the
+ /// minimum of the two RRsets. The underlying representation of the
+ /// "merged" RRsets may vary depending on the characteristic of the
+ /// underlying data source. For example, if it uses a general purpose
+ /// database that stores each RR of the same RRset separately, it may
+ /// simply be a larger sets of RRs based on both the existing and added
+ /// RRsets; the TTLs of the RRs may be different within the database, and
+ /// there may even be duplicate RRs in different database rows. As long
+ /// as the RRset returned via \c ZoneFinder::find() conforms to the
+ /// concept of "merge", the actual internal representation is up to the
+ /// implementation.
+ ///
+ /// This method must not be called once commit() is performed. If it
+ /// calls after \c commit() the implementation must throw a
+ /// \c DataSourceError exception.
+ ///
+ /// \todo As noted above we may have to revisit the design details as we
+ /// gain experiences:
+ ///
+ /// - we may want to check (and maybe reject) if there is already a
+ /// duplicate RR (that has the same RDATA).
+ /// - we may want to check (and maybe reject) if there is already an
+ /// RRset of the same name and RR type with different TTL
+ /// - we may even want to check if there is already any RRset of the
+ /// same name and RR type.
+ /// - we may want to add an "options" parameter that can control the
+ /// above points
+ /// - we may want to have this method return a value containing the
+ /// information on whether there's a duplicate, etc.
+ ///
+ /// \exception DataSourceError Called after \c commit(), RRset is invalid
+ /// (see above), internal data source error
+ /// \exception std::bad_alloc Resource allocation failure
+ ///
+ /// \param rrset The RRset to be added
+ virtual void addRRset(const isc::dns::RRset& rrset) = 0;
+
+ /// Delete an RRset from a zone via the updater
+ ///
+ /// Like \c addRRset(), the detailed semantics and behavior of this method
+ /// may have to be revisited in a future version. The following are
+ /// based on the initial implementation decisions.
+ ///
+ /// On successful completion of this method, it will remove from the zone
+ /// the RRs of the specified owner name and RR type that match one of
+ /// the RDATAs of the specified RRset. There are several points to be
+ /// noted:
+ /// - Existing RRs that don't match any of the specified RDATAs will
+ /// remain in the zone.
+ /// - Any RRs of the specified RRset that doesn't exist in the zone will
+ /// simply be ignored; the implementation of this method is not supposed
+ /// to check that condition.
+ /// - The TTL of the RRset is ignored; matching is only performed by
+ /// the owner name, RR type and RDATA
+ ///
+ /// Ignoring the TTL may not look sensible, but it's based on the
+ /// observation that it will result in more intuitive result, especially
+ /// when the underlying data source is a general purpose database.
+ /// See also \c DatabaseAccessor::deleteRecordInZone() on this point.
+ /// It also matches the dynamic update protocol (RFC2136), where TTLs
+ /// are ignored when deleting RRs.
+ ///
+ /// \note Since the TTL is ignored, this method could take the RRset
+ /// to be deleted as a tuple of name, RR type, and a list of RDATAs.
+ /// But in practice, it's quite likely that the caller has the RRset
+ /// in the form of the \c RRset object (e.g., extracted from a dynamic
+ /// update request message), so this interface would rather be more
+ /// convenient. If it turns out not to be true we can change or extend
+ /// the method signature.
+ ///
+ /// This method performs minimum level of validation on the specified
+ /// RRset:
+ /// - Whether the RR class is identical to that for the zone to be updated
+ /// - Whether the RRset is not empty, i.e., it has at least one RDATA
+ /// - Whether the RRset is not associated with an RRSIG, i.e.,
+ /// whether \c getRRsig() on the RRset returns a NULL pointer.
+ ///
+ /// This method must not be called once commit() is performed. If it
+ /// calls after \c commit() the implementation must throw a
+ /// \c DataSourceError exception.
+ ///
+ /// \todo As noted above we may have to revisit the design details as we
+ /// gain experiences:
+ ///
+ /// - we may want to check (and maybe reject) if some or all of the RRs
+ /// for the specified RRset don't exist in the zone
+ /// - we may want to allow an option to "delete everything" for specified
+ /// name and/or specified name + RR type.
+ /// - as mentioned above, we may want to include the TTL in matching the
+ /// deleted RRs
+ /// - we may want to add an "options" parameter that can control the
+ /// above points
+ /// - we may want to have this method return a value containing the
+ /// information on whether there's any RRs that are specified but don't
+ /// exit, the number of actually deleted RRs, etc.
+ ///
+ /// \exception DataSourceError Called after \c commit(), RRset is invalid
+ /// (see above), internal data source error
+ /// \exception std::bad_alloc Resource allocation failure
+ ///
+ /// \param rrset The RRset to be deleted
+ virtual void deleteRRset(const isc::dns::RRset& rrset) = 0;
+
+ /// Commit the updates made in the updater to the zone
+ ///
+ /// This method completes the "transaction" started at the creation
+ /// of the updater. After successful completion of this method, the
+ /// updates will be visible outside the scope of the updater.
+ /// The actual internal behavior will defer for different derived classes.
+ /// For a derived class with a general purpose database as a backend,
+ /// for example, this method would perform a "commit" statement for the
+ /// database.
+ ///
+ /// This operation can only be performed at most once. A duplicate call
+ /// must result in a DatasourceError exception.
+ ///
+ /// \exception DataSourceError Duplicate call of the method,
+ /// internal data source error
+ virtual void commit() = 0;
+};
+
+/// \brief A pointer-like type pointing to a \c ZoneUpdater object.
+typedef boost::shared_ptr<ZoneUpdater> ZoneUpdaterPtr;
+
+} // end of datasrc
+} // end of isc
#endif // __ZONE_H
diff --git a/src/lib/dns/Makefile.am b/src/lib/dns/Makefile.am
index c823dfd..1827b70 100644
--- a/src/lib/dns/Makefile.am
+++ b/src/lib/dns/Makefile.am
@@ -23,6 +23,7 @@ EXTRA_DIST += rdata/generic/cname_5.cc
EXTRA_DIST += rdata/generic/cname_5.h
EXTRA_DIST += rdata/generic/detail/nsec_bitmap.cc
EXTRA_DIST += rdata/generic/detail/nsec_bitmap.h
+EXTRA_DIST += rdata/generic/detail/txt_like.h
EXTRA_DIST += rdata/generic/dname_39.cc
EXTRA_DIST += rdata/generic/dname_39.h
EXTRA_DIST += rdata/generic/dnskey_48.cc
@@ -31,6 +32,8 @@ EXTRA_DIST += rdata/generic/ds_43.cc
EXTRA_DIST += rdata/generic/ds_43.h
EXTRA_DIST += rdata/generic/mx_15.cc
EXTRA_DIST += rdata/generic/mx_15.h
+EXTRA_DIST += rdata/generic/naptr_35.cc
+EXTRA_DIST += rdata/generic/naptr_35.h
EXTRA_DIST += rdata/generic/ns_2.cc
EXTRA_DIST += rdata/generic/ns_2.h
EXTRA_DIST += rdata/generic/nsec3_50.cc
@@ -49,6 +52,8 @@ EXTRA_DIST += rdata/generic/rrsig_46.cc
EXTRA_DIST += rdata/generic/rrsig_46.h
EXTRA_DIST += rdata/generic/soa_6.cc
EXTRA_DIST += rdata/generic/soa_6.h
+EXTRA_DIST += rdata/generic/spf_99.cc
+EXTRA_DIST += rdata/generic/spf_99.h
EXTRA_DIST += rdata/generic/txt_16.cc
EXTRA_DIST += rdata/generic/txt_16.h
EXTRA_DIST += rdata/generic/minfo_14.cc
@@ -61,6 +66,8 @@ EXTRA_DIST += rdata/in_1/a_1.cc
EXTRA_DIST += rdata/in_1/a_1.h
EXTRA_DIST += rdata/in_1/aaaa_28.cc
EXTRA_DIST += rdata/in_1/aaaa_28.h
+EXTRA_DIST += rdata/in_1/dhcid_49.cc
+EXTRA_DIST += rdata/in_1/dhcid_49.h
EXTRA_DIST += rdata/in_1/srv_33.cc
EXTRA_DIST += rdata/in_1/srv_33.h
#EXTRA_DIST += rdata/template.cc
@@ -97,6 +104,7 @@ libdns___la_SOURCES += tsigrecord.h tsigrecord.cc
libdns___la_SOURCES += character_string.h character_string.cc
libdns___la_SOURCES += rdata/generic/detail/nsec_bitmap.h
libdns___la_SOURCES += rdata/generic/detail/nsec_bitmap.cc
+libdns___la_SOURCES += rdata/generic/detail/txt_like.h
libdns___la_CPPFLAGS = $(AM_CPPFLAGS)
# Most applications of libdns++ will only implicitly rely on libcryptolink,
diff --git a/src/lib/dns/python/Makefile.am b/src/lib/dns/python/Makefile.am
index 6c4ef54..4452e40 100644
--- a/src/lib/dns/python/Makefile.am
+++ b/src/lib/dns/python/Makefile.am
@@ -4,40 +4,46 @@ AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
AM_CPPFLAGS += $(BOOST_INCLUDES)
AM_CXXFLAGS = $(B10_CXXFLAGS)
-pyexec_LTLIBRARIES = pydnspp.la
-pydnspp_la_SOURCES = pydnspp.cc pydnspp_common.cc pydnspp_towire.h
-pydnspp_la_SOURCES += name_python.cc name_python.h
-pydnspp_la_SOURCES += messagerenderer_python.cc messagerenderer_python.h
-pydnspp_la_SOURCES += rcode_python.cc rcode_python.h
-pydnspp_la_SOURCES += tsigkey_python.cc tsigkey_python.h
-pydnspp_la_SOURCES += tsigerror_python.cc tsigerror_python.h
-pydnspp_la_SOURCES += tsig_rdata_python.cc tsig_rdata_python.h
-pydnspp_la_SOURCES += tsigrecord_python.cc tsigrecord_python.h
-pydnspp_la_SOURCES += tsig_python.cc tsig_python.h
+lib_LTLIBRARIES = libpydnspp.la
+libpydnspp_la_SOURCES = pydnspp_common.cc pydnspp_common.h pydnspp_towire.h
+libpydnspp_la_SOURCES += name_python.cc name_python.h
+libpydnspp_la_SOURCES += rrset_python.cc rrset_python.h
+libpydnspp_la_SOURCES += rrclass_python.cc rrclass_python.h
+libpydnspp_la_SOURCES += rrtype_python.cc rrtype_python.h
+libpydnspp_la_SOURCES += rrttl_python.cc rrttl_python.h
+libpydnspp_la_SOURCES += rdata_python.cc rdata_python.h
+libpydnspp_la_SOURCES += messagerenderer_python.cc messagerenderer_python.h
+libpydnspp_la_SOURCES += rcode_python.cc rcode_python.h
+libpydnspp_la_SOURCES += opcode_python.cc opcode_python.h
+libpydnspp_la_SOURCES += question_python.cc question_python.h
+libpydnspp_la_SOURCES += tsigkey_python.cc tsigkey_python.h
+libpydnspp_la_SOURCES += tsigerror_python.cc tsigerror_python.h
+libpydnspp_la_SOURCES += tsig_rdata_python.cc tsig_rdata_python.h
+libpydnspp_la_SOURCES += tsigrecord_python.cc tsigrecord_python.h
+libpydnspp_la_SOURCES += tsig_python.cc tsig_python.h
+libpydnspp_la_SOURCES += edns_python.cc edns_python.h
+libpydnspp_la_SOURCES += message_python.cc message_python.h
+
+libpydnspp_la_CPPFLAGS = $(AM_CPPFLAGS) $(PYTHON_INCLUDES)
+libpydnspp_la_CXXFLAGS = $(AM_CXXFLAGS) $(PYTHON_CXXFLAGS)
+libpydnspp_la_LDFLAGS = $(PYTHON_LDFLAGS)
+
+
+pyexec_LTLIBRARIES = pydnspp.la
+pydnspp_la_SOURCES = pydnspp.cc
pydnspp_la_CPPFLAGS = $(AM_CPPFLAGS) $(PYTHON_INCLUDES)
# Note: PYTHON_CXXFLAGS may have some -Wno... workaround, which must be
# placed after -Wextra defined in AM_CXXFLAGS
pydnspp_la_CXXFLAGS = $(AM_CXXFLAGS) $(PYTHON_CXXFLAGS)
pydnspp_la_LDFLAGS = $(PYTHON_LDFLAGS)
-# directly included from source files, so these don't have their own
-# rules
-EXTRA_DIST = pydnspp_common.h
-EXTRA_DIST += edns_python.cc
-EXTRA_DIST += message_python.cc
-EXTRA_DIST += rrclass_python.cc
-EXTRA_DIST += opcode_python.cc
-EXTRA_DIST += rrset_python.cc
-EXTRA_DIST += question_python.cc
-EXTRA_DIST += rrttl_python.cc
-EXTRA_DIST += rdata_python.cc
-EXTRA_DIST += rrtype_python.cc
-EXTRA_DIST += tsigerror_python_inc.cc
+EXTRA_DIST = tsigerror_python_inc.cc
# Python prefers .so, while some OSes (specifically MacOS) use a different
# suffix for dynamic objects. -module is necessary to work this around.
pydnspp_la_LDFLAGS += -module
pydnspp_la_LIBADD = $(top_builddir)/src/lib/dns/libdns++.la
pydnspp_la_LIBADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
+pydnspp_la_LIBADD += libpydnspp.la
pydnspp_la_LIBADD += $(PYTHON_LIB)
diff --git a/src/lib/dns/python/edns_python.cc b/src/lib/dns/python/edns_python.cc
index 83c3bfa..8f0f1a4 100644
--- a/src/lib/dns/python/edns_python.cc
+++ b/src/lib/dns/python/edns_python.cc
@@ -12,38 +12,38 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
+#include <Python.h>
+
#include <cassert>
#include <dns/edns.h>
+#include <dns/exceptions.h>
+#include <dns/messagerenderer.h>
+#include <util/python/pycppwrapper_util.h>
+
+#include "edns_python.h"
+#include "name_python.h"
+#include "rrclass_python.h"
+#include "rrtype_python.h"
+#include "rrttl_python.h"
+#include "rdata_python.h"
+#include "messagerenderer_python.h"
+#include "pydnspp_common.h"
using namespace isc::dns;
-using namespace isc::util;
using namespace isc::dns::rdata;
-
-//
-// Definition of the classes
-//
-
-// For each class, we need a struct, a helper functions (init, destroy,
-// and static wrappers around the methods we export), a list of methods,
-// and a type description
+using namespace isc::dns::python;
+using namespace isc::util;
+using namespace isc::util::python;
namespace {
-//
-// EDNS
-//
-
-// The s_* Class simply covers one instantiation of the object
class s_EDNS : public PyObject {
public:
- EDNS* edns;
+ EDNS* cppobj;
};
-//
-// We declare the functions here, the definitions are below
-// the type definition of the object, since both can use the other
-//
+typedef CPPPyObjectContainer<s_EDNS, EDNS> EDNSContainer;
// General creation and destruction
int EDNS_init(s_EDNS* self, PyObject* args);
@@ -103,60 +103,6 @@ PyMethodDef EDNS_methods[] = {
{ NULL, NULL, 0, NULL }
};
-// This defines the complete type for reflection in python and
-// parsing of PyObject* to s_EDNS
-// Most of the functions are not actually implemented and NULL here.
-PyTypeObject edns_type = {
- PyVarObject_HEAD_INIT(NULL, 0)
- "pydnspp.EDNS",
- sizeof(s_EDNS), // tp_basicsize
- 0, // tp_itemsize
- (destructor)EDNS_destroy, // tp_dealloc
- NULL, // tp_print
- NULL, // tp_getattr
- NULL, // tp_setattr
- NULL, // tp_reserved
- NULL, // tp_repr
- NULL, // tp_as_number
- NULL, // tp_as_sequence
- NULL, // tp_as_mapping
- NULL, // tp_hash
- NULL, // tp_call
- EDNS_str, // tp_str
- NULL, // tp_getattro
- NULL, // tp_setattro
- NULL, // tp_as_buffer
- Py_TPFLAGS_DEFAULT, // tp_flags
- "The EDNS class encapsulates DNS extensions "
- "provided by the EDNSx protocol.",
- NULL, // tp_traverse
- NULL, // tp_clear
- NULL, // tp_richcompare
- 0, // tp_weaklistoffset
- NULL, // tp_iter
- NULL, // tp_iternext
- EDNS_methods, // tp_methods
- NULL, // tp_members
- NULL, // tp_getset
- NULL, // tp_base
- NULL, // tp_dict
- NULL, // tp_descr_get
- NULL, // tp_descr_set
- 0, // tp_dictoffset
- (initproc)EDNS_init, // tp_init
- NULL, // tp_alloc
- PyType_GenericNew, // tp_new
- NULL, // tp_free
- NULL, // tp_is_gc
- NULL, // tp_bases
- NULL, // tp_mro
- NULL, // tp_cache
- NULL, // tp_subclasses
- NULL, // tp_weaklist
- NULL, // tp_del
- 0 // tp_version_tag
-};
-
EDNS*
createFromRR(const Name& name, const RRClass& rrclass, const RRType& rrtype,
const RRTTL& rrttl, const Rdata& rdata, uint8_t& extended_rcode)
@@ -179,15 +125,15 @@ createFromRR(const Name& name, const RRClass& rrclass, const RRType& rrtype,
int
EDNS_init(s_EDNS* self, PyObject* args) {
uint8_t version = EDNS::SUPPORTED_VERSION;
- const s_Name* name;
- const s_RRClass* rrclass;
- const s_RRType* rrtype;
- const s_RRTTL* rrttl;
- const s_Rdata* rdata;
+ const PyObject* name;
+ const PyObject* rrclass;
+ const PyObject* rrtype;
+ const PyObject* rrttl;
+ const PyObject* rdata;
if (PyArg_ParseTuple(args, "|b", &version)) {
try {
- self->edns = new EDNS(version);
+ self->cppobj = new EDNS(version);
} catch (const isc::InvalidParameter& ex) {
PyErr_SetString(po_InvalidParameter, ex.what());
return (-1);
@@ -203,10 +149,12 @@ EDNS_init(s_EDNS* self, PyObject* args) {
// in this context so that we can share the try-catch logic with
// EDNS_createFromRR() (see below).
uint8_t extended_rcode;
- self->edns = createFromRR(*name->cppobj, *rrclass->rrclass,
- *rrtype->rrtype, *rrttl->rrttl,
- *rdata->rdata, extended_rcode);
- return (self->edns != NULL ? 0 : -1);
+ self->cppobj = createFromRR(PyName_ToName(name),
+ PyRRClass_ToRRClass(rrclass),
+ PyRRType_ToRRType(rrtype),
+ PyRRTTL_ToRRTTL(rrttl),
+ PyRdata_ToRdata(rdata), extended_rcode);
+ return (self->cppobj != NULL ? 0 : -1);
}
PyErr_Clear();
@@ -217,19 +165,19 @@ EDNS_init(s_EDNS* self, PyObject* args) {
void
EDNS_destroy(s_EDNS* const self) {
- delete self->edns;
- self->edns = NULL;
+ delete self->cppobj;
+ self->cppobj = NULL;
Py_TYPE(self)->tp_free(self);
}
PyObject*
EDNS_toText(const s_EDNS* const self) {
// Py_BuildValue makes python objects from native data
- return (Py_BuildValue("s", self->edns->toText().c_str()));
+ return (Py_BuildValue("s", self->cppobj->toText().c_str()));
}
PyObject*
-EDNS_str(PyObject* const self) {
+EDNS_str(PyObject* self) {
// Simply call the to_text method we already defined
return (PyObject_CallMethod(self,
const_cast<char*>("to_text"),
@@ -240,14 +188,14 @@ PyObject*
EDNS_toWire(const s_EDNS* const self, PyObject* args) {
PyObject* bytes;
uint8_t extended_rcode;
- s_MessageRenderer* renderer;
+ PyObject* renderer;
if (PyArg_ParseTuple(args, "Ob", &bytes, &extended_rcode) &&
PySequence_Check(bytes)) {
PyObject* bytes_o = bytes;
-
+
OutputBuffer buffer(0);
- self->edns->toWire(buffer, extended_rcode);
+ self->cppobj->toWire(buffer, extended_rcode);
PyObject* rd_bytes = PyBytes_FromStringAndSize(
static_cast<const char*>(buffer.getData()), buffer.getLength());
PyObject* result = PySequence_InPlaceConcat(bytes_o, rd_bytes);
@@ -257,8 +205,8 @@ EDNS_toWire(const s_EDNS* const self, PyObject* args) {
return (result);
} else if (PyArg_ParseTuple(args, "O!b", &messagerenderer_type,
&renderer, &extended_rcode)) {
- const unsigned int n = self->edns->toWire(*renderer->messagerenderer,
- extended_rcode);
+ const unsigned int n = self->cppobj->toWire(
+ PyMessageRenderer_ToMessageRenderer(renderer), extended_rcode);
return (Py_BuildValue("I", n));
}
@@ -269,12 +217,12 @@ EDNS_toWire(const s_EDNS* const self, PyObject* args) {
PyObject*
EDNS_getVersion(const s_EDNS* const self) {
- return (Py_BuildValue("B", self->edns->getVersion()));
+ return (Py_BuildValue("B", self->cppobj->getVersion()));
}
PyObject*
EDNS_getDNSSECAwareness(const s_EDNS* const self) {
- if (self->edns->getDNSSECAwareness()) {
+ if (self->cppobj->getDNSSECAwareness()) {
Py_RETURN_TRUE;
} else {
Py_RETURN_FALSE;
@@ -287,13 +235,13 @@ EDNS_setDNSSECAwareness(s_EDNS* self, PyObject* args) {
if (!PyArg_ParseTuple(args, "O!", &PyBool_Type, &b)) {
return (NULL);
}
- self->edns->setDNSSECAwareness(b == Py_True);
+ self->cppobj->setDNSSECAwareness(b == Py_True);
Py_RETURN_NONE;
}
PyObject*
EDNS_getUDPSize(const s_EDNS* const self) {
- return (Py_BuildValue("I", self->edns->getUDPSize()));
+ return (Py_BuildValue("I", self->cppobj->getUDPSize()));
}
PyObject*
@@ -310,17 +258,17 @@ EDNS_setUDPSize(s_EDNS* self, PyObject* args) {
"UDP size is not an unsigned 16-bit integer");
return (NULL);
}
- self->edns->setUDPSize(size);
+ self->cppobj->setUDPSize(size);
Py_RETURN_NONE;
}
PyObject*
EDNS_createFromRR(const s_EDNS* null_self, PyObject* args) {
- const s_Name* name;
- const s_RRClass* rrclass;
- const s_RRType* rrtype;
- const s_RRTTL* rrttl;
- const s_Rdata* rdata;
+ const PyObject* name;
+ const PyObject* rrclass;
+ const PyObject* rrtype;
+ const PyObject* rrttl;
+ const PyObject* rdata;
s_EDNS* edns_obj = NULL;
assert(null_self == NULL);
@@ -334,14 +282,17 @@ EDNS_createFromRR(const s_EDNS* null_self, PyObject* args) {
return (NULL);
}
- edns_obj->edns = createFromRR(*name->cppobj, *rrclass->rrclass,
- *rrtype->rrtype, *rrttl->rrttl,
- *rdata->rdata, extended_rcode);
- if (edns_obj->edns != NULL) {
+ edns_obj->cppobj = createFromRR(PyName_ToName(name),
+ PyRRClass_ToRRClass(rrclass),
+ PyRRType_ToRRType(rrtype),
+ PyRRTTL_ToRRTTL(rrttl),
+ PyRdata_ToRdata(rdata),
+ extended_rcode);
+ if (edns_obj->cppobj != NULL) {
PyObject* extrcode_obj = Py_BuildValue("B", extended_rcode);
return (Py_BuildValue("OO", edns_obj, extrcode_obj));
}
-
+
Py_DECREF(edns_obj);
return (NULL);
}
@@ -353,23 +304,90 @@ EDNS_createFromRR(const s_EDNS* null_self, PyObject* args) {
}
} // end of anonymous namespace
-// end of EDNS
-// Module Initialization, all statics are initialized here
+namespace isc {
+namespace dns {
+namespace python {
+
+// This defines the complete type for reflection in python and
+// parsing of PyObject* to s_EDNS
+// Most of the functions are not actually implemented and NULL here.
+PyTypeObject edns_type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "pydnspp.EDNS",
+ sizeof(s_EDNS), // tp_basicsize
+ 0, // tp_itemsize
+ (destructor)EDNS_destroy, // tp_dealloc
+ NULL, // tp_print
+ NULL, // tp_getattr
+ NULL, // tp_setattr
+ NULL, // tp_reserved
+ NULL, // tp_repr
+ NULL, // tp_as_number
+ NULL, // tp_as_sequence
+ NULL, // tp_as_mapping
+ NULL, // tp_hash
+ NULL, // tp_call
+ EDNS_str, // tp_str
+ NULL, // tp_getattro
+ NULL, // tp_setattro
+ NULL, // tp_as_buffer
+ Py_TPFLAGS_DEFAULT, // tp_flags
+ "The EDNS class encapsulates DNS extensions "
+ "provided by the EDNSx protocol.",
+ NULL, // tp_traverse
+ NULL, // tp_clear
+ NULL, // tp_richcompare
+ 0, // tp_weaklistoffset
+ NULL, // tp_iter
+ NULL, // tp_iternext
+ EDNS_methods, // tp_methods
+ NULL, // tp_members
+ NULL, // tp_getset
+ NULL, // tp_base
+ NULL, // tp_dict
+ NULL, // tp_descr_get
+ NULL, // tp_descr_set
+ 0, // tp_dictoffset
+ (initproc)EDNS_init, // tp_init
+ NULL, // tp_alloc
+ PyType_GenericNew, // tp_new
+ NULL, // tp_free
+ NULL, // tp_is_gc
+ NULL, // tp_bases
+ NULL, // tp_mro
+ NULL, // tp_cache
+ NULL, // tp_subclasses
+ NULL, // tp_weaklist
+ NULL, // tp_del
+ 0 // tp_version_tag
+};
+
+PyObject*
+createEDNSObject(const EDNS& source) {
+ EDNSContainer container(PyObject_New(s_EDNS, &edns_type));
+ container.set(new EDNS(source));
+ return (container.release());
+}
+
bool
-initModulePart_EDNS(PyObject* mod) {
- // We initialize the static description object with PyType_Ready(),
- // then add it to the module. This is not just a check! (leaving
- // this out results in segmentation faults)
- if (PyType_Ready(&edns_type) < 0) {
- return (false);
+PyEDNS_Check(PyObject* obj) {
+ if (obj == NULL) {
+ isc_throw(PyCPPWrapperException, "obj argument NULL in typecheck");
}
- Py_INCREF(&edns_type);
- void* p = &edns_type;
- PyModule_AddObject(mod, "EDNS", static_cast<PyObject*>(p));
-
- addClassVariable(edns_type, "SUPPORTED_VERSION",
- Py_BuildValue("B", EDNS::SUPPORTED_VERSION));
+ return (PyObject_TypeCheck(obj, &edns_type));
+}
- return (true);
+const EDNS&
+PyEDNS_ToEDNS(const PyObject* edns_obj) {
+ if (edns_obj == NULL) {
+ isc_throw(PyCPPWrapperException,
+ "obj argument NULL in EDNS PyObject conversion");
+ }
+ const s_EDNS* edns = static_cast<const s_EDNS*>(edns_obj);
+ return (*edns->cppobj);
}
+
+} // end namespace python
+} // end namespace dns
+} // end namespace isc
diff --git a/src/lib/dns/python/edns_python.h b/src/lib/dns/python/edns_python.h
new file mode 100644
index 0000000..30d92ab
--- /dev/null
+++ b/src/lib/dns/python/edns_python.h
@@ -0,0 +1,64 @@
+// 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.
+
+#ifndef __PYTHON_EDNS_H
+#define __PYTHON_EDNS_H 1
+
+#include <Python.h>
+
+namespace isc {
+namespace dns {
+class EDNS;
+
+namespace python {
+
+extern PyTypeObject edns_type;
+
+/// This is a simple shortcut to create a python EDNS object (in the
+/// form of a pointer to PyObject) with minimal exception safety.
+/// On success, it returns a valid pointer to PyObject with a reference
+/// counter of 1; if something goes wrong it throws an exception (it never
+/// returns a NULL pointer).
+/// This function is expected to be called within a try block
+/// followed by necessary setup for python exception.
+PyObject* createEDNSObject(const EDNS& source);
+
+/// \brief Checks if the given python object is a EDNS object
+///
+/// \exception PyCPPWrapperException if obj is NULL
+///
+/// \param obj The object to check the type of
+/// \return true if the object is of type EDNS, false otherwise
+bool PyEDNS_Check(PyObject* obj);
+
+/// \brief Returns a reference to the EDNS object contained within the given
+/// Python object.
+///
+/// \note The given object MUST be of type EDNS; this can be checked with
+/// either the right call to ParseTuple("O!"), or with PyEDNS_Check()
+///
+/// \note This is not a copy; if the EDNS is needed when the PyObject
+/// may be destroyed, the caller must copy it itself.
+///
+/// \param edns_obj The edns object to convert
+const EDNS& PyEDNS_ToEDNS(const PyObject* edns_obj);
+
+} // namespace python
+} // namespace dns
+} // namespace isc
+#endif // __PYTHON_EDNS_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/python/message_python.cc b/src/lib/dns/python/message_python.cc
index 00596f8..b40ab45 100644
--- a/src/lib/dns/python/message_python.cc
+++ b/src/lib/dns/python/message_python.cc
@@ -12,49 +12,39 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
+#define PY_SSIZE_T_CLEAN
+#include <Python.h>
+
#include <exceptions/exceptions.h>
#include <dns/message.h>
#include <dns/rcode.h>
#include <dns/tsig.h>
-
+#include <dns/exceptions.h>
+#include <dns/messagerenderer.h>
+
+#include "name_python.h"
+#include "question_python.h"
+#include "edns_python.h"
+#include "rcode_python.h"
+#include "opcode_python.h"
+#include "rrset_python.h"
+#include "message_python.h"
+#include "messagerenderer_python.h"
+#include "tsig_python.h"
+#include "tsigrecord_python.h"
+#include "pydnspp_common.h"
+
+using namespace std;
using namespace isc::dns;
+using namespace isc::dns::python;
using namespace isc::util;
namespace {
-//
-// Declaration of the custom exceptions
-// Initialization and addition of these go in the initModulePart
-// function at the end of this file
-//
-PyObject* po_MessageTooShort;
-PyObject* po_InvalidMessageSection;
-PyObject* po_InvalidMessageOperation;
-PyObject* po_InvalidMessageUDPSize;
-
-//
-// Definition of the classes
-//
-
-// For each class, we need a struct, a helper functions (init, destroy,
-// and static wrappers around the methods we export), a list of methods,
-// and a type description
-
-//
-// Message
-//
-
-// The s_* Class simply coverst one instantiation of the object
class s_Message : public PyObject {
public:
- Message* message;
+ isc::dns::Message* cppobj;
};
-//
-// We declare the functions here, the definitions are below
-// the type definition of the object, since both can use the other
-//
-
-// General creation and destruction
int Message_init(s_Message* self, PyObject* args);
void Message_destroy(s_Message* self);
@@ -178,59 +168,6 @@ PyMethodDef Message_methods[] = {
{ NULL, NULL, 0, NULL }
};
-// This defines the complete type for reflection in python and
-// parsing of PyObject* to s_Message
-// Most of the functions are not actually implemented and NULL here.
-PyTypeObject message_type = {
- PyVarObject_HEAD_INIT(NULL, 0)
- "pydnspp.Message",
- sizeof(s_Message), // tp_basicsize
- 0, // tp_itemsize
- (destructor)Message_destroy, // tp_dealloc
- NULL, // tp_print
- NULL, // tp_getattr
- NULL, // tp_setattr
- NULL, // tp_reserved
- NULL, // tp_repr
- NULL, // tp_as_number
- NULL, // tp_as_sequence
- NULL, // tp_as_mapping
- NULL, // tp_hash
- NULL, // tp_call
- Message_str, // tp_str
- NULL, // tp_getattro
- NULL, // tp_setattro
- NULL, // tp_as_buffer
- Py_TPFLAGS_DEFAULT, // tp_flags
- "The Message class encapsulates a standard DNS message.",
- NULL, // tp_traverse
- NULL, // tp_clear
- NULL, // tp_richcompare
- 0, // tp_weaklistoffset
- NULL, // tp_iter
- NULL, // tp_iternext
- Message_methods, // tp_methods
- NULL, // tp_members
- NULL, // tp_getset
- NULL, // tp_base
- NULL, // tp_dict
- NULL, // tp_descr_get
- NULL, // tp_descr_set
- 0, // tp_dictoffset
- (initproc)Message_init, // tp_init
- NULL, // tp_alloc
- PyType_GenericNew, // tp_new
- NULL, // tp_free
- NULL, // tp_is_gc
- NULL, // tp_bases
- NULL, // tp_mro
- NULL, // tp_cache
- NULL, // tp_subclasses
- NULL, // tp_weaklist
- NULL, // tp_del
- 0 // tp_version_tag
-};
-
int
Message_init(s_Message* self, PyObject* args) {
int i;
@@ -238,10 +175,10 @@ Message_init(s_Message* self, PyObject* args) {
if (PyArg_ParseTuple(args, "i", &i)) {
PyErr_Clear();
if (i == Message::PARSE) {
- self->message = new Message(Message::PARSE);
+ self->cppobj = new Message(Message::PARSE);
return (0);
} else if (i == Message::RENDER) {
- self->message = new Message(Message::RENDER);
+ self->cppobj = new Message(Message::RENDER);
return (0);
} else {
PyErr_SetString(PyExc_TypeError, "Message mode must be Message.PARSE or Message.RENDER");
@@ -256,8 +193,8 @@ Message_init(s_Message* self, PyObject* args) {
void
Message_destroy(s_Message* self) {
- delete self->message;
- self->message = NULL;
+ delete self->cppobj;
+ self->cppobj = NULL;
Py_TYPE(self)->tp_free(self);
}
@@ -271,7 +208,7 @@ Message_getHeaderFlag(s_Message* self, PyObject* args) {
return (NULL);
}
- if (self->message->getHeaderFlag(
+ if (self->cppobj->getHeaderFlag(
static_cast<Message::HeaderFlag>(messageflag))) {
Py_RETURN_TRUE;
} else {
@@ -296,7 +233,7 @@ Message_setHeaderFlag(s_Message* self, PyObject* args) {
}
try {
- self->message->setHeaderFlag(
+ self->cppobj->setHeaderFlag(
static_cast<Message::HeaderFlag>(messageflag), on == Py_True);
Py_RETURN_NONE;
} catch (const InvalidMessageOperation& imo) {
@@ -312,7 +249,7 @@ Message_setHeaderFlag(s_Message* self, PyObject* args) {
PyObject*
Message_getQid(s_Message* self) {
- return (Py_BuildValue("I", self->message->getQid()));
+ return (Py_BuildValue("I", self->cppobj->getQid()));
}
PyObject*
@@ -331,7 +268,7 @@ Message_setQid(s_Message* self, PyObject* args) {
}
try {
- self->message->setQid(id);
+ self->cppobj->setQid(id);
Py_RETURN_NONE;
} catch (const InvalidMessageOperation& imo) {
PyErr_SetString(po_InvalidMessageOperation, imo.what());
@@ -341,35 +278,25 @@ Message_setQid(s_Message* self, PyObject* args) {
PyObject*
Message_getRcode(s_Message* self) {
- s_Rcode* rcode;
-
- rcode = static_cast<s_Rcode*>(rcode_type.tp_alloc(&rcode_type, 0));
- if (rcode != NULL) {
- rcode->cppobj = NULL;
- try {
- rcode->cppobj = new Rcode(self->message->getRcode());
- } catch (const InvalidMessageOperation& imo) {
- PyErr_SetString(po_InvalidMessageOperation, imo.what());
- } catch (...) {
- PyErr_SetString(po_IscException, "Unexpected exception");
- }
- if (rcode->cppobj == NULL) {
- Py_DECREF(rcode);
- return (NULL);
- }
+ try {
+ return (createRcodeObject(self->cppobj->getRcode()));
+ } catch (const InvalidMessageOperation& imo) {
+ PyErr_SetString(po_InvalidMessageOperation, imo.what());
+ return (NULL);
+ } catch (...) {
+ PyErr_SetString(po_IscException, "Unexpected exception");
+ return (NULL);
}
-
- return (rcode);
}
PyObject*
Message_setRcode(s_Message* self, PyObject* args) {
- s_Rcode* rcode;
+ PyObject* rcode;
if (!PyArg_ParseTuple(args, "O!", &rcode_type, &rcode)) {
return (NULL);
}
try {
- self->message->setRcode(*rcode->cppobj);
+ self->cppobj->setRcode(PyRcode_ToRcode(rcode));
Py_RETURN_NONE;
} catch (const InvalidMessageOperation& imo) {
PyErr_SetString(po_InvalidMessageOperation, imo.what());
@@ -379,35 +306,31 @@ Message_setRcode(s_Message* self, PyObject* args) {
PyObject*
Message_getOpcode(s_Message* self) {
- s_Opcode* opcode;
-
- opcode = static_cast<s_Opcode*>(opcode_type.tp_alloc(&opcode_type, 0));
- if (opcode != NULL) {
- opcode->opcode = NULL;
- try {
- opcode->opcode = new Opcode(self->message->getOpcode());
- } catch (const InvalidMessageOperation& imo) {
- PyErr_SetString(po_InvalidMessageOperation, imo.what());
- } catch (...) {
- PyErr_SetString(po_IscException, "Unexpected exception");
- }
- if (opcode->opcode == NULL) {
- Py_DECREF(opcode);
- return (NULL);
- }
+ try {
+ return (createOpcodeObject(self->cppobj->getOpcode()));
+ } catch (const InvalidMessageOperation& imo) {
+ PyErr_SetString(po_InvalidMessageOperation, imo.what());
+ return (NULL);
+ } catch (const exception& ex) {
+ const string ex_what =
+ "Failed to get message opcode: " + string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ return (NULL);
+ } catch (...) {
+ PyErr_SetString(po_IscException,
+ "Unexpected exception getting opcode from message");
+ return (NULL);
}
-
- return (opcode);
}
PyObject*
Message_setOpcode(s_Message* self, PyObject* args) {
- s_Opcode* opcode;
+ PyObject* opcode;
if (!PyArg_ParseTuple(args, "O!", &opcode_type, &opcode)) {
return (NULL);
}
try {
- self->message->setOpcode(*opcode->opcode);
+ self->cppobj->setOpcode(PyOpcode_ToOpcode(opcode));
Py_RETURN_NONE;
} catch (const InvalidMessageOperation& imo) {
PyErr_SetString(po_InvalidMessageOperation, imo.what());
@@ -417,32 +340,31 @@ Message_setOpcode(s_Message* self, PyObject* args) {
PyObject*
Message_getEDNS(s_Message* self) {
- s_EDNS* edns;
- EDNS* edns_body;
- ConstEDNSPtr src = self->message->getEDNS();
-
+ ConstEDNSPtr src = self->cppobj->getEDNS();
if (!src) {
Py_RETURN_NONE;
}
- if ((edns_body = new(nothrow) EDNS(*src)) == NULL) {
- return (PyErr_NoMemory());
- }
- edns = static_cast<s_EDNS*>(opcode_type.tp_alloc(&edns_type, 0));
- if (edns != NULL) {
- edns->edns = edns_body;
+ try {
+ return (createEDNSObject(*src));
+ } catch (const exception& ex) {
+ const string ex_what =
+ "Failed to get EDNS from message: " + string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError,
+ "Unexpected failure getting EDNS from message");
}
-
- return (edns);
+ return (NULL);
}
PyObject*
Message_setEDNS(s_Message* self, PyObject* args) {
- s_EDNS* edns;
+ PyObject* edns;
if (!PyArg_ParseTuple(args, "O!", &edns_type, &edns)) {
return (NULL);
}
try {
- self->message->setEDNS(EDNSPtr(new EDNS(*edns->edns)));
+ self->cppobj->setEDNS(EDNSPtr(new EDNS(PyEDNS_ToEDNS(edns))));
Py_RETURN_NONE;
} catch (const InvalidMessageOperation& imo) {
PyErr_SetString(po_InvalidMessageOperation, imo.what());
@@ -453,7 +375,7 @@ Message_setEDNS(s_Message* self, PyObject* args) {
PyObject*
Message_getTSIGRecord(s_Message* self) {
try {
- const TSIGRecord* tsig_record = self->message->getTSIGRecord();
+ const TSIGRecord* tsig_record = self->cppobj->getTSIGRecord();
if (tsig_record == NULL) {
Py_RETURN_NONE;
@@ -483,7 +405,7 @@ Message_getRRCount(s_Message* self, PyObject* args) {
return (NULL);
}
try {
- return (Py_BuildValue("I", self->message->getRRCount(
+ return (Py_BuildValue("I", self->cppobj->getRRCount(
static_cast<Message::Section>(section))));
} catch (const isc::OutOfRange& ex) {
PyErr_SetString(PyExc_OverflowError, ex.what());
@@ -496,8 +418,8 @@ PyObject*
Message_getQuestion(s_Message* self) {
QuestionIterator qi, qi_end;
try {
- qi = self->message->beginQuestion();
- qi_end = self->message->endQuestion();
+ qi = self->cppobj->beginQuestion();
+ qi_end = self->cppobj->endQuestion();
} catch (const InvalidMessageSection& ex) {
PyErr_SetString(po_InvalidMessageSection, ex.what());
return (NULL);
@@ -512,23 +434,25 @@ Message_getQuestion(s_Message* self) {
return (NULL);
}
- for (; qi != qi_end; ++qi) {
- s_Question *question = static_cast<s_Question*>(
- question_type.tp_alloc(&question_type, 0));
- if (question == NULL) {
- Py_DECREF(question);
- Py_DECREF(list);
- return (NULL);
- }
- question->question = *qi;
- if (PyList_Append(list, question) == -1) {
- Py_DECREF(question);
- Py_DECREF(list);
- return (NULL);
+ try {
+ for (; qi != qi_end; ++qi) {
+ if (PyList_Append(list, createQuestionObject(**qi)) == -1) {
+ Py_DECREF(list);
+ return (NULL);
+ }
}
- Py_DECREF(question);
+ return (list);
+ } catch (const exception& ex) {
+ const string ex_what =
+ "Unexpected failure getting Question section: " +
+ string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError,
+ "Unexpected failure getting Question section");
}
- return (list);
+ Py_DECREF(list);
+ return (NULL);
}
PyObject*
@@ -542,9 +466,9 @@ Message_getSection(s_Message* self, PyObject* args) {
}
RRsetIterator rrsi, rrsi_end;
try {
- rrsi = self->message->beginSection(
+ rrsi = self->cppobj->beginSection(
static_cast<Message::Section>(section));
- rrsi_end = self->message->endSection(
+ rrsi_end = self->cppobj->endSection(
static_cast<Message::Section>(section));
} catch (const isc::OutOfRange& ex) {
PyErr_SetString(PyExc_OverflowError, ex.what());
@@ -562,25 +486,25 @@ Message_getSection(s_Message* self, PyObject* args) {
if (list == NULL) {
return (NULL);
}
- for (; rrsi != rrsi_end; ++rrsi) {
- s_RRset *rrset = static_cast<s_RRset*>(
- rrset_type.tp_alloc(&rrset_type, 0));
- if (rrset == NULL) {
- Py_DECREF(rrset);
- Py_DECREF(list);
- return (NULL);
- }
- rrset->rrset = *rrsi;
- if (PyList_Append(list, rrset) == -1) {
- Py_DECREF(rrset);
- Py_DECREF(list);
- return (NULL);
+ try {
+ for (; rrsi != rrsi_end; ++rrsi) {
+ if (PyList_Append(list, createRRsetObject(**rrsi)) == -1) {
+ Py_DECREF(list);
+ return (NULL);
+ }
}
- // PyList_Append increases refcount, so we remove ours since
- // we don't need it anymore
- Py_DECREF(rrset);
+ return (list);
+ } catch (const exception& ex) {
+ const string ex_what =
+ "Unexpected failure creating Question object: " +
+ string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError,
+ "Unexpected failure creating Question object");
}
- return (list);
+ Py_DECREF(list);
+ return (NULL);
}
//static PyObject* Message_beginQuestion(s_Message* self, PyObject* args);
@@ -590,14 +514,14 @@ Message_getSection(s_Message* self, PyObject* args) {
//static PyObject* Message_addQuestion(s_Message* self, PyObject* args);
PyObject*
Message_addQuestion(s_Message* self, PyObject* args) {
- s_Question *question;
+ PyObject* question;
if (!PyArg_ParseTuple(args, "O!", &question_type, &question)) {
return (NULL);
}
- self->message->addQuestion(question->question);
-
+ self->cppobj->addQuestion(PyQuestion_ToQuestion(question));
+
Py_RETURN_NONE;
}
@@ -605,15 +529,15 @@ PyObject*
Message_addRRset(s_Message* self, PyObject* args) {
PyObject *sign = Py_False;
int section;
- s_RRset* rrset;
+ PyObject* rrset;
if (!PyArg_ParseTuple(args, "iO!|O!", §ion, &rrset_type, &rrset,
&PyBool_Type, &sign)) {
return (NULL);
}
try {
- self->message->addRRset(static_cast<Message::Section>(section),
- rrset->rrset, sign == Py_True);
+ self->cppobj->addRRset(static_cast<Message::Section>(section),
+ PyRRset_ToRRsetPtr(rrset), sign == Py_True);
Py_RETURN_NONE;
} catch (const InvalidMessageOperation& imo) {
PyErr_SetString(po_InvalidMessageOperation, imo.what());
@@ -634,10 +558,10 @@ Message_clear(s_Message* self, PyObject* args) {
if (PyArg_ParseTuple(args, "i", &i)) {
PyErr_Clear();
if (i == Message::PARSE) {
- self->message->clear(Message::PARSE);
+ self->cppobj->clear(Message::PARSE);
Py_RETURN_NONE;
} else if (i == Message::RENDER) {
- self->message->clear(Message::RENDER);
+ self->cppobj->clear(Message::RENDER);
Py_RETURN_NONE;
} else {
PyErr_SetString(PyExc_TypeError,
@@ -651,7 +575,7 @@ Message_clear(s_Message* self, PyObject* args) {
PyObject*
Message_makeResponse(s_Message* self) {
- self->message->makeResponse();
+ self->cppobj->makeResponse();
Py_RETURN_NONE;
}
@@ -659,7 +583,7 @@ PyObject*
Message_toText(s_Message* self) {
// Py_BuildValue makes python objects from native data
try {
- return (Py_BuildValue("s", self->message->toText().c_str()));
+ return (Py_BuildValue("s", self->cppobj->toText().c_str()));
} catch (const InvalidMessageOperation& imo) {
PyErr_Clear();
PyErr_SetString(po_InvalidMessageOperation, imo.what());
@@ -680,16 +604,17 @@ Message_str(PyObject* self) {
PyObject*
Message_toWire(s_Message* self, PyObject* args) {
- s_MessageRenderer* mr;
- s_TSIGContext* tsig_ctx = NULL;
-
+ PyObject* mr;
+ PyObject* tsig_ctx = NULL;
+
if (PyArg_ParseTuple(args, "O!|O!", &messagerenderer_type, &mr,
&tsigcontext_type, &tsig_ctx)) {
try {
if (tsig_ctx == NULL) {
- self->message->toWire(*mr->messagerenderer);
+ self->cppobj->toWire(PyMessageRenderer_ToMessageRenderer(mr));
} else {
- self->message->toWire(*mr->messagerenderer, *tsig_ctx->cppobj);
+ self->cppobj->toWire(PyMessageRenderer_ToMessageRenderer(mr),
+ PyTSIGContext_ToTSIGContext(tsig_ctx));
}
// If we return NULL it is seen as an error, so use this for
// None returns
@@ -727,10 +652,10 @@ Message_fromWire(s_Message* self, PyObject* args) {
if (!PyArg_ParseTuple(args, "y#", &b, &len)) {
return (NULL);
}
-
+
InputBuffer inbuf(b, len);
try {
- self->message->fromWire(inbuf);
+ self->cppobj->fromWire(inbuf);
Py_RETURN_NONE;
} catch (const InvalidMessageOperation& imo) {
PyErr_SetString(po_InvalidMessageOperation, imo.what());
@@ -747,71 +672,75 @@ Message_fromWire(s_Message* self, PyObject* args) {
}
}
-// Module Initialization, all statics are initialized here
-bool
-initModulePart_Message(PyObject* mod) {
- if (PyType_Ready(&message_type) < 0) {
- return (false);
- }
- Py_INCREF(&message_type);
-
- // Class variables
- // These are added to the tp_dict of the type object
- //
- addClassVariable(message_type, "PARSE",
- Py_BuildValue("I", Message::PARSE));
- addClassVariable(message_type, "RENDER",
- Py_BuildValue("I", Message::RENDER));
-
- addClassVariable(message_type, "HEADERFLAG_QR",
- Py_BuildValue("I", Message::HEADERFLAG_QR));
- addClassVariable(message_type, "HEADERFLAG_AA",
- Py_BuildValue("I", Message::HEADERFLAG_AA));
- addClassVariable(message_type, "HEADERFLAG_TC",
- Py_BuildValue("I", Message::HEADERFLAG_TC));
- addClassVariable(message_type, "HEADERFLAG_RD",
- Py_BuildValue("I", Message::HEADERFLAG_RD));
- addClassVariable(message_type, "HEADERFLAG_RA",
- Py_BuildValue("I", Message::HEADERFLAG_RA));
- addClassVariable(message_type, "HEADERFLAG_AD",
- Py_BuildValue("I", Message::HEADERFLAG_AD));
- addClassVariable(message_type, "HEADERFLAG_CD",
- Py_BuildValue("I", Message::HEADERFLAG_CD));
-
- addClassVariable(message_type, "SECTION_QUESTION",
- Py_BuildValue("I", Message::SECTION_QUESTION));
- addClassVariable(message_type, "SECTION_ANSWER",
- Py_BuildValue("I", Message::SECTION_ANSWER));
- addClassVariable(message_type, "SECTION_AUTHORITY",
- Py_BuildValue("I", Message::SECTION_AUTHORITY));
- addClassVariable(message_type, "SECTION_ADDITIONAL",
- Py_BuildValue("I", Message::SECTION_ADDITIONAL));
-
- addClassVariable(message_type, "DEFAULT_MAX_UDPSIZE",
- Py_BuildValue("I", Message::DEFAULT_MAX_UDPSIZE));
-
- /* Class-specific exceptions */
- po_MessageTooShort = PyErr_NewException("pydnspp.MessageTooShort", NULL,
- NULL);
- PyModule_AddObject(mod, "MessageTooShort", po_MessageTooShort);
- po_InvalidMessageSection =
- PyErr_NewException("pydnspp.InvalidMessageSection", NULL, NULL);
- PyModule_AddObject(mod, "InvalidMessageSection", po_InvalidMessageSection);
- po_InvalidMessageOperation =
- PyErr_NewException("pydnspp.InvalidMessageOperation", NULL, NULL);
- PyModule_AddObject(mod, "InvalidMessageOperation",
- po_InvalidMessageOperation);
- po_InvalidMessageUDPSize =
- PyErr_NewException("pydnspp.InvalidMessageUDPSize", NULL, NULL);
- PyModule_AddObject(mod, "InvalidMessageUDPSize", po_InvalidMessageUDPSize);
- po_DNSMessageBADVERS = PyErr_NewException("pydnspp.DNSMessageBADVERS",
- NULL, NULL);
- PyModule_AddObject(mod, "DNSMessageBADVERS", po_DNSMessageBADVERS);
-
- PyModule_AddObject(mod, "Message",
- reinterpret_cast<PyObject*>(&message_type));
-
-
- return (true);
-}
} // end of unnamed namespace
+
+namespace isc {
+namespace dns {
+namespace python {
+
+//
+// Declaration of the custom exceptions
+// Initialization and addition of these go in the initModulePart
+// function in pydnspp.cc
+//
+PyObject* po_MessageTooShort;
+PyObject* po_InvalidMessageSection;
+PyObject* po_InvalidMessageOperation;
+PyObject* po_InvalidMessageUDPSize;
+
+// This defines the complete type for reflection in python and
+// parsing of PyObject* to s_Message
+// Most of the functions are not actually implemented and NULL here.
+PyTypeObject message_type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "pydnspp.Message",
+ sizeof(s_Message), // tp_basicsize
+ 0, // tp_itemsize
+ (destructor)Message_destroy, // tp_dealloc
+ NULL, // tp_print
+ NULL, // tp_getattr
+ NULL, // tp_setattr
+ NULL, // tp_reserved
+ NULL, // tp_repr
+ NULL, // tp_as_number
+ NULL, // tp_as_sequence
+ NULL, // tp_as_mapping
+ NULL, // tp_hash
+ NULL, // tp_call
+ Message_str, // tp_str
+ NULL, // tp_getattro
+ NULL, // tp_setattro
+ NULL, // tp_as_buffer
+ Py_TPFLAGS_DEFAULT, // tp_flags
+ "The Message class encapsulates a standard DNS message.",
+ NULL, // tp_traverse
+ NULL, // tp_clear
+ NULL, // tp_richcompare
+ 0, // tp_weaklistoffset
+ NULL, // tp_iter
+ NULL, // tp_iternext
+ Message_methods, // tp_methods
+ NULL, // tp_members
+ NULL, // tp_getset
+ NULL, // tp_base
+ NULL, // tp_dict
+ NULL, // tp_descr_get
+ NULL, // tp_descr_set
+ 0, // tp_dictoffset
+ (initproc)Message_init, // tp_init
+ NULL, // tp_alloc
+ PyType_GenericNew, // tp_new
+ NULL, // tp_free
+ NULL, // tp_is_gc
+ NULL, // tp_bases
+ NULL, // tp_mro
+ NULL, // tp_cache
+ NULL, // tp_subclasses
+ NULL, // tp_weaklist
+ NULL, // tp_del
+ 0 // tp_version_tag
+};
+
+} // end python namespace
+} // end dns namespace
+} // end isc namespace
diff --git a/src/lib/dns/python/message_python.h b/src/lib/dns/python/message_python.h
new file mode 100644
index 0000000..be23890
--- /dev/null
+++ b/src/lib/dns/python/message_python.h
@@ -0,0 +1,40 @@
+// 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.
+
+#ifndef __PYTHON_MESSAGE_H
+#define __PYTHON_MESSAGE_H 1
+
+#include <Python.h>
+
+namespace isc {
+namespace dns {
+class Message;
+
+namespace python {
+
+extern PyObject* po_MessageTooShort;
+extern PyObject* po_InvalidMessageSection;
+extern PyObject* po_InvalidMessageOperation;
+extern PyObject* po_InvalidMessageUDPSize;
+
+extern PyTypeObject message_type;
+
+} // namespace python
+} // namespace dns
+} // namespace isc
+#endif // __PYTHON_MESSAGE_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/python/messagerenderer_python.cc b/src/lib/dns/python/messagerenderer_python.cc
index e6f5d3e..bb89622 100644
--- a/src/lib/dns/python/messagerenderer_python.cc
+++ b/src/lib/dns/python/messagerenderer_python.cc
@@ -17,6 +17,7 @@
#include <util/buffer.h>
#include <dns/messagerenderer.h>
+#include <util/python/pycppwrapper_util.h>
#include "pydnspp_common.h"
#include "messagerenderer_python.h"
@@ -24,15 +25,21 @@
using namespace isc::dns;
using namespace isc::dns::python;
using namespace isc::util;
-
-// MessageRenderer
-
-s_MessageRenderer::s_MessageRenderer() : outputbuffer(NULL),
- messagerenderer(NULL)
-{
-}
+using namespace isc::util::python;
namespace {
+// The s_* Class simply covers one instantiation of the object.
+//
+// since we don't use *Buffer in the python version (but work with
+// the already existing bytearray type where we use these custom buffers
+// in C++, we need to keep track of one here.
+class s_MessageRenderer : public PyObject {
+public:
+ s_MessageRenderer();
+ isc::util::OutputBuffer* outputbuffer;
+ MessageRenderer* cppobj;
+};
+
int MessageRenderer_init(s_MessageRenderer* self);
void MessageRenderer_destroy(s_MessageRenderer* self);
@@ -72,15 +79,15 @@ PyMethodDef MessageRenderer_methods[] = {
int
MessageRenderer_init(s_MessageRenderer* self) {
self->outputbuffer = new OutputBuffer(4096);
- self->messagerenderer = new MessageRenderer(*self->outputbuffer);
+ self->cppobj = new MessageRenderer(*self->outputbuffer);
return (0);
}
void
MessageRenderer_destroy(s_MessageRenderer* self) {
- delete self->messagerenderer;
+ delete self->cppobj;
delete self->outputbuffer;
- self->messagerenderer = NULL;
+ self->cppobj = NULL;
self->outputbuffer = NULL;
Py_TYPE(self)->tp_free(self);
}
@@ -88,18 +95,18 @@ MessageRenderer_destroy(s_MessageRenderer* self) {
PyObject*
MessageRenderer_getData(s_MessageRenderer* self) {
return (Py_BuildValue("y#",
- self->messagerenderer->getData(),
- self->messagerenderer->getLength()));
+ self->cppobj->getData(),
+ self->cppobj->getLength()));
}
PyObject*
MessageRenderer_getLength(s_MessageRenderer* self) {
- return (Py_BuildValue("I", self->messagerenderer->getLength()));
+ return (Py_BuildValue("I", self->cppobj->getLength()));
}
PyObject*
MessageRenderer_isTruncated(s_MessageRenderer* self) {
- if (self->messagerenderer->isTruncated()) {
+ if (self->cppobj->isTruncated()) {
Py_RETURN_TRUE;
} else {
Py_RETURN_FALSE;
@@ -108,17 +115,17 @@ MessageRenderer_isTruncated(s_MessageRenderer* self) {
PyObject*
MessageRenderer_getLengthLimit(s_MessageRenderer* self) {
- return (Py_BuildValue("I", self->messagerenderer->getLengthLimit()));
+ return (Py_BuildValue("I", self->cppobj->getLengthLimit()));
}
PyObject*
MessageRenderer_getCompressMode(s_MessageRenderer* self) {
- return (Py_BuildValue("I", self->messagerenderer->getCompressMode()));
+ return (Py_BuildValue("I", self->cppobj->getCompressMode()));
}
PyObject*
MessageRenderer_setTruncated(s_MessageRenderer* self) {
- self->messagerenderer->setTruncated();
+ self->cppobj->setTruncated();
Py_RETURN_NONE;
}
@@ -138,7 +145,7 @@ MessageRenderer_setLengthLimit(s_MessageRenderer* self,
"MessageRenderer length limit out of range");
return (NULL);
}
- self->messagerenderer->setLengthLimit(lengthlimit);
+ self->cppobj->setLengthLimit(lengthlimit);
Py_RETURN_NONE;
}
@@ -152,12 +159,12 @@ MessageRenderer_setCompressMode(s_MessageRenderer* self,
}
if (mode == MessageRenderer::CASE_INSENSITIVE) {
- self->messagerenderer->setCompressMode(MessageRenderer::CASE_INSENSITIVE);
+ self->cppobj->setCompressMode(MessageRenderer::CASE_INSENSITIVE);
// If we return NULL it is seen as an error, so use this for
// None returns, it also applies to CASE_SENSITIVE.
Py_RETURN_NONE;
} else if (mode == MessageRenderer::CASE_SENSITIVE) {
- self->messagerenderer->setCompressMode(MessageRenderer::CASE_SENSITIVE);
+ self->cppobj->setCompressMode(MessageRenderer::CASE_SENSITIVE);
Py_RETURN_NONE;
} else {
PyErr_SetString(PyExc_TypeError,
@@ -169,12 +176,11 @@ MessageRenderer_setCompressMode(s_MessageRenderer* self,
PyObject*
MessageRenderer_clear(s_MessageRenderer* self) {
- self->messagerenderer->clear();
+ self->cppobj->clear();
Py_RETURN_NONE;
}
} // end of unnamed namespace
-// end of MessageRenderer
namespace isc {
namespace dns {
namespace python {
@@ -233,37 +239,29 @@ PyTypeObject messagerenderer_type = {
0 // tp_version_tag
};
-// Module Initialization, all statics are initialized here
-bool
-initModulePart_MessageRenderer(PyObject* mod) {
- // Add the exceptions to the module
+// If we need a createMessageRendererObject(), should we copy? can we?
+// copy the existing buffer into a new one, then create a new renderer with
+// that buffer?
- // Add the enums to the module
-
- // Add the constants to the module
-
- // Add the classes to the module
- // We initialize the static description object with PyType_Ready(),
- // then add it to the module
+bool
+PyMessageRenderer_Check(PyObject* obj) {
+ if (obj == NULL) {
+ isc_throw(PyCPPWrapperException, "obj argument NULL in typecheck");
+ }
+ return (PyObject_TypeCheck(obj, &messagerenderer_type));
+}
- // NameComparisonResult
- if (PyType_Ready(&messagerenderer_type) < 0) {
- return (false);
+MessageRenderer&
+PyMessageRenderer_ToMessageRenderer(PyObject* messagerenderer_obj) {
+ if (messagerenderer_obj == NULL) {
+ isc_throw(PyCPPWrapperException,
+ "obj argument NULL in MessageRenderer PyObject conversion");
}
- Py_INCREF(&messagerenderer_type);
+ s_MessageRenderer* messagerenderer = static_cast<s_MessageRenderer*>(messagerenderer_obj);
+ return (*messagerenderer->cppobj);
+}
- // Class variables
- // These are added to the tp_dict of the type object
- addClassVariable(messagerenderer_type, "CASE_INSENSITIVE",
- Py_BuildValue("I", MessageRenderer::CASE_INSENSITIVE));
- addClassVariable(messagerenderer_type, "CASE_SENSITIVE",
- Py_BuildValue("I", MessageRenderer::CASE_SENSITIVE));
- PyModule_AddObject(mod, "MessageRenderer",
- reinterpret_cast<PyObject*>(&messagerenderer_type));
-
- return (true);
-}
} // namespace python
} // namespace dns
} // namespace isc
diff --git a/src/lib/dns/python/messagerenderer_python.h b/src/lib/dns/python/messagerenderer_python.h
index 3bb096e..ea9a940 100644
--- a/src/lib/dns/python/messagerenderer_python.h
+++ b/src/lib/dns/python/messagerenderer_python.h
@@ -17,30 +17,35 @@
#include <Python.h>
+#include <util/buffer.h>
+
namespace isc {
-namespace util {
-class OutputBuffer;
-}
namespace dns {
class MessageRenderer;
namespace python {
-// The s_* Class simply covers one instantiation of the object.
-//
-// since we don't use *Buffer in the python version (but work with
-// the already existing bytearray type where we use these custom buffers
-// in C++, we need to keep track of one here.
-class s_MessageRenderer : public PyObject {
-public:
- s_MessageRenderer();
- isc::util::OutputBuffer* outputbuffer;
- MessageRenderer* messagerenderer;
-};
-
extern PyTypeObject messagerenderer_type;
-bool initModulePart_MessageRenderer(PyObject* mod);
+/// \brief Checks if the given python object is a MessageRenderer object
+///
+/// \exception PyCPPWrapperException if obj is NULL
+///
+/// \param obj The object to check the type of
+/// \return true if the object is of type MessageRenderer, false otherwise
+bool PyMessageRenderer_Check(PyObject* obj);
+
+/// \brief Returns a reference to the MessageRenderer object contained within the given
+/// Python object.
+///
+/// \note The given object MUST be of type MessageRenderer; this can be checked with
+/// either the right call to ParseTuple("O!"), or with PyMessageRenderer_Check()
+///
+/// \note This is not a copy; if the MessageRenderer is needed when the PyObject
+/// may be destroyed, the caller must copy it itself.
+///
+/// \param messagerenderer_obj The messagerenderer object to convert
+MessageRenderer& PyMessageRenderer_ToMessageRenderer(PyObject* messagerenderer_obj);
} // namespace python
} // namespace dns
diff --git a/src/lib/dns/python/name_python.cc b/src/lib/dns/python/name_python.cc
index d00c6f7..4043445 100644
--- a/src/lib/dns/python/name_python.cc
+++ b/src/lib/dns/python/name_python.cc
@@ -25,20 +25,25 @@
#include "messagerenderer_python.h"
#include "name_python.h"
-//
-// Definition of the classes
-//
-
-// For each class, we need a struct, a helper functions (init, destroy,
-// and static wrappers around the methods we export), a list of methods,
-// and a type description
using namespace isc::dns;
using namespace isc::dns::python;
using namespace isc::util;
using namespace isc::util::python;
namespace {
-// NameComparisonResult
+// The s_* Class simply covers one instantiation of the object.
+class s_NameComparisonResult : public PyObject {
+public:
+ s_NameComparisonResult() : cppobj(NULL) {}
+ NameComparisonResult* cppobj;
+};
+
+class s_Name : public PyObject {
+public:
+ s_Name() : cppobj(NULL), position(0) {}
+ Name* cppobj;
+ size_t position;
+};
int NameComparisonResult_init(s_NameComparisonResult*, PyObject*);
void NameComparisonResult_destroy(s_NameComparisonResult* self);
@@ -84,9 +89,7 @@ PyObject*
NameComparisonResult_getRelation(s_NameComparisonResult* self) {
return (Py_BuildValue("I", self->cppobj->getRelation()));
}
-// end of NameComparisonResult
-// Name
// Shortcut type which would be convenient for adding class variables safely.
typedef CPPPyObjectContainer<s_Name, Name> NameContainer;
@@ -292,7 +295,7 @@ Name_str(PyObject* self) {
PyObject*
Name_toWire(s_Name* self, PyObject* args) {
PyObject* bytes;
- s_MessageRenderer* mr;
+ PyObject* mr;
if (PyArg_ParseTuple(args, "O", &bytes) && PySequence_Check(bytes)) {
PyObject* bytes_o = bytes;
@@ -306,7 +309,7 @@ Name_toWire(s_Name* self, PyObject* args) {
Py_DECREF(name_bytes);
return (result);
} else if (PyArg_ParseTuple(args, "O!", &messagerenderer_type, &mr)) {
- self->cppobj->toWire(*mr->messagerenderer);
+ self->cppobj->toWire(PyMessageRenderer_ToMessageRenderer(mr));
// If we return NULL it is seen as an error, so use this for
// None returns
Py_RETURN_NONE;
@@ -495,7 +498,7 @@ Name_isWildCard(s_Name* self) {
Py_RETURN_FALSE;
}
}
-// end of Name
+
} // end of unnamed namespace
namespace isc {
@@ -634,94 +637,32 @@ PyTypeObject name_type = {
0 // tp_version_tag
};
-// Module Initialization, all statics are initialized here
-bool
-initModulePart_Name(PyObject* mod) {
- // Add the classes to the module
- // We initialize the static description object with PyType_Ready(),
- // then add it to the module
-
- //
- // NameComparisonResult
- //
- if (PyType_Ready(&name_comparison_result_type) < 0) {
- return (false);
- }
- Py_INCREF(&name_comparison_result_type);
-
- // Add the enums to the module
- po_NameRelation = Py_BuildValue("{i:s,i:s,i:s,i:s}",
- NameComparisonResult::SUPERDOMAIN, "SUPERDOMAIN",
- NameComparisonResult::SUBDOMAIN, "SUBDOMAIN",
- NameComparisonResult::EQUAL, "EQUAL",
- NameComparisonResult::COMMONANCESTOR, "COMMONANCESTOR");
- addClassVariable(name_comparison_result_type, "NameRelation", po_NameRelation);
-
- PyModule_AddObject(mod, "NameComparisonResult",
- reinterpret_cast<PyObject*>(&name_comparison_result_type));
-
- //
- // Name
- //
-
- if (PyType_Ready(&name_type) < 0) {
- return (false);
- }
- Py_INCREF(&name_type);
-
- // Add the constants to the module
- addClassVariable(name_type, "MAX_WIRE", Py_BuildValue("I", Name::MAX_WIRE));
- addClassVariable(name_type, "MAX_LABELS", Py_BuildValue("I", Name::MAX_LABELS));
- addClassVariable(name_type, "MAX_LABELLEN", Py_BuildValue("I", Name::MAX_LABELLEN));
- addClassVariable(name_type, "MAX_COMPRESS_POINTER", Py_BuildValue("I", Name::MAX_COMPRESS_POINTER));
- addClassVariable(name_type, "COMPRESS_POINTER_MARK8", Py_BuildValue("I", Name::COMPRESS_POINTER_MARK8));
- addClassVariable(name_type, "COMPRESS_POINTER_MARK16", Py_BuildValue("I", Name::COMPRESS_POINTER_MARK16));
-
- s_Name* root_name = PyObject_New(s_Name, &name_type);
- root_name->cppobj = new Name(Name::ROOT_NAME());
- PyObject* po_ROOT_NAME = root_name;
- addClassVariable(name_type, "ROOT_NAME", po_ROOT_NAME);
-
- PyModule_AddObject(mod, "Name",
- reinterpret_cast<PyObject*>(&name_type));
-
-
- // Add the exceptions to the module
- po_EmptyLabel = PyErr_NewException("pydnspp.EmptyLabel", NULL, NULL);
- PyModule_AddObject(mod, "EmptyLabel", po_EmptyLabel);
-
- po_TooLongName = PyErr_NewException("pydnspp.TooLongName", NULL, NULL);
- PyModule_AddObject(mod, "TooLongName", po_TooLongName);
-
- po_TooLongLabel = PyErr_NewException("pydnspp.TooLongLabel", NULL, NULL);
- PyModule_AddObject(mod, "TooLongLabel", po_TooLongLabel);
-
- po_BadLabelType = PyErr_NewException("pydnspp.BadLabelType", NULL, NULL);
- PyModule_AddObject(mod, "BadLabelType", po_BadLabelType);
-
- po_BadEscape = PyErr_NewException("pydnspp.BadEscape", NULL, NULL);
- PyModule_AddObject(mod, "BadEscape", po_BadEscape);
-
- po_IncompleteName = PyErr_NewException("pydnspp.IncompleteName", NULL, NULL);
- PyModule_AddObject(mod, "IncompleteName", po_IncompleteName);
-
- po_InvalidBufferPosition = PyErr_NewException("pydnspp.InvalidBufferPosition", NULL, NULL);
- PyModule_AddObject(mod, "InvalidBufferPosition", po_InvalidBufferPosition);
-
- // This one could have gone into the message_python.cc file, but is
- // already needed here.
- po_DNSMessageFORMERR = PyErr_NewException("pydnspp.DNSMessageFORMERR", NULL, NULL);
- PyModule_AddObject(mod, "DNSMessageFORMERR", po_DNSMessageFORMERR);
-
- return (true);
-}
-
PyObject*
createNameObject(const Name& source) {
- NameContainer container = PyObject_New(s_Name, &name_type);
+ NameContainer container(PyObject_New(s_Name, &name_type));
container.set(new Name(source));
return (container.release());
}
+
+bool
+PyName_Check(PyObject* obj) {
+ if (obj == NULL) {
+ isc_throw(PyCPPWrapperException, "obj argument NULL in typecheck");
+ }
+ return (PyObject_TypeCheck(obj, &name_type));
+}
+
+const Name&
+PyName_ToName(const PyObject* name_obj) {
+ if (name_obj == NULL) {
+ isc_throw(PyCPPWrapperException,
+ "obj argument NULL in Name PyObject conversion");
+ }
+ const s_Name* name = static_cast<const s_Name*>(name_obj);
+ return (*name->cppobj);
+}
+
+
} // namespace python
} // namespace dns
} // namespace isc
diff --git a/src/lib/dns/python/name_python.h b/src/lib/dns/python/name_python.h
index f8e793d..86d7fd0 100644
--- a/src/lib/dns/python/name_python.h
+++ b/src/lib/dns/python/name_python.h
@@ -17,20 +17,12 @@
#include <Python.h>
-#include <util/python/pycppwrapper_util.h>
-
namespace isc {
namespace dns {
-class NameComparisonResult;
class Name;
namespace python {
-//
-// Declaration of the custom exceptions
-// Initialization and addition of these go in the module init at the
-// end
-//
extern PyObject* po_EmptyLabel;
extern PyObject* po_TooLongName;
extern PyObject* po_TooLongLabel;
@@ -47,25 +39,9 @@ extern PyObject* po_DNSMessageFORMERR;
//
extern PyObject* po_NameRelation;
-// The s_* Class simply covers one instantiation of the object.
-class s_NameComparisonResult : public PyObject {
-public:
- s_NameComparisonResult() : cppobj(NULL) {}
- NameComparisonResult* cppobj;
-};
-
-class s_Name : public PyObject {
-public:
- s_Name() : cppobj(NULL), position(0) {}
- Name* cppobj;
- size_t position;
-};
-
extern PyTypeObject name_comparison_result_type;
extern PyTypeObject name_type;
-bool initModulePart_Name(PyObject* mod);
-
/// This is A simple shortcut to create a python Name object (in the
/// form of a pointer to PyObject) with minimal exception safety.
/// On success, it returns a valid pointer to PyObject with a reference
@@ -74,6 +50,27 @@ bool initModulePart_Name(PyObject* mod);
/// This function is expected to be called with in a try block
/// followed by necessary setup for python exception.
PyObject* createNameObject(const Name& source);
+
+/// \brief Checks if the given python object is a Name object
+///
+/// \exception PyCPPWrapperException if obj is NULL
+///
+/// \param obj The object to check the type of
+/// \return true if the object is of type Name, false otherwise
+bool PyName_Check(PyObject* obj);
+
+/// \brief Returns a reference to the Name object contained within the given
+/// Python object.
+///
+/// \note The given object MUST be of type Name; this can be checked with
+/// either the right call to ParseTuple("O!"), or with PyName_Check()
+///
+/// \note This is not a copy; if the Name is needed when the PyObject
+/// may be destroyed, the caller must copy it itself.
+///
+/// \param name_obj The name object to convert
+const Name& PyName_ToName(const PyObject* name_obj);
+
} // namespace python
} // namespace dns
} // namespace isc
diff --git a/src/lib/dns/python/opcode_python.cc b/src/lib/dns/python/opcode_python.cc
index 0e2a30b..50436a9 100644
--- a/src/lib/dns/python/opcode_python.cc
+++ b/src/lib/dns/python/opcode_python.cc
@@ -12,32 +12,31 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-#include <dns/opcode.h>
-
-using namespace isc::dns;
+#include <Python.h>
-//
-// Declaration of the custom exceptions (None for this class)
+#include <dns/opcode.h>
+#include <util/python/pycppwrapper_util.h>
-//
-// Definition of the classes
-//
+#include "pydnspp_common.h"
+#include "opcode_python.h"
+#include "edns_python.h"
-// For each class, we need a struct, a helper functions (init, destroy,
-// and static wrappers around the methods we export), a list of methods,
-// and a type description
+using namespace isc::dns;
+using namespace isc::dns::python;
+using namespace isc::util;
+using namespace isc::util::python;
namespace {
-//
-// Opcode
-//
+
class s_Opcode : public PyObject {
public:
- s_Opcode() : opcode(NULL), static_code(false) {}
- const Opcode* opcode;
+ s_Opcode() : cppobj(NULL), static_code(false) {}
+ const isc::dns::Opcode* cppobj;
bool static_code;
};
+typedef CPPPyObjectContainer<s_Opcode, Opcode> OpcodeContainer;
+
int Opcode_init(s_Opcode* const self, PyObject* args);
void Opcode_destroy(s_Opcode* const self);
@@ -103,64 +102,13 @@ PyMethodDef Opcode_methods[] = {
{ NULL, NULL, 0, NULL }
};
-PyTypeObject opcode_type = {
- PyVarObject_HEAD_INIT(NULL, 0)
- "pydnspp.Opcode",
- sizeof(s_Opcode), // tp_basicsize
- 0, // tp_itemsize
- (destructor)Opcode_destroy, // tp_dealloc
- NULL, // tp_print
- NULL, // tp_getattr
- NULL, // tp_setattr
- NULL, // tp_reserved
- NULL, // tp_repr
- NULL, // tp_as_number
- NULL, // tp_as_sequence
- NULL, // tp_as_mapping
- NULL, // tp_hash
- NULL, // tp_call
- Opcode_str, // tp_str
- NULL, // tp_getattro
- NULL, // tp_setattro
- NULL, // tp_as_buffer
- Py_TPFLAGS_DEFAULT, // tp_flags
- "The Opcode class objects represent standard OPCODEs "
- "of the header section of DNS messages.",
- NULL, // tp_traverse
- NULL, // tp_clear
- (richcmpfunc)Opcode_richcmp, // tp_richcompare
- 0, // tp_weaklistoffset
- NULL, // tp_iter
- NULL, // tp_iternext
- Opcode_methods, // tp_methods
- NULL, // tp_members
- NULL, // tp_getset
- NULL, // tp_base
- NULL, // tp_dict
- NULL, // tp_descr_get
- NULL, // tp_descr_set
- 0, // tp_dictoffset
- (initproc)Opcode_init, // tp_init
- NULL, // tp_alloc
- PyType_GenericNew, // tp_new
- NULL, // tp_free
- NULL, // tp_is_gc
- NULL, // tp_bases
- NULL, // tp_mro
- NULL, // tp_cache
- NULL, // tp_subclasses
- NULL, // tp_weaklist
- NULL, // tp_del
- 0 // tp_version_tag
-};
-
int
Opcode_init(s_Opcode* const self, PyObject* args) {
uint8_t code = 0;
if (PyArg_ParseTuple(args, "b", &code)) {
try {
- self->opcode = new Opcode(code);
+ self->cppobj = new Opcode(code);
self->static_code = false;
} catch (const isc::OutOfRange& ex) {
PyErr_SetString(PyExc_OverflowError, ex.what());
@@ -181,22 +129,22 @@ Opcode_init(s_Opcode* const self, PyObject* args) {
void
Opcode_destroy(s_Opcode* const self) {
// Depending on whether we created the rcode or are referring
- // to a global static one, we do or do not delete self->opcode here
+ // to a global static one, we do or do not delete self->cppobj here
if (!self->static_code) {
- delete self->opcode;
+ delete self->cppobj;
}
- self->opcode = NULL;
+ self->cppobj = NULL;
Py_TYPE(self)->tp_free(self);
}
PyObject*
Opcode_getCode(const s_Opcode* const self) {
- return (Py_BuildValue("I", self->opcode->getCode()));
+ return (Py_BuildValue("I", self->cppobj->getCode()));
}
PyObject*
Opcode_toText(const s_Opcode* const self) {
- return (Py_BuildValue("s", self->opcode->toText().c_str()));
+ return (Py_BuildValue("s", self->cppobj->toText().c_str()));
}
PyObject*
@@ -211,7 +159,7 @@ PyObject*
Opcode_createStatic(const Opcode& opcode) {
s_Opcode* ret = PyObject_New(s_Opcode, &opcode_type);
if (ret != NULL) {
- ret->opcode = &opcode;
+ ret->cppobj = &opcode;
ret->static_code = true;
}
return (ret);
@@ -297,7 +245,7 @@ Opcode_RESERVED15(const s_Opcode*) {
return (Opcode_createStatic(Opcode::RESERVED15()));
}
-PyObject*
+PyObject*
Opcode_richcmp(const s_Opcode* const self, const s_Opcode* const other,
const int op)
{
@@ -318,10 +266,10 @@ Opcode_richcmp(const s_Opcode* const self, const s_Opcode* const other,
PyErr_SetString(PyExc_TypeError, "Unorderable type; Opcode");
return (NULL);
case Py_EQ:
- c = (*self->opcode == *other->opcode);
+ c = (*self->cppobj == *other->cppobj);
break;
case Py_NE:
- c = (*self->opcode != *other->opcode);
+ c = (*self->cppobj != *other->cppobj);
break;
case Py_GT:
PyErr_SetString(PyExc_TypeError, "Unorderable type; Opcode");
@@ -336,55 +284,88 @@ Opcode_richcmp(const s_Opcode* const self, const s_Opcode* const other,
Py_RETURN_FALSE;
}
-// Module Initialization, all statics are initialized here
+} // end of unnamed namespace
+
+namespace isc {
+namespace dns {
+namespace python {
+
+PyTypeObject opcode_type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "pydnspp.Opcode",
+ sizeof(s_Opcode), // tp_basicsize
+ 0, // tp_itemsize
+ (destructor)Opcode_destroy, // tp_dealloc
+ NULL, // tp_print
+ NULL, // tp_getattr
+ NULL, // tp_setattr
+ NULL, // tp_reserved
+ NULL, // tp_repr
+ NULL, // tp_as_number
+ NULL, // tp_as_sequence
+ NULL, // tp_as_mapping
+ NULL, // tp_hash
+ NULL, // tp_call
+ Opcode_str, // tp_str
+ NULL, // tp_getattro
+ NULL, // tp_setattro
+ NULL, // tp_as_buffer
+ Py_TPFLAGS_DEFAULT, // tp_flags
+ "The Opcode class objects represent standard OPCODEs "
+ "of the header section of DNS messages.",
+ NULL, // tp_traverse
+ NULL, // tp_clear
+ (richcmpfunc)Opcode_richcmp, // tp_richcompare
+ 0, // tp_weaklistoffset
+ NULL, // tp_iter
+ NULL, // tp_iternext
+ Opcode_methods, // tp_methods
+ NULL, // tp_members
+ NULL, // tp_getset
+ NULL, // tp_base
+ NULL, // tp_dict
+ NULL, // tp_descr_get
+ NULL, // tp_descr_set
+ 0, // tp_dictoffset
+ (initproc)Opcode_init, // tp_init
+ NULL, // tp_alloc
+ PyType_GenericNew, // tp_new
+ NULL, // tp_free
+ NULL, // tp_is_gc
+ NULL, // tp_bases
+ NULL, // tp_mro
+ NULL, // tp_cache
+ NULL, // tp_subclasses
+ NULL, // tp_weaklist
+ NULL, // tp_del
+ 0 // tp_version_tag
+};
+
+PyObject*
+createOpcodeObject(const Opcode& source) {
+ OpcodeContainer container(PyObject_New(s_Opcode, &opcode_type));
+ container.set(new Opcode(source));
+ return (container.release());
+}
+
bool
-initModulePart_Opcode(PyObject* mod) {
- // We initialize the static description object with PyType_Ready(),
- // then add it to the module. This is not just a check! (leaving
- // this out results in segmentation faults)
- if (PyType_Ready(&opcode_type) < 0) {
- return (false);
- }
- Py_INCREF(&opcode_type);
- void* p = &opcode_type;
- if (PyModule_AddObject(mod, "Opcode", static_cast<PyObject*>(p)) != 0) {
- Py_DECREF(&opcode_type);
- return (false);
+PyOpcode_Check(PyObject* obj) {
+ if (obj == NULL) {
+ isc_throw(PyCPPWrapperException, "obj argument NULL in typecheck");
}
+ return (PyObject_TypeCheck(obj, &opcode_type));
+}
- addClassVariable(opcode_type, "QUERY_CODE",
- Py_BuildValue("h", Opcode::QUERY_CODE));
- addClassVariable(opcode_type, "IQUERY_CODE",
- Py_BuildValue("h", Opcode::IQUERY_CODE));
- addClassVariable(opcode_type, "STATUS_CODE",
- Py_BuildValue("h", Opcode::STATUS_CODE));
- addClassVariable(opcode_type, "RESERVED3_CODE",
- Py_BuildValue("h", Opcode::RESERVED3_CODE));
- addClassVariable(opcode_type, "NOTIFY_CODE",
- Py_BuildValue("h", Opcode::NOTIFY_CODE));
- addClassVariable(opcode_type, "UPDATE_CODE",
- Py_BuildValue("h", Opcode::UPDATE_CODE));
- addClassVariable(opcode_type, "RESERVED6_CODE",
- Py_BuildValue("h", Opcode::RESERVED6_CODE));
- addClassVariable(opcode_type, "RESERVED7_CODE",
- Py_BuildValue("h", Opcode::RESERVED7_CODE));
- addClassVariable(opcode_type, "RESERVED8_CODE",
- Py_BuildValue("h", Opcode::RESERVED8_CODE));
- addClassVariable(opcode_type, "RESERVED9_CODE",
- Py_BuildValue("h", Opcode::RESERVED9_CODE));
- addClassVariable(opcode_type, "RESERVED10_CODE",
- Py_BuildValue("h", Opcode::RESERVED10_CODE));
- addClassVariable(opcode_type, "RESERVED11_CODE",
- Py_BuildValue("h", Opcode::RESERVED11_CODE));
- addClassVariable(opcode_type, "RESERVED12_CODE",
- Py_BuildValue("h", Opcode::RESERVED12_CODE));
- addClassVariable(opcode_type, "RESERVED13_CODE",
- Py_BuildValue("h", Opcode::RESERVED13_CODE));
- addClassVariable(opcode_type, "RESERVED14_CODE",
- Py_BuildValue("h", Opcode::RESERVED14_CODE));
- addClassVariable(opcode_type, "RESERVED15_CODE",
- Py_BuildValue("h", Opcode::RESERVED15_CODE));
-
- return (true);
+const Opcode&
+PyOpcode_ToOpcode(const PyObject* opcode_obj) {
+ if (opcode_obj == NULL) {
+ isc_throw(PyCPPWrapperException,
+ "obj argument NULL in Opcode PyObject conversion");
+ }
+ const s_Opcode* opcode = static_cast<const s_Opcode*>(opcode_obj);
+ return (*opcode->cppobj);
}
-} // end of unnamed namespace
+
+} // end python namespace
+} // end dns namespace
+} // end isc namespace
diff --git a/src/lib/dns/python/opcode_python.h b/src/lib/dns/python/opcode_python.h
new file mode 100644
index 0000000..d0aec15
--- /dev/null
+++ b/src/lib/dns/python/opcode_python.h
@@ -0,0 +1,64 @@
+// 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.
+
+#ifndef __PYTHON_OPCODE_H
+#define __PYTHON_OPCODE_H 1
+
+#include <Python.h>
+
+namespace isc {
+namespace dns {
+class Opcode;
+
+namespace python {
+
+extern PyTypeObject opcode_type;
+
+/// This is a simple shortcut to create a python Opcode object (in the
+/// form of a pointer to PyObject) with minimal exception safety.
+/// On success, it returns a valid pointer to PyObject with a reference
+/// counter of 1; if something goes wrong it throws an exception (it never
+/// returns a NULL pointer).
+/// This function is expected to be called within a try block
+/// followed by necessary setup for python exception.
+PyObject* createOpcodeObject(const Opcode& source);
+
+/// \brief Checks if the given python object is a Opcode object
+///
+/// \exception PyCPPWrapperException if obj is NULL
+///
+/// \param obj The object to check the type of
+/// \return true if the object is of type Opcode, false otherwise
+bool PyOpcode_Check(PyObject* obj);
+
+/// \brief Returns a reference to the Opcode object contained within the given
+/// Python object.
+///
+/// \note The given object MUST be of type Opcode; this can be checked with
+/// either the right call to ParseTuple("O!"), or with PyOpcode_Check()
+///
+/// \note This is not a copy; if the Opcode is needed when the PyObject
+/// may be destroyed, the caller must copy it itself.
+///
+/// \param opcode_obj The opcode object to convert
+const Opcode& PyOpcode_ToOpcode(const PyObject* opcode_obj);
+
+} // namespace python
+} // namespace dns
+} // namespace isc
+#endif // __PYTHON_OPCODE_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/python/pydnspp.cc b/src/lib/dns/python/pydnspp.cc
index 07abf71..830876c 100644
--- a/src/lib/dns/python/pydnspp.cc
+++ b/src/lib/dns/python/pydnspp.cc
@@ -21,63 +21,680 @@
// name initModulePart_<name>, and return true/false instead of
// NULL/*mod
//
-// And of course care has to be taken that all identifiers be unique
+// The big init function is split up into a separate initModulePart function
+// for each class we add.
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <structmember.h>
-#include <config.h>
-
-#include <exceptions/exceptions.h>
-
-#include <util/buffer.h>
-
-#include <dns/exceptions.h>
-#include <dns/name.h>
-#include <dns/messagerenderer.h>
+#include <dns/message.h>
+#include <dns/opcode.h>
+#include <dns/tsig.h>
+#include <util/python/pycppwrapper_util.h>
#include "pydnspp_common.h"
+
+#include "edns_python.h"
+#include "message_python.h"
#include "messagerenderer_python.h"
#include "name_python.h"
+#include "opcode_python.h"
+#include "pydnspp_common.h"
+#include "pydnspp_towire.h"
+#include "question_python.h"
#include "rcode_python.h"
+#include "rdata_python.h"
+#include "rrclass_python.h"
+#include "rrset_python.h"
+#include "rrttl_python.h"
+#include "rrtype_python.h"
+#include "tsigerror_python.h"
#include "tsigkey_python.h"
+#include "tsig_python.h"
#include "tsig_rdata_python.h"
-#include "tsigerror_python.h"
#include "tsigrecord_python.h"
-#include "tsig_python.h"
-namespace isc {
-namespace dns {
-namespace python {
-// For our 'general' isc::Exceptions
-PyObject* po_IscException;
-PyObject* po_InvalidParameter;
+using namespace isc::dns;
+using namespace isc::dns::python;
+using namespace isc::util::python;
+
+namespace {
+
+bool
+initModulePart_EDNS(PyObject* mod) {
+ // We initialize the static description object with PyType_Ready(),
+ // then add it to the module. This is not just a check! (leaving
+ // this out results in segmentation faults)
+ //
+ // After the type has been initialized, we initialize any exceptions
+ // that are defined in the wrapper for this class, and add constants
+ // to the type, if any
+
+ if (PyType_Ready(&edns_type) < 0) {
+ return (false);
+ }
+ Py_INCREF(&edns_type);
+ void* p = &edns_type;
+ PyModule_AddObject(mod, "EDNS", static_cast<PyObject*>(p));
+
+ addClassVariable(edns_type, "SUPPORTED_VERSION",
+ Py_BuildValue("B", EDNS::SUPPORTED_VERSION));
-// For our own isc::dns::Exception
-PyObject* po_DNSMessageBADVERS;
+ return (true);
}
+
+bool
+initModulePart_Message(PyObject* mod) {
+ if (PyType_Ready(&message_type) < 0) {
+ return (false);
+ }
+ Py_INCREF(&message_type);
+
+ // Class variables
+ // These are added to the tp_dict of the type object
+ //
+ addClassVariable(message_type, "PARSE",
+ Py_BuildValue("I", Message::PARSE));
+ addClassVariable(message_type, "RENDER",
+ Py_BuildValue("I", Message::RENDER));
+
+ addClassVariable(message_type, "HEADERFLAG_QR",
+ Py_BuildValue("I", Message::HEADERFLAG_QR));
+ addClassVariable(message_type, "HEADERFLAG_AA",
+ Py_BuildValue("I", Message::HEADERFLAG_AA));
+ addClassVariable(message_type, "HEADERFLAG_TC",
+ Py_BuildValue("I", Message::HEADERFLAG_TC));
+ addClassVariable(message_type, "HEADERFLAG_RD",
+ Py_BuildValue("I", Message::HEADERFLAG_RD));
+ addClassVariable(message_type, "HEADERFLAG_RA",
+ Py_BuildValue("I", Message::HEADERFLAG_RA));
+ addClassVariable(message_type, "HEADERFLAG_AD",
+ Py_BuildValue("I", Message::HEADERFLAG_AD));
+ addClassVariable(message_type, "HEADERFLAG_CD",
+ Py_BuildValue("I", Message::HEADERFLAG_CD));
+
+ addClassVariable(message_type, "SECTION_QUESTION",
+ Py_BuildValue("I", Message::SECTION_QUESTION));
+ addClassVariable(message_type, "SECTION_ANSWER",
+ Py_BuildValue("I", Message::SECTION_ANSWER));
+ addClassVariable(message_type, "SECTION_AUTHORITY",
+ Py_BuildValue("I", Message::SECTION_AUTHORITY));
+ addClassVariable(message_type, "SECTION_ADDITIONAL",
+ Py_BuildValue("I", Message::SECTION_ADDITIONAL));
+
+ addClassVariable(message_type, "DEFAULT_MAX_UDPSIZE",
+ Py_BuildValue("I", Message::DEFAULT_MAX_UDPSIZE));
+
+ /* Class-specific exceptions */
+ po_MessageTooShort = PyErr_NewException("pydnspp.MessageTooShort", NULL,
+ NULL);
+ PyModule_AddObject(mod, "MessageTooShort", po_MessageTooShort);
+ po_InvalidMessageSection =
+ PyErr_NewException("pydnspp.InvalidMessageSection", NULL, NULL);
+ PyModule_AddObject(mod, "InvalidMessageSection", po_InvalidMessageSection);
+ po_InvalidMessageOperation =
+ PyErr_NewException("pydnspp.InvalidMessageOperation", NULL, NULL);
+ PyModule_AddObject(mod, "InvalidMessageOperation",
+ po_InvalidMessageOperation);
+ po_InvalidMessageUDPSize =
+ PyErr_NewException("pydnspp.InvalidMessageUDPSize", NULL, NULL);
+ PyModule_AddObject(mod, "InvalidMessageUDPSize", po_InvalidMessageUDPSize);
+ po_DNSMessageBADVERS = PyErr_NewException("pydnspp.DNSMessageBADVERS",
+ NULL, NULL);
+ PyModule_AddObject(mod, "DNSMessageBADVERS", po_DNSMessageBADVERS);
+
+ PyModule_AddObject(mod, "Message",
+ reinterpret_cast<PyObject*>(&message_type));
+
+
+ return (true);
}
+
+bool
+initModulePart_MessageRenderer(PyObject* mod) {
+ if (PyType_Ready(&messagerenderer_type) < 0) {
+ return (false);
+ }
+ Py_INCREF(&messagerenderer_type);
+
+ addClassVariable(messagerenderer_type, "CASE_INSENSITIVE",
+ Py_BuildValue("I", MessageRenderer::CASE_INSENSITIVE));
+ addClassVariable(messagerenderer_type, "CASE_SENSITIVE",
+ Py_BuildValue("I", MessageRenderer::CASE_SENSITIVE));
+
+ PyModule_AddObject(mod, "MessageRenderer",
+ reinterpret_cast<PyObject*>(&messagerenderer_type));
+
+ return (true);
}
-// order is important here!
-using namespace isc::dns::python;
+bool
+initModulePart_Name(PyObject* mod) {
-#include <dns/python/rrclass_python.cc> // needs Messagerenderer
-#include <dns/python/rrtype_python.cc> // needs Messagerenderer
-#include <dns/python/rrttl_python.cc> // needs Messagerenderer
-#include <dns/python/rdata_python.cc> // needs Type, Class
-#include <dns/python/rrset_python.cc> // needs Rdata, RRTTL
-#include <dns/python/question_python.cc> // needs RRClass, RRType, RRTTL,
- // Name
-#include <dns/python/opcode_python.cc>
-#include <dns/python/edns_python.cc> // needs Messagerenderer, Rcode
-#include <dns/python/message_python.cc> // needs RRset, Question
+ //
+ // NameComparisonResult
+ //
+ if (PyType_Ready(&name_comparison_result_type) < 0) {
+ return (false);
+ }
+ Py_INCREF(&name_comparison_result_type);
+
+ // Add the enums to the module
+ po_NameRelation = Py_BuildValue("{i:s,i:s,i:s,i:s}",
+ NameComparisonResult::SUPERDOMAIN, "SUPERDOMAIN",
+ NameComparisonResult::SUBDOMAIN, "SUBDOMAIN",
+ NameComparisonResult::EQUAL, "EQUAL",
+ NameComparisonResult::COMMONANCESTOR, "COMMONANCESTOR");
+ addClassVariable(name_comparison_result_type, "NameRelation",
+ po_NameRelation);
+
+ PyModule_AddObject(mod, "NameComparisonResult",
+ reinterpret_cast<PyObject*>(&name_comparison_result_type));
+
+ //
+ // Name
+ //
+
+ if (PyType_Ready(&name_type) < 0) {
+ return (false);
+ }
+ Py_INCREF(&name_type);
+
+ // Add the constants to the module
+ addClassVariable(name_type, "MAX_WIRE",
+ Py_BuildValue("I", Name::MAX_WIRE));
+ addClassVariable(name_type, "MAX_LABELS",
+ Py_BuildValue("I", Name::MAX_LABELS));
+ addClassVariable(name_type, "MAX_LABELLEN",
+ Py_BuildValue("I", Name::MAX_LABELLEN));
+ addClassVariable(name_type, "MAX_COMPRESS_POINTER",
+ Py_BuildValue("I", Name::MAX_COMPRESS_POINTER));
+ addClassVariable(name_type, "COMPRESS_POINTER_MARK8",
+ Py_BuildValue("I", Name::COMPRESS_POINTER_MARK8));
+ addClassVariable(name_type, "COMPRESS_POINTER_MARK16",
+ Py_BuildValue("I", Name::COMPRESS_POINTER_MARK16));
+
+ addClassVariable(name_type, "ROOT_NAME",
+ createNameObject(Name::ROOT_NAME()));
+
+ PyModule_AddObject(mod, "Name",
+ reinterpret_cast<PyObject*>(&name_type));
+
+
+ // Add the exceptions to the module
+ po_EmptyLabel = PyErr_NewException("pydnspp.EmptyLabel", NULL, NULL);
+ PyModule_AddObject(mod, "EmptyLabel", po_EmptyLabel);
+
+ po_TooLongName = PyErr_NewException("pydnspp.TooLongName", NULL, NULL);
+ PyModule_AddObject(mod, "TooLongName", po_TooLongName);
+
+ po_TooLongLabel = PyErr_NewException("pydnspp.TooLongLabel", NULL, NULL);
+ PyModule_AddObject(mod, "TooLongLabel", po_TooLongLabel);
+
+ po_BadLabelType = PyErr_NewException("pydnspp.BadLabelType", NULL, NULL);
+ PyModule_AddObject(mod, "BadLabelType", po_BadLabelType);
+
+ po_BadEscape = PyErr_NewException("pydnspp.BadEscape", NULL, NULL);
+ PyModule_AddObject(mod, "BadEscape", po_BadEscape);
+
+ po_IncompleteName = PyErr_NewException("pydnspp.IncompleteName", NULL, NULL);
+ PyModule_AddObject(mod, "IncompleteName", po_IncompleteName);
+
+ po_InvalidBufferPosition =
+ PyErr_NewException("pydnspp.InvalidBufferPosition", NULL, NULL);
+ PyModule_AddObject(mod, "InvalidBufferPosition", po_InvalidBufferPosition);
+
+ // This one could have gone into the message_python.cc file, but is
+ // already needed here.
+ po_DNSMessageFORMERR = PyErr_NewException("pydnspp.DNSMessageFORMERR",
+ NULL, NULL);
+ PyModule_AddObject(mod, "DNSMessageFORMERR", po_DNSMessageFORMERR);
+
+ return (true);
+}
+
+bool
+initModulePart_Opcode(PyObject* mod) {
+ if (PyType_Ready(&opcode_type) < 0) {
+ return (false);
+ }
+ Py_INCREF(&opcode_type);
+ void* p = &opcode_type;
+ if (PyModule_AddObject(mod, "Opcode", static_cast<PyObject*>(p)) != 0) {
+ Py_DECREF(&opcode_type);
+ return (false);
+ }
+
+ addClassVariable(opcode_type, "QUERY_CODE",
+ Py_BuildValue("h", Opcode::QUERY_CODE));
+ addClassVariable(opcode_type, "IQUERY_CODE",
+ Py_BuildValue("h", Opcode::IQUERY_CODE));
+ addClassVariable(opcode_type, "STATUS_CODE",
+ Py_BuildValue("h", Opcode::STATUS_CODE));
+ addClassVariable(opcode_type, "RESERVED3_CODE",
+ Py_BuildValue("h", Opcode::RESERVED3_CODE));
+ addClassVariable(opcode_type, "NOTIFY_CODE",
+ Py_BuildValue("h", Opcode::NOTIFY_CODE));
+ addClassVariable(opcode_type, "UPDATE_CODE",
+ Py_BuildValue("h", Opcode::UPDATE_CODE));
+ addClassVariable(opcode_type, "RESERVED6_CODE",
+ Py_BuildValue("h", Opcode::RESERVED6_CODE));
+ addClassVariable(opcode_type, "RESERVED7_CODE",
+ Py_BuildValue("h", Opcode::RESERVED7_CODE));
+ addClassVariable(opcode_type, "RESERVED8_CODE",
+ Py_BuildValue("h", Opcode::RESERVED8_CODE));
+ addClassVariable(opcode_type, "RESERVED9_CODE",
+ Py_BuildValue("h", Opcode::RESERVED9_CODE));
+ addClassVariable(opcode_type, "RESERVED10_CODE",
+ Py_BuildValue("h", Opcode::RESERVED10_CODE));
+ addClassVariable(opcode_type, "RESERVED11_CODE",
+ Py_BuildValue("h", Opcode::RESERVED11_CODE));
+ addClassVariable(opcode_type, "RESERVED12_CODE",
+ Py_BuildValue("h", Opcode::RESERVED12_CODE));
+ addClassVariable(opcode_type, "RESERVED13_CODE",
+ Py_BuildValue("h", Opcode::RESERVED13_CODE));
+ addClassVariable(opcode_type, "RESERVED14_CODE",
+ Py_BuildValue("h", Opcode::RESERVED14_CODE));
+ addClassVariable(opcode_type, "RESERVED15_CODE",
+ Py_BuildValue("h", Opcode::RESERVED15_CODE));
+
+ return (true);
+}
+
+bool
+initModulePart_Question(PyObject* mod) {
+ if (PyType_Ready(&question_type) < 0) {
+ return (false);
+ }
+ Py_INCREF(&question_type);
+ PyModule_AddObject(mod, "Question",
+ reinterpret_cast<PyObject*>(&question_type));
+
+ return (true);
+}
+
+bool
+initModulePart_Rcode(PyObject* mod) {
+ if (PyType_Ready(&rcode_type) < 0) {
+ return (false);
+ }
+ Py_INCREF(&rcode_type);
+ void* p = &rcode_type;
+ if (PyModule_AddObject(mod, "Rcode", static_cast<PyObject*>(p)) != 0) {
+ Py_DECREF(&rcode_type);
+ return (false);
+ }
+
+ addClassVariable(rcode_type, "NOERROR_CODE",
+ Py_BuildValue("h", Rcode::NOERROR_CODE));
+ addClassVariable(rcode_type, "FORMERR_CODE",
+ Py_BuildValue("h", Rcode::FORMERR_CODE));
+ addClassVariable(rcode_type, "SERVFAIL_CODE",
+ Py_BuildValue("h", Rcode::SERVFAIL_CODE));
+ addClassVariable(rcode_type, "NXDOMAIN_CODE",
+ Py_BuildValue("h", Rcode::NXDOMAIN_CODE));
+ addClassVariable(rcode_type, "NOTIMP_CODE",
+ Py_BuildValue("h", Rcode::NOTIMP_CODE));
+ addClassVariable(rcode_type, "REFUSED_CODE",
+ Py_BuildValue("h", Rcode::REFUSED_CODE));
+ addClassVariable(rcode_type, "YXDOMAIN_CODE",
+ Py_BuildValue("h", Rcode::YXDOMAIN_CODE));
+ addClassVariable(rcode_type, "YXRRSET_CODE",
+ Py_BuildValue("h", Rcode::YXRRSET_CODE));
+ addClassVariable(rcode_type, "NXRRSET_CODE",
+ Py_BuildValue("h", Rcode::NXRRSET_CODE));
+ addClassVariable(rcode_type, "NOTAUTH_CODE",
+ Py_BuildValue("h", Rcode::NOTAUTH_CODE));
+ addClassVariable(rcode_type, "NOTZONE_CODE",
+ Py_BuildValue("h", Rcode::NOTZONE_CODE));
+ addClassVariable(rcode_type, "RESERVED11_CODE",
+ Py_BuildValue("h", Rcode::RESERVED11_CODE));
+ addClassVariable(rcode_type, "RESERVED12_CODE",
+ Py_BuildValue("h", Rcode::RESERVED12_CODE));
+ addClassVariable(rcode_type, "RESERVED13_CODE",
+ Py_BuildValue("h", Rcode::RESERVED13_CODE));
+ addClassVariable(rcode_type, "RESERVED14_CODE",
+ Py_BuildValue("h", Rcode::RESERVED14_CODE));
+ addClassVariable(rcode_type, "RESERVED15_CODE",
+ Py_BuildValue("h", Rcode::RESERVED15_CODE));
+ addClassVariable(rcode_type, "BADVERS_CODE",
+ Py_BuildValue("h", Rcode::BADVERS_CODE));
+
+ return (true);
+}
+
+bool
+initModulePart_Rdata(PyObject* mod) {
+ if (PyType_Ready(&rdata_type) < 0) {
+ return (false);
+ }
+ Py_INCREF(&rdata_type);
+ PyModule_AddObject(mod, "Rdata",
+ reinterpret_cast<PyObject*>(&rdata_type));
+
+ // Add the exceptions to the class
+ po_InvalidRdataLength = PyErr_NewException("pydnspp.InvalidRdataLength",
+ NULL, NULL);
+ PyModule_AddObject(mod, "InvalidRdataLength", po_InvalidRdataLength);
+
+ po_InvalidRdataText = PyErr_NewException("pydnspp.InvalidRdataText",
+ NULL, NULL);
+ PyModule_AddObject(mod, "InvalidRdataText", po_InvalidRdataText);
+
+ po_CharStringTooLong = PyErr_NewException("pydnspp.CharStringTooLong",
+ NULL, NULL);
+ PyModule_AddObject(mod, "CharStringTooLong", po_CharStringTooLong);
+
+
+ return (true);
+}
+
+bool
+initModulePart_RRClass(PyObject* mod) {
+ po_InvalidRRClass = PyErr_NewException("pydnspp.InvalidRRClass",
+ NULL, NULL);
+ Py_INCREF(po_InvalidRRClass);
+ PyModule_AddObject(mod, "InvalidRRClass", po_InvalidRRClass);
+ po_IncompleteRRClass = PyErr_NewException("pydnspp.IncompleteRRClass",
+ NULL, NULL);
+ Py_INCREF(po_IncompleteRRClass);
+ PyModule_AddObject(mod, "IncompleteRRClass", po_IncompleteRRClass);
+
+ if (PyType_Ready(&rrclass_type) < 0) {
+ return (false);
+ }
+ Py_INCREF(&rrclass_type);
+ PyModule_AddObject(mod, "RRClass",
+ reinterpret_cast<PyObject*>(&rrclass_type));
+
+ return (true);
+}
+
+bool
+initModulePart_RRset(PyObject* mod) {
+ po_EmptyRRset = PyErr_NewException("pydnspp.EmptyRRset", NULL, NULL);
+ PyModule_AddObject(mod, "EmptyRRset", po_EmptyRRset);
+
+ // NameComparisonResult
+ if (PyType_Ready(&rrset_type) < 0) {
+ return (false);
+ }
+ Py_INCREF(&rrset_type);
+ PyModule_AddObject(mod, "RRset",
+ reinterpret_cast<PyObject*>(&rrset_type));
+
+ return (true);
+}
+
+bool
+initModulePart_RRTTL(PyObject* mod) {
+ po_InvalidRRTTL = PyErr_NewException("pydnspp.InvalidRRTTL", NULL, NULL);
+ PyModule_AddObject(mod, "InvalidRRTTL", po_InvalidRRTTL);
+ po_IncompleteRRTTL = PyErr_NewException("pydnspp.IncompleteRRTTL",
+ NULL, NULL);
+ PyModule_AddObject(mod, "IncompleteRRTTL", po_IncompleteRRTTL);
+
+ if (PyType_Ready(&rrttl_type) < 0) {
+ return (false);
+ }
+ Py_INCREF(&rrttl_type);
+ PyModule_AddObject(mod, "RRTTL",
+ reinterpret_cast<PyObject*>(&rrttl_type));
+
+ return (true);
+}
+
+bool
+initModulePart_RRType(PyObject* mod) {
+ // Add the exceptions to the module
+ po_InvalidRRType = PyErr_NewException("pydnspp.InvalidRRType", NULL, NULL);
+ PyModule_AddObject(mod, "InvalidRRType", po_InvalidRRType);
+ po_IncompleteRRType = PyErr_NewException("pydnspp.IncompleteRRType",
+ NULL, NULL);
+ PyModule_AddObject(mod, "IncompleteRRType", po_IncompleteRRType);
+
+ if (PyType_Ready(&rrtype_type) < 0) {
+ return (false);
+ }
+ Py_INCREF(&rrtype_type);
+ PyModule_AddObject(mod, "RRType",
+ reinterpret_cast<PyObject*>(&rrtype_type));
+
+ return (true);
+}
+
+bool
+initModulePart_TSIGError(PyObject* mod) {
+ if (PyType_Ready(&tsigerror_type) < 0) {
+ return (false);
+ }
+ void* p = &tsigerror_type;
+ if (PyModule_AddObject(mod, "TSIGError", static_cast<PyObject*>(p)) < 0) {
+ return (false);
+ }
+ Py_INCREF(&tsigerror_type);
+
+ try {
+ // Constant class variables
+ // Error codes (bare values)
+ installClassVariable(tsigerror_type, "BAD_SIG_CODE",
+ Py_BuildValue("H", TSIGError::BAD_SIG_CODE));
+ installClassVariable(tsigerror_type, "BAD_KEY_CODE",
+ Py_BuildValue("H", TSIGError::BAD_KEY_CODE));
+ installClassVariable(tsigerror_type, "BAD_TIME_CODE",
+ Py_BuildValue("H", TSIGError::BAD_TIME_CODE));
+
+ // Error codes (constant objects)
+ installClassVariable(tsigerror_type, "NOERROR",
+ createTSIGErrorObject(TSIGError::NOERROR()));
+ installClassVariable(tsigerror_type, "FORMERR",
+ createTSIGErrorObject(TSIGError::FORMERR()));
+ installClassVariable(tsigerror_type, "SERVFAIL",
+ createTSIGErrorObject(TSIGError::SERVFAIL()));
+ installClassVariable(tsigerror_type, "NXDOMAIN",
+ createTSIGErrorObject(TSIGError::NXDOMAIN()));
+ installClassVariable(tsigerror_type, "NOTIMP",
+ createTSIGErrorObject(TSIGError::NOTIMP()));
+ installClassVariable(tsigerror_type, "REFUSED",
+ createTSIGErrorObject(TSIGError::REFUSED()));
+ installClassVariable(tsigerror_type, "YXDOMAIN",
+ createTSIGErrorObject(TSIGError::YXDOMAIN()));
+ installClassVariable(tsigerror_type, "YXRRSET",
+ createTSIGErrorObject(TSIGError::YXRRSET()));
+ installClassVariable(tsigerror_type, "NXRRSET",
+ createTSIGErrorObject(TSIGError::NXRRSET()));
+ installClassVariable(tsigerror_type, "NOTAUTH",
+ createTSIGErrorObject(TSIGError::NOTAUTH()));
+ installClassVariable(tsigerror_type, "NOTZONE",
+ createTSIGErrorObject(TSIGError::NOTZONE()));
+ installClassVariable(tsigerror_type, "RESERVED11",
+ createTSIGErrorObject(TSIGError::RESERVED11()));
+ installClassVariable(tsigerror_type, "RESERVED12",
+ createTSIGErrorObject(TSIGError::RESERVED12()));
+ installClassVariable(tsigerror_type, "RESERVED13",
+ createTSIGErrorObject(TSIGError::RESERVED13()));
+ installClassVariable(tsigerror_type, "RESERVED14",
+ createTSIGErrorObject(TSIGError::RESERVED14()));
+ installClassVariable(tsigerror_type, "RESERVED15",
+ createTSIGErrorObject(TSIGError::RESERVED15()));
+ installClassVariable(tsigerror_type, "BAD_SIG",
+ createTSIGErrorObject(TSIGError::BAD_SIG()));
+ installClassVariable(tsigerror_type, "BAD_KEY",
+ createTSIGErrorObject(TSIGError::BAD_KEY()));
+ installClassVariable(tsigerror_type, "BAD_TIME",
+ createTSIGErrorObject(TSIGError::BAD_TIME()));
+ } catch (const std::exception& ex) {
+ const std::string ex_what =
+ "Unexpected failure in TSIGError initialization: " +
+ std::string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ return (false);
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError,
+ "Unexpected failure in TSIGError initialization");
+ return (false);
+ }
+
+ return (true);
+}
+
+bool
+initModulePart_TSIGKey(PyObject* mod) {
+ if (PyType_Ready(&tsigkey_type) < 0) {
+ return (false);
+ }
+ void* p = &tsigkey_type;
+ if (PyModule_AddObject(mod, "TSIGKey", static_cast<PyObject*>(p)) != 0) {
+ return (false);
+ }
+ Py_INCREF(&tsigkey_type);
+
+ try {
+ // Constant class variables
+ installClassVariable(tsigkey_type, "HMACMD5_NAME",
+ createNameObject(TSIGKey::HMACMD5_NAME()));
+ installClassVariable(tsigkey_type, "HMACSHA1_NAME",
+ createNameObject(TSIGKey::HMACSHA1_NAME()));
+ installClassVariable(tsigkey_type, "HMACSHA256_NAME",
+ createNameObject(TSIGKey::HMACSHA256_NAME()));
+ installClassVariable(tsigkey_type, "HMACSHA224_NAME",
+ createNameObject(TSIGKey::HMACSHA224_NAME()));
+ installClassVariable(tsigkey_type, "HMACSHA384_NAME",
+ createNameObject(TSIGKey::HMACSHA384_NAME()));
+ installClassVariable(tsigkey_type, "HMACSHA512_NAME",
+ createNameObject(TSIGKey::HMACSHA512_NAME()));
+ } catch (const std::exception& ex) {
+ const std::string ex_what =
+ "Unexpected failure in TSIGKey initialization: " +
+ std::string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ return (false);
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError,
+ "Unexpected failure in TSIGKey initialization");
+ return (false);
+ }
+
+ return (true);
+}
+
+bool
+initModulePart_TSIGKeyRing(PyObject* mod) {
+ if (PyType_Ready(&tsigkeyring_type) < 0) {
+ return (false);
+ }
+ Py_INCREF(&tsigkeyring_type);
+ void* p = &tsigkeyring_type;
+ if (PyModule_AddObject(mod, "TSIGKeyRing",
+ static_cast<PyObject*>(p)) != 0) {
+ Py_DECREF(&tsigkeyring_type);
+ return (false);
+ }
+
+ addClassVariable(tsigkeyring_type, "SUCCESS",
+ Py_BuildValue("I", TSIGKeyRing::SUCCESS));
+ addClassVariable(tsigkeyring_type, "EXIST",
+ Py_BuildValue("I", TSIGKeyRing::EXIST));
+ addClassVariable(tsigkeyring_type, "NOTFOUND",
+ Py_BuildValue("I", TSIGKeyRing::NOTFOUND));
+
+ return (true);
+}
+
+bool
+initModulePart_TSIGContext(PyObject* mod) {
+ if (PyType_Ready(&tsigcontext_type) < 0) {
+ return (false);
+ }
+ void* p = &tsigcontext_type;
+ if (PyModule_AddObject(mod, "TSIGContext",
+ static_cast<PyObject*>(p)) < 0) {
+ return (false);
+ }
+ Py_INCREF(&tsigcontext_type);
+
+ try {
+ // Class specific exceptions
+ po_TSIGContextError = PyErr_NewException("pydnspp.TSIGContextError",
+ po_IscException, NULL);
+ PyObjectContainer(po_TSIGContextError).installToModule(
+ mod, "TSIGContextError");
+
+ // Constant class variables
+ installClassVariable(tsigcontext_type, "STATE_INIT",
+ Py_BuildValue("I", TSIGContext::INIT));
+ installClassVariable(tsigcontext_type, "STATE_SENT_REQUEST",
+ Py_BuildValue("I", TSIGContext::SENT_REQUEST));
+ installClassVariable(tsigcontext_type, "STATE_RECEIVED_REQUEST",
+ Py_BuildValue("I", TSIGContext::RECEIVED_REQUEST));
+ installClassVariable(tsigcontext_type, "STATE_SENT_RESPONSE",
+ Py_BuildValue("I", TSIGContext::SENT_RESPONSE));
+ installClassVariable(tsigcontext_type, "STATE_VERIFIED_RESPONSE",
+ Py_BuildValue("I",
+ TSIGContext::VERIFIED_RESPONSE));
+
+ installClassVariable(tsigcontext_type, "DEFAULT_FUDGE",
+ Py_BuildValue("H", TSIGContext::DEFAULT_FUDGE));
+ } catch (const std::exception& ex) {
+ const std::string ex_what =
+ "Unexpected failure in TSIGContext initialization: " +
+ std::string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ return (false);
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError,
+ "Unexpected failure in TSIGContext initialization");
+ return (false);
+ }
+
+ return (true);
+}
+
+bool
+initModulePart_TSIG(PyObject* mod) {
+ if (PyType_Ready(&tsig_type) < 0) {
+ return (false);
+ }
+ void* p = &tsig_type;
+ if (PyModule_AddObject(mod, "TSIG", static_cast<PyObject*>(p)) < 0) {
+ return (false);
+ }
+ Py_INCREF(&tsig_type);
+
+ return (true);
+}
+
+bool
+initModulePart_TSIGRecord(PyObject* mod) {
+ if (PyType_Ready(&tsigrecord_type) < 0) {
+ return (false);
+ }
+ void* p = &tsigrecord_type;
+ if (PyModule_AddObject(mod, "TSIGRecord", static_cast<PyObject*>(p)) < 0) {
+ return (false);
+ }
+ Py_INCREF(&tsigrecord_type);
+
+ try {
+ // Constant class variables
+ installClassVariable(tsigrecord_type, "TSIG_TTL",
+ Py_BuildValue("I", 0));
+ } catch (const std::exception& ex) {
+ const std::string ex_what =
+ "Unexpected failure in TSIGRecord initialization: " +
+ std::string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ return (false);
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError,
+ "Unexpected failure in TSIGRecord initialization");
+ return (false);
+ }
+
+ return (true);
+}
-//
-// Definition of the module
-//
-namespace {
PyModuleDef pydnspp = {
{ PyObject_HEAD_INIT(NULL) NULL, 0, NULL},
"pydnspp",
diff --git a/src/lib/dns/python/pydnspp_common.cc b/src/lib/dns/python/pydnspp_common.cc
index 8ca763a..0f0f873 100644
--- a/src/lib/dns/python/pydnspp_common.cc
+++ b/src/lib/dns/python/pydnspp_common.cc
@@ -15,9 +15,45 @@
#include <Python.h>
#include <pydnspp_common.h>
+#include <exceptions/exceptions.h>
+
+#include <util/buffer.h>
+
+#include <dns/exceptions.h>
+#include <dns/name.h>
+#include <dns/messagerenderer.h>
+
+#include "pydnspp_common.h"
+#include "messagerenderer_python.h"
+#include "name_python.h"
+#include "rdata_python.h"
+#include "rrclass_python.h"
+#include "rrtype_python.h"
+#include "rrttl_python.h"
+#include "rrset_python.h"
+#include "rcode_python.h"
+#include "opcode_python.h"
+#include "tsigkey_python.h"
+#include "tsig_rdata_python.h"
+#include "tsigerror_python.h"
+#include "tsigrecord_python.h"
+#include "tsig_python.h"
+#include "question_python.h"
+#include "message_python.h"
+
+using namespace isc::dns::python;
+
namespace isc {
namespace dns {
namespace python {
+// For our 'general' isc::Exceptions
+PyObject* po_IscException;
+PyObject* po_InvalidParameter;
+
+// For our own isc::dns::Exception
+PyObject* po_DNSMessageBADVERS;
+
+
int
readDataFromSequence(uint8_t *data, size_t len, PyObject* sequence) {
PyObject* el = NULL;
diff --git a/src/lib/dns/python/pydnspp_common.h b/src/lib/dns/python/pydnspp_common.h
index ed90998..8092b08 100644
--- a/src/lib/dns/python/pydnspp_common.h
+++ b/src/lib/dns/python/pydnspp_common.h
@@ -20,8 +20,6 @@
#include <stdexcept>
#include <string>
-#include <util/python/pycppwrapper_util.h>
-
namespace isc {
namespace dns {
namespace python {
diff --git a/src/lib/dns/python/pydnspp_towire.h b/src/lib/dns/python/pydnspp_towire.h
index 66362a0..e987a29 100644
--- a/src/lib/dns/python/pydnspp_towire.h
+++ b/src/lib/dns/python/pydnspp_towire.h
@@ -93,10 +93,10 @@ toWireWrapper(const PYSTRUCT* const self, PyObject* args) {
}
// To MessageRenderer version
- s_MessageRenderer* renderer;
+ PyObject* renderer;
if (PyArg_ParseTuple(args, "O!", &messagerenderer_type, &renderer)) {
const unsigned int n = TOWIRECALLER(*self->cppobj)(
- *renderer->messagerenderer);
+ PyMessageRenderer_ToMessageRenderer(renderer));
return (Py_BuildValue("I", n));
}
diff --git a/src/lib/dns/python/question_python.cc b/src/lib/dns/python/question_python.cc
index c702f85..44d68a2 100644
--- a/src/lib/dns/python/question_python.cc
+++ b/src/lib/dns/python/question_python.cc
@@ -12,25 +12,34 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
+#define PY_SSIZE_T_CLEAN
+#include <Python.h>
#include <dns/question.h>
+#include <dns/messagerenderer.h>
+#include <dns/exceptions.h>
+#include <util/buffer.h>
+#include <util/python/pycppwrapper_util.h>
+
+#include "pydnspp_common.h"
+#include "question_python.h"
+#include "name_python.h"
+#include "rrclass_python.h"
+#include "rrtype_python.h"
+#include "messagerenderer_python.h"
+
+using namespace std;
using namespace isc::dns;
+using namespace isc::dns::python;
+using namespace isc::util;
+using namespace isc::util::python;
+using namespace isc;
-//
-// Question
-//
-
-// The s_* Class simply coverst one instantiation of the object
+namespace {
class s_Question : public PyObject {
public:
- QuestionPtr question;
+ isc::dns::QuestionPtr cppobj;
};
-//
-// We declare the functions here, the definitions are below
-// the type definition of the object, since both can use the other
-//
-
-// General creation and destruction
static int Question_init(s_Question* self, PyObject* args);
static void Question_destroy(s_Question* self);
@@ -69,60 +78,6 @@ static PyMethodDef Question_methods[] = {
{ NULL, NULL, 0, NULL }
};
-// This defines the complete type for reflection in python and
-// parsing of PyObject* to s_Question
-// Most of the functions are not actually implemented and NULL here.
-static PyTypeObject question_type = {
- PyVarObject_HEAD_INIT(NULL, 0)
- "pydnspp.Question",
- sizeof(s_Question), // tp_basicsize
- 0, // tp_itemsize
- (destructor)Question_destroy, // tp_dealloc
- NULL, // tp_print
- NULL, // tp_getattr
- NULL, // tp_setattr
- NULL, // tp_reserved
- NULL, // tp_repr
- NULL, // tp_as_number
- NULL, // tp_as_sequence
- NULL, // tp_as_mapping
- NULL, // tp_hash
- NULL, // tp_call
- Question_str, // tp_str
- NULL, // tp_getattro
- NULL, // tp_setattro
- NULL, // tp_as_buffer
- Py_TPFLAGS_DEFAULT, // tp_flags
- "The Question class encapsulates the common search key of DNS"
- "lookup, consisting of owner name, RR type and RR class.",
- NULL, // tp_traverse
- NULL, // tp_clear
- NULL, // tp_richcompare
- 0, // tp_weaklistoffset
- NULL, // tp_iter
- NULL, // tp_iternext
- Question_methods, // tp_methods
- NULL, // tp_members
- NULL, // tp_getset
- NULL, // tp_base
- NULL, // tp_dict
- NULL, // tp_descr_get
- NULL, // tp_descr_set
- 0, // tp_dictoffset
- (initproc)Question_init, // tp_init
- NULL, // tp_alloc
- PyType_GenericNew, // tp_new
- NULL, // tp_free
- NULL, // tp_is_gc
- NULL, // tp_bases
- NULL, // tp_mro
- NULL, // tp_cache
- NULL, // tp_subclasses
- NULL, // tp_weaklist
- NULL, // tp_del
- 0 // tp_version_tag
-};
-
static int
Question_init(s_Question* self, PyObject* args) {
// Try out the various combinations of arguments to call the
@@ -131,9 +86,9 @@ Question_init(s_Question* self, PyObject* args) {
// that if we try several like here. Otherwise the *next* python
// call will suddenly appear to throw an exception.
// (the way to do exceptions is to set PyErr and return -1)
- s_Name* name;
- s_RRClass* rrclass;
- s_RRType* rrtype;
+ PyObject* name;
+ PyObject* rrclass;
+ PyObject* rrtype;
const char* b;
Py_ssize_t len;
@@ -141,17 +96,18 @@ Question_init(s_Question* self, PyObject* args) {
try {
if (PyArg_ParseTuple(args, "O!O!O!", &name_type, &name,
- &rrclass_type, &rrclass,
- &rrtype_type, &rrtype
+ &rrclass_type, &rrclass,
+ &rrtype_type, &rrtype
)) {
- self->question = QuestionPtr(new Question(*name->cppobj, *rrclass->rrclass,
- *rrtype->rrtype));
+ self->cppobj = QuestionPtr(new Question(PyName_ToName(name),
+ PyRRClass_ToRRClass(rrclass),
+ PyRRType_ToRRType(rrtype)));
return (0);
} else if (PyArg_ParseTuple(args, "y#|I", &b, &len, &position)) {
PyErr_Clear();
InputBuffer inbuf(b, len);
inbuf.setPosition(position);
- self->question = QuestionPtr(new Question(inbuf));
+ self->cppobj = QuestionPtr(new Question(inbuf));
return (0);
}
} catch (const DNSMessageFORMERR& dmfe) {
@@ -168,7 +124,7 @@ Question_init(s_Question* self, PyObject* args) {
return (-1);
}
- self->question = QuestionPtr();
+ self->cppobj = QuestionPtr();
PyErr_Clear();
PyErr_SetString(PyExc_TypeError,
@@ -178,52 +134,62 @@ Question_init(s_Question* self, PyObject* args) {
static void
Question_destroy(s_Question* self) {
- self->question.reset();
+ self->cppobj.reset();
Py_TYPE(self)->tp_free(self);
}
static PyObject*
Question_getName(s_Question* self) {
- s_Name* name;
-
- // is this the best way to do this?
- name = static_cast<s_Name*>(name_type.tp_alloc(&name_type, 0));
- if (name != NULL) {
- name->cppobj = new Name(self->question->getName());
+ try {
+ return (createNameObject(self->cppobj->getName()));
+ } catch (const exception& ex) {
+ const string ex_what =
+ "Unexpected failure getting question Name: " +
+ string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError,
+ "Unexpected failure getting question Name");
}
-
- return (name);
+ return (NULL);
}
static PyObject*
Question_getType(s_Question* self) {
- s_RRType* rrtype;
-
- rrtype = static_cast<s_RRType*>(rrtype_type.tp_alloc(&rrtype_type, 0));
- if (rrtype != NULL) {
- rrtype->rrtype = new RRType(self->question->getType());
+ try {
+ return (createRRTypeObject(self->cppobj->getType()));
+ } catch (const exception& ex) {
+ const string ex_what =
+ "Unexpected failure getting question RRType: " +
+ string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError,
+ "Unexpected failure getting question RRType");
}
-
- return (rrtype);
+ return (NULL);
}
static PyObject*
Question_getClass(s_Question* self) {
- s_RRClass* rrclass;
-
- rrclass = static_cast<s_RRClass*>(rrclass_type.tp_alloc(&rrclass_type, 0));
- if (rrclass != NULL) {
- rrclass->rrclass = new RRClass(self->question->getClass());
+ try {
+ return (createRRClassObject(self->cppobj->getClass()));
+ } catch (const exception& ex) {
+ const string ex_what =
+ "Unexpected failure getting question RRClass: " +
+ string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError,
+ "Unexpected failure getting question RRClass");
}
-
- return (rrclass);
+ return (NULL);
}
-
static PyObject*
Question_toText(s_Question* self) {
// Py_BuildValue makes python objects from native data
- return (Py_BuildValue("s", self->question->toText().c_str()));
+ return (Py_BuildValue("s", self->cppobj->toText().c_str()));
}
static PyObject*
@@ -237,14 +203,14 @@ Question_str(PyObject* self) {
static PyObject*
Question_toWire(s_Question* self, PyObject* args) {
PyObject* bytes;
- s_MessageRenderer* mr;
-
+ PyObject* mr;
+
if (PyArg_ParseTuple(args, "O", &bytes) && PySequence_Check(bytes)) {
PyObject* bytes_o = bytes;
// Max length is Name::MAX_WIRE + rrclass (2) + rrtype (2)
OutputBuffer buffer(Name::MAX_WIRE + 4);
- self->question->toWire(buffer);
+ self->cppobj->toWire(buffer);
PyObject* n = PyBytes_FromStringAndSize(static_cast<const char*>(buffer.getData()),
buffer.getLength());
PyObject* result = PySequence_InPlaceConcat(bytes_o, n);
@@ -253,7 +219,7 @@ Question_toWire(s_Question* self, PyObject* args) {
Py_DECREF(n);
return (result);
} else if (PyArg_ParseTuple(args, "O!", &messagerenderer_type, &mr)) {
- self->question->toWire(*mr->messagerenderer);
+ self->cppobj->toWire(PyMessageRenderer_ToMessageRenderer(mr));
// If we return NULL it is seen as an error, so use this for
// None returns
Py_RETURN_NONE;
@@ -264,23 +230,92 @@ Question_toWire(s_Question* self, PyObject* args) {
return (NULL);
}
-// end of Question
+} // end of unnamed namespace
+
+namespace isc {
+namespace dns {
+namespace python {
+
+// This defines the complete type for reflection in python and
+// parsing of PyObject* to s_Question
+// Most of the functions are not actually implemented and NULL here.
+PyTypeObject question_type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "pydnspp.Question",
+ sizeof(s_Question), // tp_basicsize
+ 0, // tp_itemsize
+ (destructor)Question_destroy, // tp_dealloc
+ NULL, // tp_print
+ NULL, // tp_getattr
+ NULL, // tp_setattr
+ NULL, // tp_reserved
+ NULL, // tp_repr
+ NULL, // tp_as_number
+ NULL, // tp_as_sequence
+ NULL, // tp_as_mapping
+ NULL, // tp_hash
+ NULL, // tp_call
+ Question_str, // tp_str
+ NULL, // tp_getattro
+ NULL, // tp_setattro
+ NULL, // tp_as_buffer
+ Py_TPFLAGS_DEFAULT, // tp_flags
+ "The Question class encapsulates the common search key of DNS"
+ "lookup, consisting of owner name, RR type and RR class.",
+ NULL, // tp_traverse
+ NULL, // tp_clear
+ NULL, // tp_richcompare
+ 0, // tp_weaklistoffset
+ NULL, // tp_iter
+ NULL, // tp_iternext
+ Question_methods, // tp_methods
+ NULL, // tp_members
+ NULL, // tp_getset
+ NULL, // tp_base
+ NULL, // tp_dict
+ NULL, // tp_descr_get
+ NULL, // tp_descr_set
+ 0, // tp_dictoffset
+ (initproc)Question_init, // tp_init
+ NULL, // tp_alloc
+ PyType_GenericNew, // tp_new
+ NULL, // tp_free
+ NULL, // tp_is_gc
+ NULL, // tp_bases
+ NULL, // tp_mro
+ NULL, // tp_cache
+ NULL, // tp_subclasses
+ NULL, // tp_weaklist
+ NULL, // tp_del
+ 0 // tp_version_tag
+};
+PyObject*
+createQuestionObject(const Question& source) {
+ s_Question* question =
+ static_cast<s_Question*>(question_type.tp_alloc(&question_type, 0));
+ question->cppobj = QuestionPtr(new Question(source));
+ return (question);
+}
-// Module Initialization, all statics are initialized here
bool
-initModulePart_Question(PyObject* mod) {
- // Add the exceptions to the module
+PyQuestion_Check(PyObject* obj) {
+ if (obj == NULL) {
+ isc_throw(PyCPPWrapperException, "obj argument NULL in typecheck");
+ }
+ return (PyObject_TypeCheck(obj, &question_type));
+}
- // We initialize the static description object with PyType_Ready(),
- // then add it to the module. This is not just a check! (leaving
- // this out results in segmentation faults)
- if (PyType_Ready(&question_type) < 0) {
- return (false);
+const Question&
+PyQuestion_ToQuestion(const PyObject* question_obj) {
+ if (question_obj == NULL) {
+ isc_throw(PyCPPWrapperException,
+ "obj argument NULL in Question PyObject conversion");
}
- Py_INCREF(&question_type);
- PyModule_AddObject(mod, "Question",
- reinterpret_cast<PyObject*>(&question_type));
-
- return (true);
+ const s_Question* question = static_cast<const s_Question*>(question_obj);
+ return (*question->cppobj);
}
+
+} // end python namespace
+} // end dns namespace
+} // end isc namespace
diff --git a/src/lib/dns/python/question_python.h b/src/lib/dns/python/question_python.h
new file mode 100644
index 0000000..f5d78b1
--- /dev/null
+++ b/src/lib/dns/python/question_python.h
@@ -0,0 +1,66 @@
+// 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.
+
+#ifndef __PYTHON_QUESTION_H
+#define __PYTHON_QUESTION_H 1
+
+#include <Python.h>
+
+namespace isc {
+namespace dns {
+class Question;
+
+namespace python {
+
+extern PyObject* po_EmptyQuestion;
+
+extern PyTypeObject question_type;
+
+/// This is a simple shortcut to create a python Question object (in the
+/// form of a pointer to PyObject) with minimal exception safety.
+/// On success, it returns a valid pointer to PyObject with a reference
+/// counter of 1; if something goes wrong it throws an exception (it never
+/// returns a NULL pointer).
+/// This function is expected to be called within a try block
+/// followed by necessary setup for python exception.
+PyObject* createQuestionObject(const Question& source);
+
+/// \brief Checks if the given python object is a Question object
+///
+/// \exception PyCPPWrapperException if obj is NULL
+///
+/// \param obj The object to check the type of
+/// \return true if the object is of type Question, false otherwise
+bool PyQuestion_Check(PyObject* obj);
+
+/// \brief Returns a reference to the Question object contained within the given
+/// Python object.
+///
+/// \note The given object MUST be of type Question; this can be checked with
+/// either the right call to ParseTuple("O!"), or with PyQuestion_Check()
+///
+/// \note This is not a copy; if the Question is needed when the PyObject
+/// may be destroyed, the caller must copy it itself.
+///
+/// \param question_obj The question object to convert
+const Question& PyQuestion_ToQuestion(const PyObject* question_obj);
+
+} // namespace python
+} // namespace dns
+} // namespace isc
+#endif // __PYTHON_QUESTION_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/python/rcode_python.cc b/src/lib/dns/python/rcode_python.cc
index b594ad3..42b48e7 100644
--- a/src/lib/dns/python/rcode_python.cc
+++ b/src/lib/dns/python/rcode_python.cc
@@ -15,34 +15,39 @@
#include <Python.h>
#include <exceptions/exceptions.h>
-
#include <dns/rcode.h>
+#include <util/python/pycppwrapper_util.h>
#include "pydnspp_common.h"
#include "rcode_python.h"
using namespace isc::dns;
using namespace isc::dns::python;
+using namespace isc::util::python;
+namespace {
+// The s_* Class simply covers one instantiation of the object.
//
-// Declaration of the custom exceptions (None for this class)
-
-//
-// Definition of the classes
-//
-
-// For each class, we need a struct, a helper functions (init, destroy,
-// and static wrappers around the methods we export), a list of methods,
-// and a type description
-
-//
-// Rcode
+// We added a helper variable static_code here
+// Since we can create Rcodes dynamically with Rcode(int), but also
+// use the static globals (Rcode::NOERROR() etc), we use this
+// variable to see if the code came from one of the latter, in which
+// case Rcode_destroy should not free it (the other option is to
+// allocate new Rcodes for every use of the static ones, but this
+// seems more efficient).
//
+// Follow-up note: we don't have to use the proxy function in the python lib;
+// we can just define class specific constants directly (see TSIGError).
+// We should make this cleanup later.
+class s_Rcode : public PyObject {
+public:
+ s_Rcode() : cppobj(NULL), static_code(false) {};
+ const Rcode* cppobj;
+ bool static_code;
+};
-// Trivial constructor.
-s_Rcode::s_Rcode() : cppobj(NULL), static_code(false) {}
+typedef CPPPyObjectContainer<s_Rcode, Rcode> RcodeContainer;
-namespace {
int Rcode_init(s_Rcode* const self, PyObject* args);
void Rcode_destroy(s_Rcode* const self);
@@ -282,7 +287,7 @@ Rcode_BADVERS(const s_Rcode*) {
return (Rcode_createStatic(Rcode::BADVERS()));
}
-PyObject*
+PyObject*
Rcode_richcmp(const s_Rcode* const self, const s_Rcode* const other,
const int op)
{
@@ -376,59 +381,31 @@ PyTypeObject rcode_type = {
0 // tp_version_tag
};
-// Module Initialization, all statics are initialized here
+PyObject*
+createRcodeObject(const Rcode& source) {
+ RcodeContainer container(PyObject_New(s_Rcode, &rcode_type));
+ container.set(new Rcode(source));
+ return (container.release());
+}
+
bool
-initModulePart_Rcode(PyObject* mod) {
- // We initialize the static description object with PyType_Ready(),
- // then add it to the module. This is not just a check! (leaving
- // this out results in segmentation faults)
- if (PyType_Ready(&rcode_type) < 0) {
- return (false);
- }
- Py_INCREF(&rcode_type);
- void* p = &rcode_type;
- if (PyModule_AddObject(mod, "Rcode", static_cast<PyObject*>(p)) != 0) {
- Py_DECREF(&rcode_type);
- return (false);
+PyRcode_Check(PyObject* obj) {
+ if (obj == NULL) {
+ isc_throw(PyCPPWrapperException, "obj argument NULL in typecheck");
}
+ return (PyObject_TypeCheck(obj, &rcode_type));
+}
- addClassVariable(rcode_type, "NOERROR_CODE",
- Py_BuildValue("h", Rcode::NOERROR_CODE));
- addClassVariable(rcode_type, "FORMERR_CODE",
- Py_BuildValue("h", Rcode::FORMERR_CODE));
- addClassVariable(rcode_type, "SERVFAIL_CODE",
- Py_BuildValue("h", Rcode::SERVFAIL_CODE));
- addClassVariable(rcode_type, "NXDOMAIN_CODE",
- Py_BuildValue("h", Rcode::NXDOMAIN_CODE));
- addClassVariable(rcode_type, "NOTIMP_CODE",
- Py_BuildValue("h", Rcode::NOTIMP_CODE));
- addClassVariable(rcode_type, "REFUSED_CODE",
- Py_BuildValue("h", Rcode::REFUSED_CODE));
- addClassVariable(rcode_type, "YXDOMAIN_CODE",
- Py_BuildValue("h", Rcode::YXDOMAIN_CODE));
- addClassVariable(rcode_type, "YXRRSET_CODE",
- Py_BuildValue("h", Rcode::YXRRSET_CODE));
- addClassVariable(rcode_type, "NXRRSET_CODE",
- Py_BuildValue("h", Rcode::NXRRSET_CODE));
- addClassVariable(rcode_type, "NOTAUTH_CODE",
- Py_BuildValue("h", Rcode::NOTAUTH_CODE));
- addClassVariable(rcode_type, "NOTZONE_CODE",
- Py_BuildValue("h", Rcode::NOTZONE_CODE));
- addClassVariable(rcode_type, "RESERVED11_CODE",
- Py_BuildValue("h", Rcode::RESERVED11_CODE));
- addClassVariable(rcode_type, "RESERVED12_CODE",
- Py_BuildValue("h", Rcode::RESERVED12_CODE));
- addClassVariable(rcode_type, "RESERVED13_CODE",
- Py_BuildValue("h", Rcode::RESERVED13_CODE));
- addClassVariable(rcode_type, "RESERVED14_CODE",
- Py_BuildValue("h", Rcode::RESERVED14_CODE));
- addClassVariable(rcode_type, "RESERVED15_CODE",
- Py_BuildValue("h", Rcode::RESERVED15_CODE));
- addClassVariable(rcode_type, "BADVERS_CODE",
- Py_BuildValue("h", Rcode::BADVERS_CODE));
-
- return (true);
+const Rcode&
+PyRcode_ToRcode(const PyObject* rcode_obj) {
+ if (rcode_obj == NULL) {
+ isc_throw(PyCPPWrapperException,
+ "obj argument NULL in Rcode PyObject conversion");
+ }
+ const s_Rcode* rcode = static_cast<const s_Rcode*>(rcode_obj);
+ return (*rcode->cppobj);
}
+
} // namespace python
} // namespace dns
} // namespace isc
diff --git a/src/lib/dns/python/rcode_python.h b/src/lib/dns/python/rcode_python.h
index 9b5e699..a149406 100644
--- a/src/lib/dns/python/rcode_python.h
+++ b/src/lib/dns/python/rcode_python.h
@@ -23,29 +23,36 @@ class Rcode;
namespace python {
-// The s_* Class simply covers one instantiation of the object.
-//
-// We added a helper variable static_code here
-// Since we can create Rcodes dynamically with Rcode(int), but also
-// use the static globals (Rcode::NOERROR() etc), we use this
-// variable to see if the code came from one of the latter, in which
-// case Rcode_destroy should not free it (the other option is to
-// allocate new Rcodes for every use of the static ones, but this
-// seems more efficient).
-//
-// Follow-up note: we don't have to use the proxy function in the python lib;
-// we can just define class specific constants directly (see TSIGError).
-// We should make this cleanup later.
-class s_Rcode : public PyObject {
-public:
- s_Rcode();
- const Rcode* cppobj;
- bool static_code;
-};
-
extern PyTypeObject rcode_type;
-bool initModulePart_Rcode(PyObject* mod);
+/// This is a simple shortcut to create a python Rcode object (in the
+/// form of a pointer to PyObject) with minimal exception safety.
+/// On success, it returns a valid pointer to PyObject with a reference
+/// counter of 1; if something goes wrong it throws an exception (it never
+/// returns a NULL pointer).
+/// This function is expected to be called within a try block
+/// followed by necessary setup for python exception.
+PyObject* createRcodeObject(const Rcode& source);
+
+/// \brief Checks if the given python object is a Rcode object
+///
+/// \exception PyCPPWrapperException if obj is NULL
+///
+/// \param obj The object to check the type of
+/// \return true if the object is of type Rcode, false otherwise
+bool PyRcode_Check(PyObject* obj);
+
+/// \brief Returns a reference to the Rcode object contained within the given
+/// Python object.
+///
+/// \note The given object MUST be of type Rcode; this can be checked with
+/// either the right call to ParseTuple("O!"), or with PyRcode_Check()
+///
+/// \note This is not a copy; if the Rcode is needed when the PyObject
+/// may be destroyed, the caller must copy it itself.
+///
+/// \param rcode_obj The rcode object to convert
+const Rcode& PyRcode_ToRcode(const PyObject* rcode_obj);
} // namespace python
} // namespace dns
diff --git a/src/lib/dns/python/rdata_python.cc b/src/lib/dns/python/rdata_python.cc
index faa4f4c..06c0263 100644
--- a/src/lib/dns/python/rdata_python.cc
+++ b/src/lib/dns/python/rdata_python.cc
@@ -12,60 +12,48 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
+#define PY_SSIZE_T_CLEAN
+#include <Python.h>
#include <dns/rdata.h>
+#include <dns/messagerenderer.h>
+#include <util/buffer.h>
+#include <util/python/pycppwrapper_util.h>
+
+#include "rdata_python.h"
+#include "rrtype_python.h"
+#include "rrclass_python.h"
+#include "messagerenderer_python.h"
+
using namespace isc::dns;
+using namespace isc::dns::python;
using namespace isc::util;
+using namespace isc::util::python;
using namespace isc::dns::rdata;
-//
-// Declaration of the custom exceptions
-// Initialization and addition of these go in the initModulePart
-// function at the end of this file
-//
-static PyObject* po_InvalidRdataLength;
-static PyObject* po_InvalidRdataText;
-static PyObject* po_CharStringTooLong;
-
-//
-// Definition of the classes
-//
-
-// For each class, we need a struct, a helper functions (init, destroy,
-// and static wrappers around the methods we export), a list of methods,
-// and a type description
-
-//
-// Rdata
-//
-
-// The s_* Class simply coverst one instantiation of the object
-
-// Using a shared_ptr here should not really be necessary (PyObject
-// is already reference-counted), however internally on the cpp side,
-// not doing so might result in problems, since we can't copy construct
-// rdata field, adding them to rrsets results in a problem when the
-// rrset is destroyed later
+namespace {
class s_Rdata : public PyObject {
public:
- RdataPtr rdata;
+ isc::dns::rdata::ConstRdataPtr cppobj;
};
+typedef CPPPyObjectContainer<s_Rdata, Rdata> RdataContainer;
+
//
// We declare the functions here, the definitions are below
// the type definition of the object, since both can use the other
//
// General creation and destruction
-static int Rdata_init(s_Rdata* self, PyObject* args);
-static void Rdata_destroy(s_Rdata* self);
+int Rdata_init(s_Rdata* self, PyObject* args);
+void Rdata_destroy(s_Rdata* self);
// These are the functions we export
-static PyObject* Rdata_toText(s_Rdata* self);
+PyObject* Rdata_toText(s_Rdata* self);
// This is a second version of toText, we need one where the argument
// is a PyObject*, for the str() function in python.
-static PyObject* Rdata_str(PyObject* self);
-static PyObject* Rdata_toWire(s_Rdata* self, PyObject* args);
-static PyObject* RData_richcmp(s_Rdata* self, s_Rdata* other, int op);
+PyObject* Rdata_str(PyObject* self);
+PyObject* Rdata_toWire(s_Rdata* self, PyObject* args);
+PyObject* RData_richcmp(s_Rdata* self, s_Rdata* other, int op);
// This list contains the actual set of functions we have in
// python. Each entry has
@@ -73,7 +61,7 @@ static PyObject* RData_richcmp(s_Rdata* self, s_Rdata* other, int op);
// 2. Our static function here
// 3. Argument type
// 4. Documentation
-static PyMethodDef Rdata_methods[] = {
+PyMethodDef Rdata_methods[] = {
{ "to_text", reinterpret_cast<PyCFunction>(Rdata_toText), METH_NOARGS,
"Returns the string representation" },
{ "to_wire", reinterpret_cast<PyCFunction>(Rdata_toWire), METH_VARARGS,
@@ -86,64 +74,10 @@ static PyMethodDef Rdata_methods[] = {
{ NULL, NULL, 0, NULL }
};
-// This defines the complete type for reflection in python and
-// parsing of PyObject* to s_Rdata
-// Most of the functions are not actually implemented and NULL here.
-static PyTypeObject rdata_type = {
- PyVarObject_HEAD_INIT(NULL, 0)
- "pydnspp.Rdata",
- sizeof(s_Rdata), // tp_basicsize
- 0, // tp_itemsize
- (destructor)Rdata_destroy, // tp_dealloc
- NULL, // tp_print
- NULL, // tp_getattr
- NULL, // tp_setattr
- NULL, // tp_reserved
- NULL, // tp_repr
- NULL, // tp_as_number
- NULL, // tp_as_sequence
- NULL, // tp_as_mapping
- NULL, // tp_hash
- NULL, // tp_call
- Rdata_str, // tp_str
- NULL, // tp_getattro
- NULL, // tp_setattro
- NULL, // tp_as_buffer
- Py_TPFLAGS_DEFAULT, // tp_flags
- "The Rdata class is an abstract base class that provides "
- "a set of common interfaces to manipulate concrete RDATA objects.",
- NULL, // tp_traverse
- NULL, // tp_clear
- (richcmpfunc)RData_richcmp, // tp_richcompare
- 0, // tp_weaklistoffset
- NULL, // tp_iter
- NULL, // tp_iternext
- Rdata_methods, // tp_methods
- NULL, // tp_members
- NULL, // tp_getset
- NULL, // tp_base
- NULL, // tp_dict
- NULL, // tp_descr_get
- NULL, // tp_descr_set
- 0, // tp_dictoffset
- (initproc)Rdata_init, // tp_init
- NULL, // tp_alloc
- PyType_GenericNew, // tp_new
- NULL, // tp_free
- NULL, // tp_is_gc
- NULL, // tp_bases
- NULL, // tp_mro
- NULL, // tp_cache
- NULL, // tp_subclasses
- NULL, // tp_weaklist
- NULL, // tp_del
- 0 // tp_version_tag
-};
-
-static int
+int
Rdata_init(s_Rdata* self, PyObject* args) {
- s_RRType* rrtype;
- s_RRClass* rrclass;
+ PyObject* rrtype;
+ PyObject* rrclass;
const char* s;
const char* data;
Py_ssize_t len;
@@ -152,34 +86,36 @@ Rdata_init(s_Rdata* self, PyObject* args) {
if (PyArg_ParseTuple(args, "O!O!s", &rrtype_type, &rrtype,
&rrclass_type, &rrclass,
&s)) {
- self->rdata = createRdata(*rrtype->rrtype, *rrclass->rrclass, s);
+ self->cppobj = createRdata(PyRRType_ToRRType(rrtype),
+ PyRRClass_ToRRClass(rrclass), s);
return (0);
} else if (PyArg_ParseTuple(args, "O!O!y#", &rrtype_type, &rrtype,
&rrclass_type, &rrclass, &data, &len)) {
InputBuffer input_buffer(data, len);
- self->rdata = createRdata(*rrtype->rrtype, *rrclass->rrclass,
- input_buffer, len);
+ self->cppobj = createRdata(PyRRType_ToRRType(rrtype),
+ PyRRClass_ToRRClass(rrclass),
+ input_buffer, len);
return (0);
}
return (-1);
}
-static void
+void
Rdata_destroy(s_Rdata* self) {
// Clear the shared_ptr so that its reference count is zero
// before we call tp_free() (there is no direct release())
- self->rdata.reset();
+ self->cppobj.reset();
Py_TYPE(self)->tp_free(self);
}
-static PyObject*
+PyObject*
Rdata_toText(s_Rdata* self) {
// Py_BuildValue makes python objects from native data
- return (Py_BuildValue("s", self->rdata->toText().c_str()));
+ return (Py_BuildValue("s", self->cppobj->toText().c_str()));
}
-static PyObject*
+PyObject*
Rdata_str(PyObject* self) {
// Simply call the to_text method we already defined
return (PyObject_CallMethod(self,
@@ -187,16 +123,16 @@ Rdata_str(PyObject* self) {
const_cast<char*>("")));
}
-static PyObject*
+PyObject*
Rdata_toWire(s_Rdata* self, PyObject* args) {
PyObject* bytes;
- s_MessageRenderer* mr;
-
+ PyObject* mr;
+
if (PyArg_ParseTuple(args, "O", &bytes) && PySequence_Check(bytes)) {
PyObject* bytes_o = bytes;
-
+
OutputBuffer buffer(4);
- self->rdata->toWire(buffer);
+ self->cppobj->toWire(buffer);
PyObject* rd_bytes = PyBytes_FromStringAndSize(static_cast<const char*>(buffer.getData()), buffer.getLength());
PyObject* result = PySequence_InPlaceConcat(bytes_o, rd_bytes);
// We need to release the object we temporarily created here
@@ -204,7 +140,7 @@ Rdata_toWire(s_Rdata* self, PyObject* args) {
Py_DECREF(rd_bytes);
return (result);
} else if (PyArg_ParseTuple(args, "O!", &messagerenderer_type, &mr)) {
- self->rdata->toWire(*mr->messagerenderer);
+ self->cppobj->toWire(PyMessageRenderer_ToMessageRenderer(mr));
// If we return NULL it is seen as an error, so use this for
// None returns
Py_RETURN_NONE;
@@ -215,9 +151,7 @@ Rdata_toWire(s_Rdata* self, PyObject* args) {
return (NULL);
}
-
-
-static PyObject*
+PyObject*
RData_richcmp(s_Rdata* self, s_Rdata* other, int op) {
bool c;
@@ -229,24 +163,24 @@ RData_richcmp(s_Rdata* self, s_Rdata* other, int op) {
switch (op) {
case Py_LT:
- c = self->rdata->compare(*other->rdata) < 0;
+ c = self->cppobj->compare(*other->cppobj) < 0;
break;
case Py_LE:
- c = self->rdata->compare(*other->rdata) < 0 ||
- self->rdata->compare(*other->rdata) == 0;
+ c = self->cppobj->compare(*other->cppobj) < 0 ||
+ self->cppobj->compare(*other->cppobj) == 0;
break;
case Py_EQ:
- c = self->rdata->compare(*other->rdata) == 0;
+ c = self->cppobj->compare(*other->cppobj) == 0;
break;
case Py_NE:
- c = self->rdata->compare(*other->rdata) != 0;
+ c = self->cppobj->compare(*other->cppobj) != 0;
break;
case Py_GT:
- c = self->rdata->compare(*other->rdata) > 0;
+ c = self->cppobj->compare(*other->cppobj) > 0;
break;
case Py_GE:
- c = self->rdata->compare(*other->rdata) > 0 ||
- self->rdata->compare(*other->rdata) == 0;
+ c = self->cppobj->compare(*other->cppobj) > 0 ||
+ self->cppobj->compare(*other->cppobj) == 0;
break;
default:
PyErr_SetString(PyExc_IndexError,
@@ -258,32 +192,107 @@ RData_richcmp(s_Rdata* self, s_Rdata* other, int op) {
else
Py_RETURN_FALSE;
}
-// end of Rdata
+} // end of unnamed namespace
-// Module Initialization, all statics are initialized here
-bool
-initModulePart_Rdata(PyObject* mod) {
- // We initialize the static description object with PyType_Ready(),
- // then add it to the module. This is not just a check! (leaving
- // this out results in segmentation faults)
- if (PyType_Ready(&rdata_type) < 0) {
- return (false);
- }
- Py_INCREF(&rdata_type);
- PyModule_AddObject(mod, "Rdata",
- reinterpret_cast<PyObject*>(&rdata_type));
+namespace isc {
+namespace dns {
+namespace python {
- // Add the exceptions to the class
- po_InvalidRdataLength = PyErr_NewException("pydnspp.InvalidRdataLength", NULL, NULL);
- PyModule_AddObject(mod, "InvalidRdataLength", po_InvalidRdataLength);
- po_InvalidRdataText = PyErr_NewException("pydnspp.InvalidRdataText", NULL, NULL);
- PyModule_AddObject(mod, "InvalidRdataText", po_InvalidRdataText);
+//
+// Declaration of the custom exceptions
+// Initialization and addition of these go in the initModulePart
+// function in pydnspp
+//
+PyObject* po_InvalidRdataLength;
+PyObject* po_InvalidRdataText;
+PyObject* po_CharStringTooLong;
- po_CharStringTooLong = PyErr_NewException("pydnspp.CharStringTooLong", NULL, NULL);
- PyModule_AddObject(mod, "CharStringTooLong", po_CharStringTooLong);
+// This defines the complete type for reflection in python and
+// parsing of PyObject* to s_Rdata
+// Most of the functions are not actually implemented and NULL here.
+PyTypeObject rdata_type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "pydnspp.Rdata",
+ sizeof(s_Rdata), // tp_basicsize
+ 0, // tp_itemsize
+ (destructor)Rdata_destroy, // tp_dealloc
+ NULL, // tp_print
+ NULL, // tp_getattr
+ NULL, // tp_setattr
+ NULL, // tp_reserved
+ NULL, // tp_repr
+ NULL, // tp_as_number
+ NULL, // tp_as_sequence
+ NULL, // tp_as_mapping
+ NULL, // tp_hash
+ NULL, // tp_call
+ Rdata_str, // tp_str
+ NULL, // tp_getattro
+ NULL, // tp_setattro
+ NULL, // tp_as_buffer
+ Py_TPFLAGS_DEFAULT, // tp_flags
+ "The Rdata class is an abstract base class that provides "
+ "a set of common interfaces to manipulate concrete RDATA objects.",
+ NULL, // tp_traverse
+ NULL, // tp_clear
+ (richcmpfunc)RData_richcmp, // tp_richcompare
+ 0, // tp_weaklistoffset
+ NULL, // tp_iter
+ NULL, // tp_iternext
+ Rdata_methods, // tp_methods
+ NULL, // tp_members
+ NULL, // tp_getset
+ NULL, // tp_base
+ NULL, // tp_dict
+ NULL, // tp_descr_get
+ NULL, // tp_descr_set
+ 0, // tp_dictoffset
+ (initproc)Rdata_init, // tp_init
+ NULL, // tp_alloc
+ PyType_GenericNew, // tp_new
+ NULL, // tp_free
+ NULL, // tp_is_gc
+ NULL, // tp_bases
+ NULL, // tp_mro
+ NULL, // tp_cache
+ NULL, // tp_subclasses
+ NULL, // tp_weaklist
+ NULL, // tp_del
+ 0 // tp_version_tag
+};
-
- return (true);
+PyObject*
+createRdataObject(ConstRdataPtr source) {
+ s_Rdata* py_rdata =
+ static_cast<s_Rdata*>(rdata_type.tp_alloc(&rdata_type, 0));
+ if (py_rdata == NULL) {
+ isc_throw(PyCPPWrapperException, "Unexpected NULL C++ object, "
+ "probably due to short memory");
+ }
+ py_rdata->cppobj = source;
+ return (py_rdata);
}
+
+bool
+PyRdata_Check(PyObject* obj) {
+ if (obj == NULL) {
+ isc_throw(PyCPPWrapperException, "obj argument NULL in typecheck");
+ }
+ return (PyObject_TypeCheck(obj, &rdata_type));
+}
+
+const Rdata&
+PyRdata_ToRdata(const PyObject* rdata_obj) {
+ if (rdata_obj == NULL) {
+ isc_throw(PyCPPWrapperException,
+ "obj argument NULL in Rdata PyObject conversion");
+ }
+ const s_Rdata* rdata = static_cast<const s_Rdata*>(rdata_obj);
+ return (*rdata->cppobj);
+}
+
+} // end python namespace
+} // end dns namespace
+} // end isc namespace
diff --git a/src/lib/dns/python/rdata_python.h b/src/lib/dns/python/rdata_python.h
new file mode 100644
index 0000000..c7ddd57
--- /dev/null
+++ b/src/lib/dns/python/rdata_python.h
@@ -0,0 +1,68 @@
+// 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.
+
+#ifndef __PYTHON_RDATA_H
+#define __PYTHON_RDATA_H 1
+
+#include <Python.h>
+
+#include <dns/rdata.h>
+
+namespace isc {
+namespace dns {
+namespace python {
+
+extern PyObject* po_InvalidRdataLength;
+extern PyObject* po_InvalidRdataText;
+extern PyObject* po_CharStringTooLong;
+
+extern PyTypeObject rdata_type;
+
+/// This is a simple shortcut to create a python Rdata object (in the
+/// form of a pointer to PyObject) with minimal exception safety.
+/// On success, it returns a valid pointer to PyObject with a reference
+/// counter of 1; if something goes wrong it throws an exception (it never
+/// returns a NULL pointer).
+/// This function is expected to be called within a try block
+/// followed by necessary setup for python exception.
+PyObject* createRdataObject(isc::dns::rdata::ConstRdataPtr source);
+
+/// \brief Checks if the given python object is a Rdata object
+///
+/// \exception PyCPPWrapperException if obj is NULL
+///
+/// \param obj The object to check the type of
+/// \return true if the object is of type Rdata, false otherwise
+bool PyRdata_Check(PyObject* obj);
+
+/// \brief Returns a reference to the Rdata object contained within the given
+/// Python object.
+///
+/// \note The given object MUST be of type Rdata; this can be checked with
+/// either the right call to ParseTuple("O!"), or with PyRdata_Check()
+///
+/// \note This is not a copy; if the Rdata is needed when the PyObject
+/// may be destroyed, the caller must copy it itself.
+///
+/// \param rdata_obj The rdata object to convert
+const isc::dns::rdata::Rdata& PyRdata_ToRdata(const PyObject* rdata_obj);
+
+} // namespace python
+} // namespace dns
+} // namespace isc
+#endif // __PYTHON_RDATA_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/python/rrclass_python.cc b/src/lib/dns/python/rrclass_python.cc
index 6d150c2..0014187 100644
--- a/src/lib/dns/python/rrclass_python.cc
+++ b/src/lib/dns/python/rrclass_python.cc
@@ -11,35 +11,28 @@
// 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 <Python.h>
#include <dns/rrclass.h>
-using namespace isc::dns;
-using namespace isc::util;
-
-//
-// Declaration of the custom exceptions
-// Initialization and addition of these go in the initModulePart
-// function at the end of this file
-//
-static PyObject* po_InvalidRRClass;
-static PyObject* po_IncompleteRRClass;
-
-//
-// Definition of the classes
-//
+#include <dns/messagerenderer.h>
+#include <util/buffer.h>
+#include <util/python/pycppwrapper_util.h>
-// For each class, we need a struct, a helper functions (init, destroy,
-// and static wrappers around the methods we export), a list of methods,
-// and a type description
+#include "rrclass_python.h"
+#include "messagerenderer_python.h"
+#include "pydnspp_common.h"
-//
-// RRClass
-//
+using namespace isc::dns;
+using namespace isc::dns::python;
+using namespace isc::util;
+using namespace isc::util::python;
+namespace {
// The s_* Class simply covers one instantiation of the object
class s_RRClass : public PyObject {
public:
- RRClass* rrclass;
+ s_RRClass() : cppobj(NULL) {};
+ RRClass* cppobj;
};
//
@@ -48,25 +41,26 @@ public:
//
// General creation and destruction
-static int RRClass_init(s_RRClass* self, PyObject* args);
-static void RRClass_destroy(s_RRClass* self);
+int RRClass_init(s_RRClass* self, PyObject* args);
+void RRClass_destroy(s_RRClass* self);
// These are the functions we export
-static PyObject* RRClass_toText(s_RRClass* self);
+PyObject* RRClass_toText(s_RRClass* self);
// This is a second version of toText, we need one where the argument
// is a PyObject*, for the str() function in python.
-static PyObject* RRClass_str(PyObject* self);
-static PyObject* RRClass_toWire(s_RRClass* self, PyObject* args);
-static PyObject* RRClass_getCode(s_RRClass* self);
-static PyObject* RRClass_richcmp(s_RRClass* self, s_RRClass* other, int op);
+PyObject* RRClass_str(PyObject* self);
+PyObject* RRClass_toWire(s_RRClass* self, PyObject* args);
+PyObject* RRClass_getCode(s_RRClass* self);
+PyObject* RRClass_richcmp(s_RRClass* self, s_RRClass* other, int op);
// Static function for direct class creation
-static PyObject* RRClass_IN(s_RRClass *self);
-static PyObject* RRClass_CH(s_RRClass *self);
-static PyObject* RRClass_HS(s_RRClass *self);
-static PyObject* RRClass_NONE(s_RRClass *self);
-static PyObject* RRClass_ANY(s_RRClass *self);
+PyObject* RRClass_IN(s_RRClass *self);
+PyObject* RRClass_CH(s_RRClass *self);
+PyObject* RRClass_HS(s_RRClass *self);
+PyObject* RRClass_NONE(s_RRClass *self);
+PyObject* RRClass_ANY(s_RRClass *self);
+typedef CPPPyObjectContainer<s_RRClass, RRClass> RRClassContainer;
// This list contains the actual set of functions we have in
// python. Each entry has
@@ -74,7 +68,7 @@ static PyObject* RRClass_ANY(s_RRClass *self);
// 2. Our static function here
// 3. Argument type
// 4. Documentation
-static PyMethodDef RRClass_methods[] = {
+PyMethodDef RRClass_methods[] = {
{ "to_text", reinterpret_cast<PyCFunction>(RRClass_toText), METH_NOARGS,
"Returns the string representation" },
{ "to_wire", reinterpret_cast<PyCFunction>(RRClass_toWire), METH_VARARGS,
@@ -94,63 +88,7 @@ static PyMethodDef RRClass_methods[] = {
{ NULL, NULL, 0, NULL }
};
-// This defines the complete type for reflection in python and
-// parsing of PyObject* to s_RRClass
-// Most of the functions are not actually implemented and NULL here.
-static PyTypeObject rrclass_type = {
- PyVarObject_HEAD_INIT(NULL, 0)
- "pydnspp.RRClass",
- sizeof(s_RRClass), // tp_basicsize
- 0, // tp_itemsize
- (destructor)RRClass_destroy, // tp_dealloc
- NULL, // tp_print
- NULL, // tp_getattr
- NULL, // tp_setattr
- NULL, // tp_reserved
- NULL, // tp_repr
- NULL, // tp_as_number
- NULL, // tp_as_sequence
- NULL, // tp_as_mapping
- NULL, // tp_hash
- NULL, // tp_call
- RRClass_str, // tp_str
- NULL, // tp_getattro
- NULL, // tp_setattro
- NULL, // tp_as_buffer
- Py_TPFLAGS_DEFAULT, // tp_flags
- "The RRClass class encapsulates DNS resource record classes.\n"
- "This class manages the 16-bit integer class codes in quite a straightforward"
- "way. The only non trivial task is to handle textual representations of"
- "RR classes, such as \"IN\", \"CH\", or \"CLASS65534\".",
- NULL, // tp_traverse
- NULL, // tp_clear
- (richcmpfunc)RRClass_richcmp, // tp_richcompare
- 0, // tp_weaklistoffset
- NULL, // tp_iter
- NULL, // tp_iternext
- RRClass_methods, // tp_methods
- NULL, // tp_members
- NULL, // tp_getset
- NULL, // tp_base
- NULL, // tp_dict
- NULL, // tp_descr_get
- NULL, // tp_descr_set
- 0, // tp_dictoffset
- (initproc)RRClass_init, // tp_init
- NULL, // tp_alloc
- PyType_GenericNew, // tp_new
- NULL, // tp_free
- NULL, // tp_is_gc
- NULL, // tp_bases
- NULL, // tp_mro
- NULL, // tp_cache
- NULL, // tp_subclasses
- NULL, // tp_weaklist
- NULL, // tp_del
- 0 // tp_version_tag
-};
-
-static int
+int
RRClass_init(s_RRClass* self, PyObject* args) {
const char* s;
long i;
@@ -164,7 +102,7 @@ RRClass_init(s_RRClass* self, PyObject* args) {
// (the way to do exceptions is to set PyErr and return -1)
try {
if (PyArg_ParseTuple(args, "s", &s)) {
- self->rrclass = new RRClass(s);
+ self->cppobj = new RRClass(s);
return (0);
} else if (PyArg_ParseTuple(args, "l", &i)) {
if (i < 0 || i > 0xffff) {
@@ -173,7 +111,7 @@ RRClass_init(s_RRClass* self, PyObject* args) {
"RR class number out of range");
return (-1);
}
- self->rrclass = new RRClass(i);
+ self->cppobj = new RRClass(i);
return (0);
} else if (PyArg_ParseTuple(args, "O", &bytes) && PySequence_Check(bytes)) {
uint8_t data[2];
@@ -182,7 +120,7 @@ RRClass_init(s_RRClass* self, PyObject* args) {
return (result);
}
InputBuffer ib(data, 2);
- self->rrclass = new RRClass(ib);
+ self->cppobj = new RRClass(ib);
PyErr_Clear();
return (0);
}
@@ -199,20 +137,20 @@ RRClass_init(s_RRClass* self, PyObject* args) {
return (-1);
}
-static void
+void
RRClass_destroy(s_RRClass* self) {
- delete self->rrclass;
- self->rrclass = NULL;
+ delete self->cppobj;
+ self->cppobj = NULL;
Py_TYPE(self)->tp_free(self);
}
-static PyObject*
+PyObject*
RRClass_toText(s_RRClass* self) {
// Py_BuildValue makes python objects from native data
- return (Py_BuildValue("s", self->rrclass->toText().c_str()));
+ return (Py_BuildValue("s", self->cppobj->toText().c_str()));
}
-static PyObject*
+PyObject*
RRClass_str(PyObject* self) {
// Simply call the to_text method we already defined
return (PyObject_CallMethod(self,
@@ -220,16 +158,16 @@ RRClass_str(PyObject* self) {
const_cast<char*>("")));
}
-static PyObject*
+PyObject*
RRClass_toWire(s_RRClass* self, PyObject* args) {
PyObject* bytes;
- s_MessageRenderer* mr;
-
+ PyObject* mr;
+
if (PyArg_ParseTuple(args, "O", &bytes) && PySequence_Check(bytes)) {
PyObject* bytes_o = bytes;
-
+
OutputBuffer buffer(2);
- self->rrclass->toWire(buffer);
+ self->cppobj->toWire(buffer);
PyObject* n = PyBytes_FromStringAndSize(static_cast<const char*>(buffer.getData()), buffer.getLength());
PyObject* result = PySequence_InPlaceConcat(bytes_o, n);
// We need to release the object we temporarily created here
@@ -237,7 +175,7 @@ RRClass_toWire(s_RRClass* self, PyObject* args) {
Py_DECREF(n);
return (result);
} else if (PyArg_ParseTuple(args, "O!", &messagerenderer_type, &mr)) {
- self->rrclass->toWire(*mr->messagerenderer);
+ self->cppobj->toWire(PyMessageRenderer_ToMessageRenderer(mr));
// If we return NULL it is seen as an error, so use this for
// None returns
Py_RETURN_NONE;
@@ -248,12 +186,12 @@ RRClass_toWire(s_RRClass* self, PyObject* args) {
return (NULL);
}
-static PyObject*
+PyObject*
RRClass_getCode(s_RRClass* self) {
- return (Py_BuildValue("I", self->rrclass->getCode()));
+ return (Py_BuildValue("I", self->cppobj->getCode()));
}
-static PyObject*
+PyObject*
RRClass_richcmp(s_RRClass* self, s_RRClass* other, int op) {
bool c;
@@ -265,24 +203,24 @@ RRClass_richcmp(s_RRClass* self, s_RRClass* other, int op) {
switch (op) {
case Py_LT:
- c = *self->rrclass < *other->rrclass;
+ c = *self->cppobj < *other->cppobj;
break;
case Py_LE:
- c = *self->rrclass < *other->rrclass ||
- *self->rrclass == *other->rrclass;
+ c = *self->cppobj < *other->cppobj ||
+ *self->cppobj == *other->cppobj;
break;
case Py_EQ:
- c = *self->rrclass == *other->rrclass;
+ c = *self->cppobj == *other->cppobj;
break;
case Py_NE:
- c = *self->rrclass != *other->rrclass;
+ c = *self->cppobj != *other->cppobj;
break;
case Py_GT:
- c = *other->rrclass < *self->rrclass;
+ c = *other->cppobj < *self->cppobj;
break;
case Py_GE:
- c = *other->rrclass < *self->rrclass ||
- *self->rrclass == *other->rrclass;
+ c = *other->cppobj < *self->cppobj ||
+ *self->cppobj == *other->cppobj;
break;
default:
PyErr_SetString(PyExc_IndexError,
@@ -298,56 +236,131 @@ RRClass_richcmp(s_RRClass* self, s_RRClass* other, int op) {
//
// Common function for RRClass_IN/CH/etc.
//
-static PyObject* RRClass_createStatic(RRClass stc) {
+PyObject* RRClass_createStatic(RRClass stc) {
s_RRClass* ret = PyObject_New(s_RRClass, &rrclass_type);
if (ret != NULL) {
- ret->rrclass = new RRClass(stc);
+ ret->cppobj = new RRClass(stc);
}
return (ret);
}
-static PyObject* RRClass_IN(s_RRClass*) {
+PyObject* RRClass_IN(s_RRClass*) {
return (RRClass_createStatic(RRClass::IN()));
}
-static PyObject* RRClass_CH(s_RRClass*) {
+PyObject* RRClass_CH(s_RRClass*) {
return (RRClass_createStatic(RRClass::CH()));
}
-static PyObject* RRClass_HS(s_RRClass*) {
+PyObject* RRClass_HS(s_RRClass*) {
return (RRClass_createStatic(RRClass::HS()));
}
-static PyObject* RRClass_NONE(s_RRClass*) {
+PyObject* RRClass_NONE(s_RRClass*) {
return (RRClass_createStatic(RRClass::NONE()));
}
-static PyObject* RRClass_ANY(s_RRClass*) {
+PyObject* RRClass_ANY(s_RRClass*) {
return (RRClass_createStatic(RRClass::ANY()));
}
-// end of RRClass
+
+} // end anonymous namespace
+
+namespace isc {
+namespace dns {
+namespace python {
+
+//
+// Declaration of the custom exceptions
+// Initialization and addition of these go in the initModulePart
+// function in pydnspp.cc
+//
+PyObject* po_InvalidRRClass;
+PyObject* po_IncompleteRRClass;
+
+
+// This defines the complete type for reflection in python and
+// parsing of PyObject* to s_RRClass
+// Most of the functions are not actually implemented and NULL here.
+PyTypeObject rrclass_type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "pydnspp.RRClass",
+ sizeof(s_RRClass), // tp_basicsize
+ 0, // tp_itemsize
+ (destructor)RRClass_destroy, // tp_dealloc
+ NULL, // tp_print
+ NULL, // tp_getattr
+ NULL, // tp_setattr
+ NULL, // tp_reserved
+ NULL, // tp_repr
+ NULL, // tp_as_number
+ NULL, // tp_as_sequence
+ NULL, // tp_as_mapping
+ NULL, // tp_hash
+ NULL, // tp_call
+ RRClass_str, // tp_str
+ NULL, // tp_getattro
+ NULL, // tp_setattro
+ NULL, // tp_as_buffer
+ Py_TPFLAGS_DEFAULT, // tp_flags
+ "The RRClass class encapsulates DNS resource record classes.\n"
+ "This class manages the 16-bit integer class codes in quite a straightforward"
+ "way. The only non trivial task is to handle textual representations of"
+ "RR classes, such as \"IN\", \"CH\", or \"CLASS65534\".",
+ NULL, // tp_traverse
+ NULL, // tp_clear
+ (richcmpfunc)RRClass_richcmp, // tp_richcompare
+ 0, // tp_weaklistoffset
+ NULL, // tp_iter
+ NULL, // tp_iternext
+ RRClass_methods, // tp_methods
+ NULL, // tp_members
+ NULL, // tp_getset
+ NULL, // tp_base
+ NULL, // tp_dict
+ NULL, // tp_descr_get
+ NULL, // tp_descr_set
+ 0, // tp_dictoffset
+ (initproc)RRClass_init, // tp_init
+ NULL, // tp_alloc
+ PyType_GenericNew, // tp_new
+ NULL, // tp_free
+ NULL, // tp_is_gc
+ NULL, // tp_bases
+ NULL, // tp_mro
+ NULL, // tp_cache
+ NULL, // tp_subclasses
+ NULL, // tp_weaklist
+ NULL, // tp_del
+ 0 // tp_version_tag
+};
+
+PyObject*
+createRRClassObject(const RRClass& source) {
+ RRClassContainer container(PyObject_New(s_RRClass, &rrclass_type));
+ container.set(new RRClass(source));
+ return (container.release());
+}
-// Module Initialization, all statics are initialized here
bool
-initModulePart_RRClass(PyObject* mod) {
- // Add the exceptions to the module
- po_InvalidRRClass = PyErr_NewException("pydnspp.InvalidRRClass", NULL, NULL);
- Py_INCREF(po_InvalidRRClass);
- PyModule_AddObject(mod, "InvalidRRClass", po_InvalidRRClass);
- po_IncompleteRRClass = PyErr_NewException("pydnspp.IncompleteRRClass", NULL, NULL);
- Py_INCREF(po_IncompleteRRClass);
- PyModule_AddObject(mod, "IncompleteRRClass", po_IncompleteRRClass);
-
- // We initialize the static description object with PyType_Ready(),
- // then add it to the module. This is not just a check! (leaving
- // this out results in segmentation faults)
- if (PyType_Ready(&rrclass_type) < 0) {
- return (false);
+PyRRClass_Check(PyObject* obj) {
+ if (obj == NULL) {
+ isc_throw(PyCPPWrapperException, "obj argument NULL in typecheck");
}
- Py_INCREF(&rrclass_type);
- PyModule_AddObject(mod, "RRClass",
- reinterpret_cast<PyObject*>(&rrclass_type));
-
- return (true);
+ return (PyObject_TypeCheck(obj, &rrclass_type));
}
+
+const RRClass&
+PyRRClass_ToRRClass(const PyObject* rrclass_obj) {
+ if (rrclass_obj == NULL) {
+ isc_throw(PyCPPWrapperException,
+ "obj argument NULL in RRClass PyObject conversion");
+ }
+ const s_RRClass* rrclass = static_cast<const s_RRClass*>(rrclass_obj);
+ return (*rrclass->cppobj);
+}
+
+} // end namespace python
+} // end namespace dns
+} // end namespace isc
diff --git a/src/lib/dns/python/rrclass_python.h b/src/lib/dns/python/rrclass_python.h
new file mode 100644
index 0000000..f58bba6
--- /dev/null
+++ b/src/lib/dns/python/rrclass_python.h
@@ -0,0 +1,68 @@
+// 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.
+
+#ifndef __PYTHON_RRCLASS_H
+#define __PYTHON_RRCLASS_H 1
+
+#include <Python.h>
+
+namespace isc {
+namespace dns {
+class RRClass;
+
+namespace python {
+
+extern PyObject* po_InvalidRRClass;
+extern PyObject* po_IncompleteRRClass;
+
+extern PyTypeObject rrclass_type;
+
+/// This is a simple shortcut to create a python RRClass object (in the
+/// form of a pointer to PyObject) with minimal exception safety.
+/// On success, it returns a valid pointer to PyObject with a reference
+/// counter of 1; if something goes wrong it throws an exception (it never
+/// returns a NULL pointer).
+/// This function is expected to be called within a try block
+/// followed by necessary setup for python exception.
+PyObject* createRRClassObject(const RRClass& source);
+
+/// \brief Checks if the given python object is a RRClass object
+///
+/// \exception PyCPPWrapperException if obj is NULL
+///
+/// \param obj The object to check the type of
+/// \return true if the object is of type RRClass, false otherwise
+bool PyRRClass_Check(PyObject* obj);
+
+/// \brief Returns a reference to the RRClass object contained within the given
+/// Python object.
+///
+/// \note The given object MUST be of type RRClass; this can be checked with
+/// either the right call to ParseTuple("O!"), or with PyRRClass_Check()
+///
+/// \note This is not a copy; if the RRClass is needed when the PyObject
+/// may be destroyed, the caller must copy it itself.
+///
+/// \param rrclass_obj The rrclass object to convert
+const RRClass& PyRRClass_ToRRClass(const PyObject* rrclass_obj);
+
+
+} // namespace python
+} // namespace dns
+} // namespace isc
+#endif // __PYTHON_RRCLASS_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/python/rrset_python.cc b/src/lib/dns/python/rrset_python.cc
index 71a0710..9fc3d79 100644
--- a/src/lib/dns/python/rrset_python.cc
+++ b/src/lib/dns/python/rrset_python.cc
@@ -12,55 +12,63 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-#include <dns/rrset.h>
+#include <Python.h>
-//
-// Declaration of the custom exceptions
-// Initialization and addition of these go in the module init at the
-// end
-//
-static PyObject* po_EmptyRRset;
+#include <util/python/pycppwrapper_util.h>
-//
-// Definition of the classes
-//
-
-// For each class, we need a struct, a helper functions (init, destroy,
-// and static wrappers around the methods we export), a list of methods,
-// and a type description
+#include <dns/rrset.h>
+#include <dns/name.h>
+#include <dns/messagerenderer.h>
+
+#include "name_python.h"
+#include "pydnspp_common.h"
+#include "rrset_python.h"
+#include "rrclass_python.h"
+#include "rrtype_python.h"
+#include "rrttl_python.h"
+#include "rdata_python.h"
+#include "messagerenderer_python.h"
+
+using namespace std;
using namespace isc::dns;
+using namespace isc::dns::python;
using namespace isc::util;
+using namespace isc::util::python;
+
+namespace {
-// RRset
+// The s_* Class simply coverst one instantiation of the object
// Using a shared_ptr here should not really be necessary (PyObject
// is already reference-counted), however internally on the cpp side,
// not doing so might result in problems, since we can't copy construct
-// rrsets, adding them to messages results in a problem when the
-// message is destroyed or cleared later
+// rdata field, adding them to rrsets results in a problem when the
+// rrset is destroyed later
class s_RRset : public PyObject {
public:
- RRsetPtr rrset;
+ isc::dns::RRsetPtr cppobj;
};
-static int RRset_init(s_RRset* self, PyObject* args);
-static void RRset_destroy(s_RRset* self);
-
-static PyObject* RRset_getRdataCount(s_RRset* self);
-static PyObject* RRset_getName(s_RRset* self);
-static PyObject* RRset_getClass(s_RRset* self);
-static PyObject* RRset_getType(s_RRset* self);
-static PyObject* RRset_getTTL(s_RRset* self);
-static PyObject* RRset_setName(s_RRset* self, PyObject* args);
-static PyObject* RRset_setTTL(s_RRset* self, PyObject* args);
-static PyObject* RRset_toText(s_RRset* self);
-static PyObject* RRset_str(PyObject* self);
-static PyObject* RRset_toWire(s_RRset* self, PyObject* args);
-static PyObject* RRset_addRdata(s_RRset* self, PyObject* args);
-static PyObject* RRset_getRdata(s_RRset* self);
+int RRset_init(s_RRset* self, PyObject* args);
+void RRset_destroy(s_RRset* self);
+
+PyObject* RRset_getRdataCount(s_RRset* self);
+PyObject* RRset_getName(s_RRset* self);
+PyObject* RRset_getClass(s_RRset* self);
+PyObject* RRset_getType(s_RRset* self);
+PyObject* RRset_getTTL(s_RRset* self);
+PyObject* RRset_setName(s_RRset* self, PyObject* args);
+PyObject* RRset_setTTL(s_RRset* self, PyObject* args);
+PyObject* RRset_toText(s_RRset* self);
+PyObject* RRset_str(PyObject* self);
+PyObject* RRset_toWire(s_RRset* self, PyObject* args);
+PyObject* RRset_addRdata(s_RRset* self, PyObject* args);
+PyObject* RRset_getRdata(s_RRset* self);
+PyObject* RRset_removeRRsig(s_RRset* self);
+
// TODO: iterator?
-static PyMethodDef RRset_methods[] = {
+PyMethodDef RRset_methods[] = {
{ "get_rdata_count", reinterpret_cast<PyCFunction>(RRset_getRdataCount), METH_NOARGS,
"Returns the number of rdata fields." },
{ "get_name", reinterpret_cast<PyCFunction>(RRset_getName), METH_NOARGS,
@@ -88,208 +96,142 @@ static PyMethodDef RRset_methods[] = {
"Adds the rdata for one RR to the RRset.\nTakes an Rdata object as an argument" },
{ "get_rdata", reinterpret_cast<PyCFunction>(RRset_getRdata), METH_NOARGS,
"Returns a List containing all Rdata elements" },
+ { "remove_rrsig", reinterpret_cast<PyCFunction>(RRset_removeRRsig), METH_NOARGS,
+ "Clears the list of RRsigs for this RRset" },
{ NULL, NULL, 0, NULL }
};
-static PyTypeObject rrset_type = {
- PyVarObject_HEAD_INIT(NULL, 0)
- "pydnspp.RRset",
- sizeof(s_RRset), // tp_basicsize
- 0, // tp_itemsize
- (destructor)RRset_destroy, // tp_dealloc
- NULL, // tp_print
- NULL, // tp_getattr
- NULL, // tp_setattr
- NULL, // tp_reserved
- NULL, // tp_repr
- NULL, // tp_as_number
- NULL, // tp_as_sequence
- NULL, // tp_as_mapping
- NULL, // tp_hash
- NULL, // tp_call
- RRset_str, // tp_str
- NULL, // tp_getattro
- NULL, // tp_setattro
- NULL, // tp_as_buffer
- Py_TPFLAGS_DEFAULT, // tp_flags
- "The AbstractRRset class is an abstract base class that "
- "models a DNS RRset.\n\n"
- "An object of (a specific derived class of) AbstractRRset "
- "models an RRset as described in the DNS standard:\n"
- "A set of DNS resource records (RRs) of the same type and class. "
- "The standard requires the TTL of all RRs in an RRset be the same; "
- "this class follows that requirement.\n\n"
- "Note about duplicate RDATA: RFC2181 states that it's meaningless that an "
- "RRset contains two identical RRs and that name servers should suppress "
- "such duplicates.\n"
- "This class is not responsible for ensuring this requirement: For example, "
- "addRdata() method doesn't check if there's already RDATA identical "
- "to the one being added.\n"
- "This is because such checks can be expensive, and it's often easy to "
- "ensure the uniqueness requirement at the %data preparation phase "
- "(e.g. when loading a zone).",
- NULL, // tp_traverse
- NULL, // tp_clear
- NULL, // tp_richcompare
- 0, // tp_weaklistoffset
- NULL, // tp_iter
- NULL, // tp_iternext
- RRset_methods, // tp_methods
- NULL, // tp_members
- NULL, // tp_getset
- NULL, // tp_base
- NULL, // tp_dict
- NULL, // tp_descr_get
- NULL, // tp_descr_set
- 0, // tp_dictoffset
- (initproc)RRset_init, // tp_init
- NULL, // tp_alloc
- PyType_GenericNew, // tp_new
- NULL, // tp_free
- NULL, // tp_is_gc
- NULL, // tp_bases
- NULL, // tp_mro
- NULL, // tp_cache
- NULL, // tp_subclasses
- NULL, // tp_weaklist
- NULL, // tp_del
- 0 // tp_version_tag
-};
-
-static int
+int
RRset_init(s_RRset* self, PyObject* args) {
- s_Name* name;
- s_RRClass* rrclass;
- s_RRType* rrtype;
- s_RRTTL* rrttl;
+ PyObject* name;
+ PyObject* rrclass;
+ PyObject* rrtype;
+ PyObject* rrttl;
if (PyArg_ParseTuple(args, "O!O!O!O!", &name_type, &name,
&rrclass_type, &rrclass,
&rrtype_type, &rrtype,
&rrttl_type, &rrttl
)) {
- self->rrset = RRsetPtr(new RRset(*name->cppobj, *rrclass->rrclass,
- *rrtype->rrtype, *rrttl->rrttl));
+ self->cppobj = RRsetPtr(new RRset(PyName_ToName(name),
+ PyRRClass_ToRRClass(rrclass),
+ PyRRType_ToRRType(rrtype),
+ PyRRTTL_ToRRTTL(rrttl)));
return (0);
}
- self->rrset = RRsetPtr();
+ self->cppobj = RRsetPtr();
return (-1);
}
-static void
+void
RRset_destroy(s_RRset* self) {
// Clear the shared_ptr so that its reference count is zero
// before we call tp_free() (there is no direct release())
- self->rrset.reset();
+ self->cppobj.reset();
Py_TYPE(self)->tp_free(self);
}
-static PyObject*
+PyObject*
RRset_getRdataCount(s_RRset* self) {
- return (Py_BuildValue("I", self->rrset->getRdataCount()));
+ return (Py_BuildValue("I", self->cppobj->getRdataCount()));
}
-static PyObject*
+PyObject*
RRset_getName(s_RRset* self) {
- s_Name* name;
-
- // is this the best way to do this?
- name = static_cast<s_Name*>(name_type.tp_alloc(&name_type, 0));
- if (name != NULL) {
- name->cppobj = new Name(self->rrset->getName());
- if (name->cppobj == NULL)
- {
- Py_DECREF(name);
- return (NULL);
- }
+ try {
+ return (createNameObject(self->cppobj->getName()));
+ } catch (const exception& ex) {
+ const string ex_what =
+ "Unexpected failure getting rrset Name: " +
+ string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError,
+ "Unexpected failure getting rrset Name");
}
-
- return (name);
+ return (NULL);
}
-static PyObject*
+PyObject*
RRset_getClass(s_RRset* self) {
- s_RRClass* rrclass;
-
- rrclass = static_cast<s_RRClass*>(rrclass_type.tp_alloc(&rrclass_type, 0));
- if (rrclass != NULL) {
- rrclass->rrclass = new RRClass(self->rrset->getClass());
- if (rrclass->rrclass == NULL)
- {
- Py_DECREF(rrclass);
- return (NULL);
- }
+ try {
+ return (createRRClassObject(self->cppobj->getClass()));
+ } catch (const exception& ex) {
+ const string ex_what =
+ "Unexpected failure getting question RRClass: " +
+ string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError,
+ "Unexpected failure getting question RRClass");
}
-
- return (rrclass);
+ return (NULL);
}
-static PyObject*
+PyObject*
RRset_getType(s_RRset* self) {
- s_RRType* rrtype;
-
- rrtype = static_cast<s_RRType*>(rrtype_type.tp_alloc(&rrtype_type, 0));
- if (rrtype != NULL) {
- rrtype->rrtype = new RRType(self->rrset->getType());
- if (rrtype->rrtype == NULL)
- {
- Py_DECREF(rrtype);
- return (NULL);
- }
+ try {
+ return (createRRTypeObject(self->cppobj->getType()));
+ } catch (const exception& ex) {
+ const string ex_what =
+ "Unexpected failure getting question RRType: " +
+ string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError,
+ "Unexpected failure getting question RRType");
}
-
- return (rrtype);
+ return (NULL);
}
-static PyObject*
+PyObject*
RRset_getTTL(s_RRset* self) {
- s_RRTTL* rrttl;
-
- rrttl = static_cast<s_RRTTL*>(rrttl_type.tp_alloc(&rrttl_type, 0));
- if (rrttl != NULL) {
- rrttl->rrttl = new RRTTL(self->rrset->getTTL());
- if (rrttl->rrttl == NULL)
- {
- Py_DECREF(rrttl);
- return (NULL);
- }
+ try {
+ return (createRRTTLObject(self->cppobj->getTTL()));
+ } catch (const exception& ex) {
+ const string ex_what =
+ "Unexpected failure getting question TTL: " +
+ string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError,
+ "Unexpected failure getting question TTL");
}
-
- return (rrttl);
+ return (NULL);
}
-static PyObject*
+PyObject*
RRset_setName(s_RRset* self, PyObject* args) {
- s_Name* name;
+ PyObject* name;
if (!PyArg_ParseTuple(args, "O!", &name_type, &name)) {
return (NULL);
}
- self->rrset->setName(*name->cppobj);
+ self->cppobj->setName(PyName_ToName(name));
Py_RETURN_NONE;
}
-static PyObject*
+PyObject*
RRset_setTTL(s_RRset* self, PyObject* args) {
- s_RRTTL* rrttl;
+ PyObject* rrttl;
if (!PyArg_ParseTuple(args, "O!", &rrttl_type, &rrttl)) {
return (NULL);
}
- self->rrset->setTTL(*rrttl->rrttl);
+ self->cppobj->setTTL(PyRRTTL_ToRRTTL(rrttl));
Py_RETURN_NONE;
}
-static PyObject*
+PyObject*
RRset_toText(s_RRset* self) {
try {
- return (Py_BuildValue("s", self->rrset->toText().c_str()));
+ return (Py_BuildValue("s", self->cppobj->toText().c_str()));
} catch (const EmptyRRset& ers) {
PyErr_SetString(po_EmptyRRset, ers.what());
return (NULL);
}
}
-static PyObject*
+PyObject*
RRset_str(PyObject* self) {
// Simply call the to_text method we already defined
return (PyObject_CallMethod(self,
@@ -297,17 +239,17 @@ RRset_str(PyObject* self) {
const_cast<char*>("")));
}
-static PyObject*
+PyObject*
RRset_toWire(s_RRset* self, PyObject* args) {
PyObject* bytes;
- s_MessageRenderer* mr;
+ PyObject* mr;
try {
if (PyArg_ParseTuple(args, "O", &bytes) && PySequence_Check(bytes)) {
PyObject* bytes_o = bytes;
-
+
OutputBuffer buffer(4096);
- self->rrset->toWire(buffer);
+ self->cppobj->toWire(buffer);
PyObject* n = PyBytes_FromStringAndSize(static_cast<const char*>(buffer.getData()), buffer.getLength());
PyObject* result = PySequence_InPlaceConcat(bytes_o, n);
// We need to release the object we temporarily created here
@@ -315,7 +257,7 @@ RRset_toWire(s_RRset* self, PyObject* args) {
Py_DECREF(n);
return (result);
} else if (PyArg_ParseTuple(args, "O!", &messagerenderer_type, &mr)) {
- self->rrset->toWire(*mr->messagerenderer);
+ self->cppobj->toWire(PyMessageRenderer_ToMessageRenderer(mr));
// If we return NULL it is seen as an error, so use this for
// None returns
Py_RETURN_NONE;
@@ -331,14 +273,14 @@ RRset_toWire(s_RRset* self, PyObject* args) {
return (NULL);
}
-static PyObject*
+PyObject*
RRset_addRdata(s_RRset* self, PyObject* args) {
- s_Rdata* rdata;
+ PyObject* rdata;
if (!PyArg_ParseTuple(args, "O!", &rdata_type, &rdata)) {
return (NULL);
}
try {
- self->rrset->addRdata(*rdata->rdata);
+ self->cppobj->addRdata(PyRdata_ToRdata(rdata));
Py_RETURN_NONE;
} catch (const std::bad_cast&) {
PyErr_Clear();
@@ -348,55 +290,173 @@ RRset_addRdata(s_RRset* self, PyObject* args) {
}
}
-static PyObject*
+PyObject*
RRset_getRdata(s_RRset* self) {
PyObject* list = PyList_New(0);
- RdataIteratorPtr it = self->rrset->getRdataIterator();
-
- for (; !it->isLast(); it->next()) {
- s_Rdata *rds = static_cast<s_Rdata*>(rdata_type.tp_alloc(&rdata_type, 0));
- if (rds != NULL) {
- // hmz them iterators/shared_ptrs and private constructors
- // make this a bit weird, so we create a new one with
- // the data available
- const Rdata *rd = &it->getCurrent();
- rds->rdata = createRdata(self->rrset->getType(), self->rrset->getClass(), *rd);
- PyList_Append(list, rds);
- } else {
- return (NULL);
+ RdataIteratorPtr it = self->cppobj->getRdataIterator();
+
+ try {
+ for (; !it->isLast(); it->next()) {
+ const rdata::Rdata *rd = &it->getCurrent();
+ if (PyList_Append(list,
+ createRdataObject(createRdata(self->cppobj->getType(),
+ self->cppobj->getClass(), *rd))) == -1) {
+ Py_DECREF(list);
+ return (NULL);
+ }
}
+ return (list);
+ } catch (const exception& ex) {
+ const string ex_what =
+ "Unexpected failure getting rrset Rdata: " +
+ string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError,
+ "Unexpected failure getting rrset Rdata");
}
-
- return (list);
+ Py_DECREF(list);
+ return (NULL);
}
-// end of RRset
+PyObject*
+RRset_removeRRsig(s_RRset* self) {
+ self->cppobj->removeRRsig();
+ Py_RETURN_NONE;
+}
+} // end of unnamed namespace
-// Module Initialization, all statics are initialized here
-bool
-initModulePart_RRset(PyObject* mod) {
- // Add the exceptions to the module
- po_EmptyRRset = PyErr_NewException("pydnspp.EmptyRRset", NULL, NULL);
- PyModule_AddObject(mod, "EmptyRRset", po_EmptyRRset);
+namespace isc {
+namespace dns {
+namespace python {
- // Add the enums to the module
+//
+// Declaration of the custom exceptions
+// Initialization and addition of these go in the module init at the
+// end
+//
+PyObject* po_EmptyRRset;
- // Add the constants to the module
+PyTypeObject rrset_type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "pydnspp.RRset",
+ sizeof(s_RRset), // tp_basicsize
+ 0, // tp_itemsize
+ (destructor)RRset_destroy, // tp_dealloc
+ NULL, // tp_print
+ NULL, // tp_getattr
+ NULL, // tp_setattr
+ NULL, // tp_reserved
+ NULL, // tp_repr
+ NULL, // tp_as_number
+ NULL, // tp_as_sequence
+ NULL, // tp_as_mapping
+ NULL, // tp_hash
+ NULL, // tp_call
+ RRset_str, // tp_str
+ NULL, // tp_getattro
+ NULL, // tp_setattro
+ NULL, // tp_as_buffer
+ Py_TPFLAGS_DEFAULT, // tp_flags
+ "The AbstractRRset class is an abstract base class that "
+ "models a DNS RRset.\n\n"
+ "An object of (a specific derived class of) AbstractRRset "
+ "models an RRset as described in the DNS standard:\n"
+ "A set of DNS resource records (RRs) of the same type and class. "
+ "The standard requires the TTL of all RRs in an RRset be the same; "
+ "this class follows that requirement.\n\n"
+ "Note about duplicate RDATA: RFC2181 states that it's meaningless that an "
+ "RRset contains two identical RRs and that name servers should suppress "
+ "such duplicates.\n"
+ "This class is not responsible for ensuring this requirement: For example, "
+ "addRdata() method doesn't check if there's already RDATA identical "
+ "to the one being added.\n"
+ "This is because such checks can be expensive, and it's often easy to "
+ "ensure the uniqueness requirement at the %data preparation phase "
+ "(e.g. when loading a zone).",
+ NULL, // tp_traverse
+ NULL, // tp_clear
+ NULL, // tp_richcompare
+ 0, // tp_weaklistoffset
+ NULL, // tp_iter
+ NULL, // tp_iternext
+ RRset_methods, // tp_methods
+ NULL, // tp_members
+ NULL, // tp_getset
+ NULL, // tp_base
+ NULL, // tp_dict
+ NULL, // tp_descr_get
+ NULL, // tp_descr_set
+ 0, // tp_dictoffset
+ (initproc)RRset_init, // tp_init
+ NULL, // tp_alloc
+ PyType_GenericNew, // tp_new
+ NULL, // tp_free
+ NULL, // tp_is_gc
+ NULL, // tp_bases
+ NULL, // tp_mro
+ NULL, // tp_cache
+ NULL, // tp_subclasses
+ NULL, // tp_weaklist
+ NULL, // tp_del
+ 0 // tp_version_tag
+};
+
+PyObject*
+createRRsetObject(const RRset& source) {
- // Add the classes to the module
- // We initialize the static description object with PyType_Ready(),
- // then add it to the module
+ // RRsets are noncopyable, so as a workaround we recreate a new one
+ // and copy over all content
+ RRsetPtr new_rrset = isc::dns::RRsetPtr(
+ new isc::dns::RRset(source.getName(), source.getClass(),
+ source.getType(), source.getTTL()));
- // NameComparisonResult
- if (PyType_Ready(&rrset_type) < 0) {
- return (false);
+ isc::dns::RdataIteratorPtr rdata_it(source.getRdataIterator());
+ for (rdata_it->first(); !rdata_it->isLast(); rdata_it->next()) {
+ new_rrset->addRdata(rdata_it->getCurrent());
+ }
+
+ isc::dns::RRsetPtr sigs = source.getRRsig();
+ if (sigs) {
+ new_rrset->addRRsig(sigs);
+ }
+ s_RRset* py_rrset =
+ static_cast<s_RRset*>(rrset_type.tp_alloc(&rrset_type, 0));
+ if (py_rrset == NULL) {
+ isc_throw(PyCPPWrapperException, "Unexpected NULL C++ object, "
+ "probably due to short memory");
}
- Py_INCREF(&rrset_type);
- PyModule_AddObject(mod, "RRset",
- reinterpret_cast<PyObject*>(&rrset_type));
-
- return (true);
+ py_rrset->cppobj = new_rrset;
+ return (py_rrset);
}
+bool
+PyRRset_Check(PyObject* obj) {
+ if (obj == NULL) {
+ isc_throw(PyCPPWrapperException, "obj argument NULL in typecheck");
+ }
+ return (PyObject_TypeCheck(obj, &rrset_type));
+}
+
+RRset&
+PyRRset_ToRRset(PyObject* rrset_obj) {
+ s_RRset* rrset = static_cast<s_RRset*>(rrset_obj);
+ return (*rrset->cppobj);
+}
+
+RRsetPtr
+PyRRset_ToRRsetPtr(PyObject* rrset_obj) {
+ if (rrset_obj == NULL) {
+ isc_throw(PyCPPWrapperException,
+ "obj argument NULL in RRset PyObject conversion");
+ }
+ s_RRset* rrset = static_cast<s_RRset*>(rrset_obj);
+ return (rrset->cppobj);
+}
+
+
+} // end python namespace
+} // end dns namespace
+} // end isc namespace
diff --git a/src/lib/dns/python/rrset_python.h b/src/lib/dns/python/rrset_python.h
new file mode 100644
index 0000000..4268678
--- /dev/null
+++ b/src/lib/dns/python/rrset_python.h
@@ -0,0 +1,78 @@
+// 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.
+
+#ifndef __PYTHON_RRSET_H
+#define __PYTHON_RRSET_H 1
+
+#include <Python.h>
+
+#include <dns/rrset.h>
+
+#include <util/python/pycppwrapper_util.h>
+
+namespace isc {
+namespace dns {
+namespace python {
+
+extern PyObject* po_EmptyRRset;
+
+extern PyTypeObject rrset_type;
+
+/// This is a simple shortcut to create a python RRset object (in the
+/// form of a pointer to PyObject) with minimal exception safety.
+/// On success, it returns a valid pointer to PyObject with a reference
+/// counter of 1; if something goes wrong it throws an exception (it never
+/// returns a NULL pointer).
+/// This function is expected to be called within a try block
+/// followed by necessary setup for python exception.
+PyObject* createRRsetObject(const RRset& source);
+
+/// \brief Checks if the given python object is a RRset object
+///
+/// \exception PyCPPWrapperException if obj is NULL
+///
+/// \param obj The object to check the type of
+/// \return true if the object is of type RRset, false otherwise
+bool PyRRset_Check(PyObject* obj);
+
+/// \brief Returns a reference to the RRset object contained within the given
+/// Python object.
+///
+/// \note The given object MUST be of type RRset; this can be checked with
+/// either the right call to ParseTuple("O!"), or with PyRRset_Check()
+///
+/// \note This is not a copy; if the RRset is needed when the PyObject
+/// may be destroyed, the caller must copy it itself.
+///
+/// \param rrset_obj The rrset object to convert
+RRset& PyRRset_ToRRset(PyObject* rrset_obj);
+
+/// \brief Returns the shared_ptr of the RRset object contained within the
+/// given Python object.
+///
+/// \note The given object MUST be of type RRset; this can be checked with
+/// either the right call to ParseTuple("O!"), or with PyRRset_Check()
+///
+/// \param rrset_obj The rrset object to convert
+RRsetPtr PyRRset_ToRRsetPtr(PyObject* rrset_obj);
+
+
+} // namespace python
+} // namespace dns
+} // namespace isc
+#endif // __PYTHON_RRSET_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/python/rrttl_python.cc b/src/lib/dns/python/rrttl_python.cc
index c4b25bf..3a3f067 100644
--- a/src/lib/dns/python/rrttl_python.cc
+++ b/src/lib/dns/python/rrttl_python.cc
@@ -12,57 +12,41 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
+#include <Python.h>
#include <vector>
#include <dns/rrttl.h>
+#include <dns/messagerenderer.h>
+#include <util/buffer.h>
+#include <util/python/pycppwrapper_util.h>
+
+#include "rrttl_python.h"
+#include "pydnspp_common.h"
+#include "messagerenderer_python.h"
using namespace std;
using namespace isc::dns;
+using namespace isc::dns::python;
using namespace isc::util;
+using namespace isc::util::python;
-//
-// Declaration of the custom exceptions
-// Initialization and addition of these go in the initModulePart
-// function at the end of this file
-//
-static PyObject* po_InvalidRRTTL;
-static PyObject* po_IncompleteRRTTL;
-
-//
-// Definition of the classes
-//
-
-// For each class, we need a struct, a helper functions (init, destroy,
-// and static wrappers around the methods we export), a list of methods,
-// and a type description
-
-//
-// RRTTL
-//
-
+namespace {
// The s_* Class simply covers one instantiation of the object
class s_RRTTL : public PyObject {
public:
- RRTTL* rrttl;
+ s_RRTTL() : cppobj(NULL) {};
+ isc::dns::RRTTL* cppobj;
};
-//
-// We declare the functions here, the definitions are below
-// the type definition of the object, since both can use the other
-//
-
-// General creation and destruction
-static int RRTTL_init(s_RRTTL* self, PyObject* args);
-static void RRTTL_destroy(s_RRTTL* self);
+typedef CPPPyObjectContainer<s_RRTTL, RRTTL> RRTTLContainer;
-// These are the functions we export
-static PyObject* RRTTL_toText(s_RRTTL* self);
+PyObject* RRTTL_toText(s_RRTTL* self);
// This is a second version of toText, we need one where the argument
// is a PyObject*, for the str() function in python.
-static PyObject* RRTTL_str(PyObject* self);
-static PyObject* RRTTL_toWire(s_RRTTL* self, PyObject* args);
-static PyObject* RRTTL_getValue(s_RRTTL* self);
-static PyObject* RRTTL_richcmp(s_RRTTL* self, s_RRTTL* other, int op);
+PyObject* RRTTL_str(PyObject* self);
+PyObject* RRTTL_toWire(s_RRTTL* self, PyObject* args);
+PyObject* RRTTL_getValue(s_RRTTL* self);
+PyObject* RRTTL_richcmp(s_RRTTL* self, s_RRTTL* other, int op);
// This list contains the actual set of functions we have in
// python. Each entry has
@@ -70,7 +54,7 @@ static PyObject* RRTTL_richcmp(s_RRTTL* self, s_RRTTL* other, int op);
// 2. Our static function here
// 3. Argument type
// 4. Documentation
-static PyMethodDef RRTTL_methods[] = {
+PyMethodDef RRTTL_methods[] = {
{ "to_text", reinterpret_cast<PyCFunction>(RRTTL_toText), METH_NOARGS,
"Returns the string representation" },
{ "to_wire", reinterpret_cast<PyCFunction>(RRTTL_toWire), METH_VARARGS,
@@ -85,65 +69,7 @@ static PyMethodDef RRTTL_methods[] = {
{ NULL, NULL, 0, NULL }
};
-// This defines the complete type for reflection in python and
-// parsing of PyObject* to s_RRTTL
-// Most of the functions are not actually implemented and NULL here.
-static PyTypeObject rrttl_type = {
- PyVarObject_HEAD_INIT(NULL, 0)
- "pydnspp.RRTTL",
- sizeof(s_RRTTL), // tp_basicsize
- 0, // tp_itemsize
- (destructor)RRTTL_destroy, // tp_dealloc
- NULL, // tp_print
- NULL, // tp_getattr
- NULL, // tp_setattr
- NULL, // tp_reserved
- NULL, // tp_repr
- NULL, // tp_as_number
- NULL, // tp_as_sequence
- NULL, // tp_as_mapping
- NULL, // tp_hash
- NULL, // tp_call
- RRTTL_str, // tp_str
- NULL, // tp_getattro
- NULL, // tp_setattro
- NULL, // tp_as_buffer
- Py_TPFLAGS_DEFAULT, // tp_flags
- "The RRTTL class encapsulates TTLs used in DNS resource records.\n\n"
- "This is a straightforward class; an RRTTL object simply maintains a "
- "32-bit unsigned integer corresponding to the TTL value. The main purpose "
- "of this class is to provide convenient interfaces to convert a textual "
- "representation into the integer TTL value and vice versa, and to handle "
- "wire-format representations.",
- NULL, // tp_traverse
- NULL, // tp_clear
- (richcmpfunc)RRTTL_richcmp, // tp_richcompare
- 0, // tp_weaklistoffset
- NULL, // tp_iter
- NULL, // tp_iternext
- RRTTL_methods, // tp_methods
- NULL, // tp_members
- NULL, // tp_getset
- NULL, // tp_base
- NULL, // tp_dict
- NULL, // tp_descr_get
- NULL, // tp_descr_set
- 0, // tp_dictoffset
- (initproc)RRTTL_init, // tp_init
- NULL, // tp_alloc
- PyType_GenericNew, // tp_new
- NULL, // tp_free
- NULL, // tp_is_gc
- NULL, // tp_bases
- NULL, // tp_mro
- NULL, // tp_cache
- NULL, // tp_subclasses
- NULL, // tp_weaklist
- NULL, // tp_del
- 0 // tp_version_tag
-};
-
-static int
+int
RRTTL_init(s_RRTTL* self, PyObject* args) {
const char* s;
long long i;
@@ -157,7 +83,7 @@ RRTTL_init(s_RRTTL* self, PyObject* args) {
// (the way to do exceptions is to set PyErr and return -1)
try {
if (PyArg_ParseTuple(args, "s", &s)) {
- self->rrttl = new RRTTL(s);
+ self->cppobj = new RRTTL(s);
return (0);
} else if (PyArg_ParseTuple(args, "L", &i)) {
PyErr_Clear();
@@ -165,7 +91,7 @@ RRTTL_init(s_RRTTL* self, PyObject* args) {
PyErr_SetString(PyExc_ValueError, "RR TTL number out of range");
return (-1);
}
- self->rrttl = new RRTTL(i);
+ self->cppobj = new RRTTL(i);
return (0);
} else if (PyArg_ParseTuple(args, "O", &bytes) &&
PySequence_Check(bytes)) {
@@ -176,7 +102,7 @@ RRTTL_init(s_RRTTL* self, PyObject* args) {
return (result);
}
InputBuffer ib(&data[0], size);
- self->rrttl = new RRTTL(ib);
+ self->cppobj = new RRTTL(ib);
PyErr_Clear();
return (0);
}
@@ -200,20 +126,20 @@ RRTTL_init(s_RRTTL* self, PyObject* args) {
return (-1);
}
-static void
+void
RRTTL_destroy(s_RRTTL* self) {
- delete self->rrttl;
- self->rrttl = NULL;
+ delete self->cppobj;
+ self->cppobj = NULL;
Py_TYPE(self)->tp_free(self);
}
-static PyObject*
+PyObject*
RRTTL_toText(s_RRTTL* self) {
// Py_BuildValue makes python objects from native data
- return (Py_BuildValue("s", self->rrttl->toText().c_str()));
+ return (Py_BuildValue("s", self->cppobj->toText().c_str()));
}
-static PyObject*
+PyObject*
RRTTL_str(PyObject* self) {
// Simply call the to_text method we already defined
return (PyObject_CallMethod(self,
@@ -221,16 +147,16 @@ RRTTL_str(PyObject* self) {
const_cast<char*>("")));
}
-static PyObject*
+PyObject*
RRTTL_toWire(s_RRTTL* self, PyObject* args) {
PyObject* bytes;
- s_MessageRenderer* mr;
-
+ PyObject* mr;
+
if (PyArg_ParseTuple(args, "O", &bytes) && PySequence_Check(bytes)) {
PyObject* bytes_o = bytes;
-
+
OutputBuffer buffer(4);
- self->rrttl->toWire(buffer);
+ self->cppobj->toWire(buffer);
PyObject* n = PyBytes_FromStringAndSize(static_cast<const char*>(buffer.getData()),
buffer.getLength());
PyObject* result = PySequence_InPlaceConcat(bytes_o, n);
@@ -239,7 +165,7 @@ RRTTL_toWire(s_RRTTL* self, PyObject* args) {
Py_DECREF(n);
return (result);
} else if (PyArg_ParseTuple(args, "O!", &messagerenderer_type, &mr)) {
- self->rrttl->toWire(*mr->messagerenderer);
+ self->cppobj->toWire(PyMessageRenderer_ToMessageRenderer(mr));
// If we return NULL it is seen as an error, so use this for
// None returns
Py_RETURN_NONE;
@@ -250,12 +176,12 @@ RRTTL_toWire(s_RRTTL* self, PyObject* args) {
return (NULL);
}
-static PyObject*
+PyObject*
RRTTL_getValue(s_RRTTL* self) {
- return (Py_BuildValue("I", self->rrttl->getValue()));
+ return (Py_BuildValue("I", self->cppobj->getValue()));
}
-static PyObject*
+PyObject*
RRTTL_richcmp(s_RRTTL* self, s_RRTTL* other, int op) {
bool c = false;
@@ -267,24 +193,24 @@ RRTTL_richcmp(s_RRTTL* self, s_RRTTL* other, int op) {
switch (op) {
case Py_LT:
- c = *self->rrttl < *other->rrttl;
+ c = *self->cppobj < *other->cppobj;
break;
case Py_LE:
- c = *self->rrttl < *other->rrttl ||
- *self->rrttl == *other->rrttl;
+ c = *self->cppobj < *other->cppobj ||
+ *self->cppobj == *other->cppobj;
break;
case Py_EQ:
- c = *self->rrttl == *other->rrttl;
+ c = *self->cppobj == *other->cppobj;
break;
case Py_NE:
- c = *self->rrttl != *other->rrttl;
+ c = *self->cppobj != *other->cppobj;
break;
case Py_GT:
- c = *other->rrttl < *self->rrttl;
+ c = *other->cppobj < *self->cppobj;
break;
case Py_GE:
- c = *other->rrttl < *self->rrttl ||
- *self->rrttl == *other->rrttl;
+ c = *other->cppobj < *self->cppobj ||
+ *self->cppobj == *other->cppobj;
break;
}
if (c)
@@ -292,27 +218,104 @@ RRTTL_richcmp(s_RRTTL* self, s_RRTTL* other, int op) {
else
Py_RETURN_FALSE;
}
-// end of RRTTL
+} // end anonymous namespace
+
+namespace isc {
+namespace dns {
+namespace python {
+
+//
+// Declaration of the custom exceptions
+// Initialization and addition of these go in the initModulePart
+// function in pydnspp.cc
+//
+PyObject* po_InvalidRRTTL;
+PyObject* po_IncompleteRRTTL;
+
+// This defines the complete type for reflection in python and
+// parsing of PyObject* to s_RRTTL
+// Most of the functions are not actually implemented and NULL here.
+PyTypeObject rrttl_type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "pydnspp.RRTTL",
+ sizeof(s_RRTTL), // tp_basicsize
+ 0, // tp_itemsize
+ (destructor)RRTTL_destroy, // tp_dealloc
+ NULL, // tp_print
+ NULL, // tp_getattr
+ NULL, // tp_setattr
+ NULL, // tp_reserved
+ NULL, // tp_repr
+ NULL, // tp_as_number
+ NULL, // tp_as_sequence
+ NULL, // tp_as_mapping
+ NULL, // tp_hash
+ NULL, // tp_call
+ RRTTL_str, // tp_str
+ NULL, // tp_getattro
+ NULL, // tp_setattro
+ NULL, // tp_as_buffer
+ Py_TPFLAGS_DEFAULT, // tp_flags
+ "The RRTTL class encapsulates TTLs used in DNS resource records.\n\n"
+ "This is a straightforward class; an RRTTL object simply maintains a "
+ "32-bit unsigned integer corresponding to the TTL value. The main purpose "
+ "of this class is to provide convenient interfaces to convert a textual "
+ "representation into the integer TTL value and vice versa, and to handle "
+ "wire-format representations.",
+ NULL, // tp_traverse
+ NULL, // tp_clear
+ (richcmpfunc)RRTTL_richcmp, // tp_richcompare
+ 0, // tp_weaklistoffset
+ NULL, // tp_iter
+ NULL, // tp_iternext
+ RRTTL_methods, // tp_methods
+ NULL, // tp_members
+ NULL, // tp_getset
+ NULL, // tp_base
+ NULL, // tp_dict
+ NULL, // tp_descr_get
+ NULL, // tp_descr_set
+ 0, // tp_dictoffset
+ (initproc)RRTTL_init, // tp_init
+ NULL, // tp_alloc
+ PyType_GenericNew, // tp_new
+ NULL, // tp_free
+ NULL, // tp_is_gc
+ NULL, // tp_bases
+ NULL, // tp_mro
+ NULL, // tp_cache
+ NULL, // tp_subclasses
+ NULL, // tp_weaklist
+ NULL, // tp_del
+ 0 // tp_version_tag
+};
+
+PyObject*
+createRRTTLObject(const RRTTL& source) {
+ RRTTLContainer container(PyObject_New(s_RRTTL, &rrttl_type));
+ container.set(new RRTTL(source));
+ return (container.release());
+}
-// Module Initialization, all statics are initialized here
bool
-initModulePart_RRTTL(PyObject* mod) {
- // Add the exceptions to the module
- po_InvalidRRTTL = PyErr_NewException("pydnspp.InvalidRRTTL", NULL, NULL);
- PyModule_AddObject(mod, "InvalidRRTTL", po_InvalidRRTTL);
- po_IncompleteRRTTL = PyErr_NewException("pydnspp.IncompleteRRTTL", NULL, NULL);
- PyModule_AddObject(mod, "IncompleteRRTTL", po_IncompleteRRTTL);
+PyRRTTL_Check(PyObject* obj) {
+ if (obj == NULL) {
+ isc_throw(PyCPPWrapperException, "obj argument NULL in typecheck");
+ }
+ return (PyObject_TypeCheck(obj, &rrttl_type));
+}
- // We initialize the static description object with PyType_Ready(),
- // then add it to the module. This is not just a check! (leaving
- // this out results in segmentation faults)
- if (PyType_Ready(&rrttl_type) < 0) {
- return (false);
+const RRTTL&
+PyRRTTL_ToRRTTL(const PyObject* rrttl_obj) {
+ if (rrttl_obj == NULL) {
+ isc_throw(PyCPPWrapperException,
+ "obj argument NULL in RRTTL PyObject conversion");
}
- Py_INCREF(&rrttl_type);
- PyModule_AddObject(mod, "RRTTL",
- reinterpret_cast<PyObject*>(&rrttl_type));
-
- return (true);
+ const s_RRTTL* rrttl = static_cast<const s_RRTTL*>(rrttl_obj);
+ return (*rrttl->cppobj);
}
+
+} // namespace python
+} // namespace dns
+} // namespace isc
diff --git a/src/lib/dns/python/rrttl_python.h b/src/lib/dns/python/rrttl_python.h
new file mode 100644
index 0000000..9dbc982
--- /dev/null
+++ b/src/lib/dns/python/rrttl_python.h
@@ -0,0 +1,67 @@
+// 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.
+
+#ifndef __PYTHON_RRTTL_H
+#define __PYTHON_RRTTL_H 1
+
+#include <Python.h>
+
+namespace isc {
+namespace dns {
+class RRTTL;
+
+namespace python {
+
+extern PyObject* po_InvalidRRTTL;
+extern PyObject* po_IncompleteRRTTL;
+
+extern PyTypeObject rrttl_type;
+
+/// This is a simple shortcut to create a python RRTTL object (in the
+/// form of a pointer to PyObject) with minimal exception safety.
+/// On success, it returns a valid pointer to PyObject with a reference
+/// counter of 1; if something goes wrong it throws an exception (it never
+/// returns a NULL pointer).
+/// This function is expected to be called within a try block
+/// followed by necessary setup for python exception.
+PyObject* createRRTTLObject(const RRTTL& source);
+
+/// \brief Checks if the given python object is a RRTTL object
+///
+/// \exception PyCPPWrapperException if obj is NULL
+///
+/// \param obj The object to check the type of
+/// \return true if the object is of type RRTTL, false otherwise
+bool PyRRTTL_Check(PyObject* obj);
+
+/// \brief Returns a reference to the RRTTL object contained within the given
+/// Python object.
+///
+/// \note The given object MUST be of type RRTTL; this can be checked with
+/// either the right call to ParseTuple("O!"), or with PyRRTTL_Check()
+///
+/// \note This is not a copy; if the RRTTL is needed when the PyObject
+/// may be destroyed, the caller must copy it itself.
+///
+/// \param rrttl_obj The rrttl object to convert
+const RRTTL& PyRRTTL_ToRRTTL(const PyObject* rrttl_obj);
+
+} // namespace python
+} // namespace dns
+} // namespace isc
+#endif // __PYTHON_RRTTL_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/python/rrtype_python.cc b/src/lib/dns/python/rrtype_python.cc
index 00e0acd..bf20b7c 100644
--- a/src/lib/dns/python/rrtype_python.cc
+++ b/src/lib/dns/python/rrtype_python.cc
@@ -12,77 +12,64 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
+#include <Python.h>
#include <vector>
#include <dns/rrtype.h>
+#include <dns/messagerenderer.h>
+#include <util/python/pycppwrapper_util.h>
+
+#include "rrtype_python.h"
+#include "messagerenderer_python.h"
+#include "pydnspp_common.h"
using namespace std;
using namespace isc::dns;
+using namespace isc::dns::python;
using namespace isc::util;
+using namespace isc::util::python;
-//
-// Declaration of the custom exceptions
-// Initialization and addition of these go in the initModulePart
-// function at the end of this file
-//
-static PyObject* po_InvalidRRType;
-static PyObject* po_IncompleteRRType;
-
-//
-// Definition of the classes
-//
-
-// For each class, we need a struct, a helper functions (init, destroy,
-// and static wrappers around the methods we export), a list of methods,
-// and a type description
-
-//
-// RRType
-//
-
+namespace {
// The s_* Class simply covers one instantiation of the object
class s_RRType : public PyObject {
public:
- const RRType* rrtype;
+ const RRType* cppobj;
};
-//
-// We declare the functions here, the definitions are below
-// the type definition of the object, since both can use the other
-//
-
// General creation and destruction
-static int RRType_init(s_RRType* self, PyObject* args);
-static void RRType_destroy(s_RRType* self);
+int RRType_init(s_RRType* self, PyObject* args);
+void RRType_destroy(s_RRType* self);
// These are the functions we export
-static PyObject*
+PyObject*
RRType_toText(s_RRType* self);
// This is a second version of toText, we need one where the argument
// is a PyObject*, for the str() function in python.
-static PyObject* RRType_str(PyObject* self);
-static PyObject* RRType_toWire(s_RRType* self, PyObject* args);
-static PyObject* RRType_getCode(s_RRType* self);
-static PyObject* RRType_richcmp(s_RRType* self, s_RRType* other, int op);
-static PyObject* RRType_NSEC3PARAM(s_RRType *self);
-static PyObject* RRType_DNAME(s_RRType *self);
-static PyObject* RRType_PTR(s_RRType *self);
-static PyObject* RRType_MX(s_RRType *self);
-static PyObject* RRType_DNSKEY(s_RRType *self);
-static PyObject* RRType_TXT(s_RRType *self);
-static PyObject* RRType_RRSIG(s_RRType *self);
-static PyObject* RRType_NSEC(s_RRType *self);
-static PyObject* RRType_AAAA(s_RRType *self);
-static PyObject* RRType_DS(s_RRType *self);
-static PyObject* RRType_OPT(s_RRType *self);
-static PyObject* RRType_A(s_RRType *self);
-static PyObject* RRType_NS(s_RRType *self);
-static PyObject* RRType_CNAME(s_RRType *self);
-static PyObject* RRType_SOA(s_RRType *self);
-static PyObject* RRType_NSEC3(s_RRType *self);
-static PyObject* RRType_IXFR(s_RRType *self);
-static PyObject* RRType_AXFR(s_RRType *self);
-static PyObject* RRType_ANY(s_RRType *self);
+PyObject* RRType_str(PyObject* self);
+PyObject* RRType_toWire(s_RRType* self, PyObject* args);
+PyObject* RRType_getCode(s_RRType* self);
+PyObject* RRType_richcmp(s_RRType* self, s_RRType* other, int op);
+PyObject* RRType_NSEC3PARAM(s_RRType *self);
+PyObject* RRType_DNAME(s_RRType *self);
+PyObject* RRType_PTR(s_RRType *self);
+PyObject* RRType_MX(s_RRType *self);
+PyObject* RRType_DNSKEY(s_RRType *self);
+PyObject* RRType_TXT(s_RRType *self);
+PyObject* RRType_RRSIG(s_RRType *self);
+PyObject* RRType_NSEC(s_RRType *self);
+PyObject* RRType_AAAA(s_RRType *self);
+PyObject* RRType_DS(s_RRType *self);
+PyObject* RRType_OPT(s_RRType *self);
+PyObject* RRType_A(s_RRType *self);
+PyObject* RRType_NS(s_RRType *self);
+PyObject* RRType_CNAME(s_RRType *self);
+PyObject* RRType_SOA(s_RRType *self);
+PyObject* RRType_NSEC3(s_RRType *self);
+PyObject* RRType_IXFR(s_RRType *self);
+PyObject* RRType_AXFR(s_RRType *self);
+PyObject* RRType_ANY(s_RRType *self);
+
+typedef CPPPyObjectContainer<s_RRType, RRType> RRTypeContainer;
// This list contains the actual set of functions we have in
// python. Each entry has
@@ -90,7 +77,7 @@ static PyObject* RRType_ANY(s_RRType *self);
// 2. Our static function here
// 3. Argument type
// 4. Documentation
-static PyMethodDef RRType_methods[] = {
+PyMethodDef RRType_methods[] = {
{ "to_text", reinterpret_cast<PyCFunction>(RRType_toText), METH_NOARGS,
"Returns the string representation" },
{ "to_wire", reinterpret_cast<PyCFunction>(RRType_toWire), METH_VARARGS,
@@ -124,63 +111,7 @@ static PyMethodDef RRType_methods[] = {
{ NULL, NULL, 0, NULL }
};
-// This defines the complete type for reflection in python and
-// parsing of PyObject* to s_RRType
-// Most of the functions are not actually implemented and NULL here.
-static PyTypeObject rrtype_type = {
- PyVarObject_HEAD_INIT(NULL, 0)
- "pydnspp.RRType",
- sizeof(s_RRType), // tp_basicsize
- 0, // tp_itemsize
- (destructor)RRType_destroy, // tp_dealloc
- NULL, // tp_print
- NULL, // tp_getattr
- NULL, // tp_setattr
- NULL, // tp_reserved
- NULL, // tp_repr
- NULL, // tp_as_number
- NULL, // tp_as_sequence
- NULL, // tp_as_mapping
- NULL, // tp_hash
- NULL, // tp_call
- RRType_str, // tp_str
- NULL, // tp_getattro
- NULL, // tp_setattro
- NULL, // tp_as_buffer
- Py_TPFLAGS_DEFAULT, // tp_flags
- "The RRType class encapsulates DNS resource record types.\n\n"
- "This class manages the 16-bit integer type codes in quite a straightforward "
- "way. The only non trivial task is to handle textual representations of "
- "RR types, such as \"A\", \"AAAA\", or \"TYPE65534\".",
- NULL, // tp_traverse
- NULL, // tp_clear
- (richcmpfunc)RRType_richcmp, // tp_richcompare
- 0, // tp_weaklistoffset
- NULL, // tp_iter
- NULL, // tp_iternext
- RRType_methods, // tp_methods
- NULL, // tp_members
- NULL, // tp_getset
- NULL, // tp_base
- NULL, // tp_dict
- NULL, // tp_descr_get
- NULL, // tp_descr_set
- 0, // tp_dictoffset
- (initproc)RRType_init, // tp_init
- NULL, // tp_alloc
- PyType_GenericNew, // tp_new
- NULL, // tp_free
- NULL, // tp_is_gc
- NULL, // tp_bases
- NULL, // tp_mro
- NULL, // tp_cache
- NULL, // tp_subclasses
- NULL, // tp_weaklist
- NULL, // tp_del
- 0 // tp_version_tag
-};
-
-static int
+int
RRType_init(s_RRType* self, PyObject* args) {
const char* s;
long i;
@@ -194,7 +125,7 @@ RRType_init(s_RRType* self, PyObject* args) {
// (the way to do exceptions is to set PyErr and return -1)
try {
if (PyArg_ParseTuple(args, "s", &s)) {
- self->rrtype = new RRType(s);
+ self->cppobj = new RRType(s);
return (0);
} else if (PyArg_ParseTuple(args, "l", &i)) {
PyErr_Clear();
@@ -202,7 +133,7 @@ RRType_init(s_RRType* self, PyObject* args) {
PyErr_SetString(PyExc_ValueError, "RR Type number out of range");
return (-1);
}
- self->rrtype = new RRType(i);
+ self->cppobj = new RRType(i);
return (0);
} else if (PyArg_ParseTuple(args, "O", &bytes) && PySequence_Check(bytes)) {
Py_ssize_t size = PySequence_Size(bytes);
@@ -212,7 +143,7 @@ RRType_init(s_RRType* self, PyObject* args) {
return (result);
}
InputBuffer ib(&data[0], size);
- self->rrtype = new RRType(ib);
+ self->cppobj = new RRType(ib);
PyErr_Clear();
return (0);
}
@@ -236,36 +167,36 @@ RRType_init(s_RRType* self, PyObject* args) {
return (-1);
}
-static void
+void
RRType_destroy(s_RRType* self) {
- delete self->rrtype;
- self->rrtype = NULL;
+ delete self->cppobj;
+ self->cppobj = NULL;
Py_TYPE(self)->tp_free(self);
}
-static PyObject*
+PyObject*
RRType_toText(s_RRType* self) {
// Py_BuildValue makes python objects from native data
- return (Py_BuildValue("s", self->rrtype->toText().c_str()));
+ return (Py_BuildValue("s", self->cppobj->toText().c_str()));
}
-static PyObject*
+PyObject*
RRType_str(PyObject* self) {
// Simply call the to_text method we already defined
return (PyObject_CallMethod(self, const_cast<char*>("to_text"),
const_cast<char*>("")));
}
-static PyObject*
+PyObject*
RRType_toWire(s_RRType* self, PyObject* args) {
PyObject* bytes;
- s_MessageRenderer* mr;
+ PyObject* mr;
if (PyArg_ParseTuple(args, "O", &bytes) && PySequence_Check(bytes)) {
PyObject* bytes_o = bytes;
OutputBuffer buffer(2);
- self->rrtype->toWire(buffer);
+ self->cppobj->toWire(buffer);
PyObject* n = PyBytes_FromStringAndSize(static_cast<const char*>(buffer.getData()), buffer.getLength());
PyObject* result = PySequence_InPlaceConcat(bytes_o, n);
// We need to release the object we temporarily created here
@@ -273,7 +204,7 @@ RRType_toWire(s_RRType* self, PyObject* args) {
Py_DECREF(n);
return (result);
} else if (PyArg_ParseTuple(args, "O!", &messagerenderer_type, &mr)) {
- self->rrtype->toWire(*mr->messagerenderer);
+ self->cppobj->toWire(PyMessageRenderer_ToMessageRenderer(mr));
// If we return NULL it is seen as an error, so use this for
// None returns
Py_RETURN_NONE;
@@ -284,12 +215,12 @@ RRType_toWire(s_RRType* self, PyObject* args) {
return (NULL);
}
-static PyObject*
+PyObject*
RRType_getCode(s_RRType* self) {
- return (Py_BuildValue("I", self->rrtype->getCode()));
+ return (Py_BuildValue("I", self->cppobj->getCode()));
}
-static PyObject*
+PyObject*
RRType_richcmp(s_RRType* self, s_RRType* other, int op) {
bool c;
@@ -301,24 +232,24 @@ RRType_richcmp(s_RRType* self, s_RRType* other, int op) {
switch (op) {
case Py_LT:
- c = *self->rrtype < *other->rrtype;
+ c = *self->cppobj < *other->cppobj;
break;
case Py_LE:
- c = *self->rrtype < *other->rrtype ||
- *self->rrtype == *other->rrtype;
+ c = *self->cppobj < *other->cppobj ||
+ *self->cppobj == *other->cppobj;
break;
case Py_EQ:
- c = *self->rrtype == *other->rrtype;
+ c = *self->cppobj == *other->cppobj;
break;
case Py_NE:
- c = *self->rrtype != *other->rrtype;
+ c = *self->cppobj != *other->cppobj;
break;
case Py_GT:
- c = *other->rrtype < *self->rrtype;
+ c = *other->cppobj < *self->cppobj;
break;
case Py_GE:
- c = *other->rrtype < *self->rrtype ||
- *self->rrtype == *other->rrtype;
+ c = *other->cppobj < *self->cppobj ||
+ *self->cppobj == *other->cppobj;
break;
default:
PyErr_SetString(PyExc_IndexError,
@@ -334,131 +265,200 @@ RRType_richcmp(s_RRType* self, s_RRType* other, int op) {
//
// Common function for RRType_A/NS/etc.
//
-static PyObject* RRType_createStatic(RRType stc) {
+PyObject* RRType_createStatic(RRType stc) {
s_RRType* ret = PyObject_New(s_RRType, &rrtype_type);
if (ret != NULL) {
- ret->rrtype = new RRType(stc);
+ ret->cppobj = new RRType(stc);
}
return (ret);
}
-static PyObject*
+PyObject*
RRType_NSEC3PARAM(s_RRType*) {
return (RRType_createStatic(RRType::NSEC3PARAM()));
}
-static PyObject*
+PyObject*
RRType_DNAME(s_RRType*) {
return (RRType_createStatic(RRType::DNAME()));
}
-static PyObject*
+PyObject*
RRType_PTR(s_RRType*) {
return (RRType_createStatic(RRType::PTR()));
}
-static PyObject*
+PyObject*
RRType_MX(s_RRType*) {
return (RRType_createStatic(RRType::MX()));
}
-static PyObject*
+PyObject*
RRType_DNSKEY(s_RRType*) {
return (RRType_createStatic(RRType::DNSKEY()));
}
-static PyObject*
+PyObject*
RRType_TXT(s_RRType*) {
return (RRType_createStatic(RRType::TXT()));
}
-static PyObject*
+PyObject*
RRType_RRSIG(s_RRType*) {
return (RRType_createStatic(RRType::RRSIG()));
}
-static PyObject*
+PyObject*
RRType_NSEC(s_RRType*) {
return (RRType_createStatic(RRType::NSEC()));
}
-static PyObject*
+PyObject*
RRType_AAAA(s_RRType*) {
return (RRType_createStatic(RRType::AAAA()));
}
-static PyObject*
+PyObject*
RRType_DS(s_RRType*) {
return (RRType_createStatic(RRType::DS()));
}
-static PyObject*
+PyObject*
RRType_OPT(s_RRType*) {
return (RRType_createStatic(RRType::OPT()));
}
-static PyObject*
+PyObject*
RRType_A(s_RRType*) {
return (RRType_createStatic(RRType::A()));
}
-static PyObject*
+PyObject*
RRType_NS(s_RRType*) {
return (RRType_createStatic(RRType::NS()));
}
-static PyObject*
+PyObject*
RRType_CNAME(s_RRType*) {
return (RRType_createStatic(RRType::CNAME()));
}
-static PyObject*
+PyObject*
RRType_SOA(s_RRType*) {
return (RRType_createStatic(RRType::SOA()));
}
-static PyObject*
+PyObject*
RRType_NSEC3(s_RRType*) {
return (RRType_createStatic(RRType::NSEC3()));
}
-static PyObject*
+PyObject*
RRType_IXFR(s_RRType*) {
return (RRType_createStatic(RRType::IXFR()));
}
-static PyObject*
+PyObject*
RRType_AXFR(s_RRType*) {
return (RRType_createStatic(RRType::AXFR()));
}
-static PyObject*
+PyObject*
RRType_ANY(s_RRType*) {
return (RRType_createStatic(RRType::ANY()));
}
+} // end anonymous namespace
+
+namespace isc {
+namespace dns {
+namespace python {
-// end of RRType
+PyObject* po_InvalidRRType;
+PyObject* po_IncompleteRRType;
+
+// This defines the complete type for reflection in python and
+// parsing of PyObject* to s_RRType
+// Most of the functions are not actually implemented and NULL here.
+PyTypeObject rrtype_type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "pydnspp.RRType",
+ sizeof(s_RRType), // tp_basicsize
+ 0, // tp_itemsize
+ (destructor)RRType_destroy, // tp_dealloc
+ NULL, // tp_print
+ NULL, // tp_getattr
+ NULL, // tp_setattr
+ NULL, // tp_reserved
+ NULL, // tp_repr
+ NULL, // tp_as_number
+ NULL, // tp_as_sequence
+ NULL, // tp_as_mapping
+ NULL, // tp_hash
+ NULL, // tp_call
+ RRType_str, // tp_str
+ NULL, // tp_getattro
+ NULL, // tp_setattro
+ NULL, // tp_as_buffer
+ Py_TPFLAGS_DEFAULT, // tp_flags
+ "The RRType class encapsulates DNS resource record types.\n\n"
+ "This class manages the 16-bit integer type codes in quite a straightforward "
+ "way. The only non trivial task is to handle textual representations of "
+ "RR types, such as \"A\", \"AAAA\", or \"TYPE65534\".",
+ NULL, // tp_traverse
+ NULL, // tp_clear
+ (richcmpfunc)RRType_richcmp, // tp_richcompare
+ 0, // tp_weaklistoffset
+ NULL, // tp_iter
+ NULL, // tp_iternext
+ RRType_methods, // tp_methods
+ NULL, // tp_members
+ NULL, // tp_getset
+ NULL, // tp_base
+ NULL, // tp_dict
+ NULL, // tp_descr_get
+ NULL, // tp_descr_set
+ 0, // tp_dictoffset
+ (initproc)RRType_init, // tp_init
+ NULL, // tp_alloc
+ PyType_GenericNew, // tp_new
+ NULL, // tp_free
+ NULL, // tp_is_gc
+ NULL, // tp_bases
+ NULL, // tp_mro
+ NULL, // tp_cache
+ NULL, // tp_subclasses
+ NULL, // tp_weaklist
+ NULL, // tp_del
+ 0 // tp_version_tag
+};
+PyObject*
+createRRTypeObject(const RRType& source) {
+ RRTypeContainer container(PyObject_New(s_RRType, &rrtype_type));
+ container.set(new RRType(source));
+ return (container.release());
+}
-// Module Initialization, all statics are initialized here
bool
-initModulePart_RRType(PyObject* mod) {
- // Add the exceptions to the module
- po_InvalidRRType = PyErr_NewException("pydnspp.InvalidRRType", NULL, NULL);
- PyModule_AddObject(mod, "InvalidRRType", po_InvalidRRType);
- po_IncompleteRRType = PyErr_NewException("pydnspp.IncompleteRRType", NULL, NULL);
- PyModule_AddObject(mod, "IncompleteRRType", po_IncompleteRRType);
-
- // We initialize the static description object with PyType_Ready(),
- // then add it to the module. This is not just a check! (leaving
- // this out results in segmentation faults)
- if (PyType_Ready(&rrtype_type) < 0) {
- return (false);
+PyRRType_Check(PyObject* obj) {
+ if (obj == NULL) {
+ isc_throw(PyCPPWrapperException, "obj argument NULL in typecheck");
+ }
+ return (PyObject_TypeCheck(obj, &rrtype_type));
+}
+
+const RRType&
+PyRRType_ToRRType(const PyObject* rrtype_obj) {
+ if (rrtype_obj == NULL) {
+ isc_throw(PyCPPWrapperException,
+ "obj argument NULL in RRType PyObject conversion");
}
- Py_INCREF(&rrtype_type);
- PyModule_AddObject(mod, "RRType",
- reinterpret_cast<PyObject*>(&rrtype_type));
-
- return (true);
+ const s_RRType* rrtype = static_cast<const s_RRType*>(rrtype_obj);
+ return (*rrtype->cppobj);
}
+
+
+} // end namespace python
+} // end namespace dns
+} // end namespace isc
diff --git a/src/lib/dns/python/rrtype_python.h b/src/lib/dns/python/rrtype_python.h
new file mode 100644
index 0000000..596598e
--- /dev/null
+++ b/src/lib/dns/python/rrtype_python.h
@@ -0,0 +1,68 @@
+// 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.
+
+#ifndef __PYTHON_RRTYPE_H
+#define __PYTHON_RRTYPE_H 1
+
+#include <Python.h>
+
+namespace isc {
+namespace dns {
+class RRType;
+
+namespace python {
+
+extern PyObject* po_InvalidRRType;
+extern PyObject* po_IncompleteRRType;
+
+extern PyTypeObject rrtype_type;
+
+/// This is a simple shortcut to create a python RRType object (in the
+/// form of a pointer to PyObject) with minimal exception safety.
+/// On success, it returns a valid pointer to PyObject with a reference
+/// counter of 1; if something goes wrong it throws an exception (it never
+/// returns a NULL pointer).
+/// This function is expected to be called within a try block
+/// followed by necessary setup for python exception.
+PyObject* createRRTypeObject(const RRType& source);
+
+/// \brief Checks if the given python object is a RRType object
+///
+/// \exception PyCPPWrapperException if obj is NULL
+///
+/// \param obj The object to check the type of
+/// \return true if the object is of type RRType, false otherwise
+bool PyRRType_Check(PyObject* obj);
+
+/// \brief Returns a reference to the RRType object contained within the given
+/// Python object.
+///
+/// \note The given object MUST be of type RRType; this can be checked with
+/// either the right call to ParseTuple("O!"), or with PyRRType_Check()
+///
+/// \note This is not a copy; if the RRType is needed when the PyObject
+/// may be destroyed, the caller must copy it itself.
+///
+/// \param rrtype_obj The rrtype object to convert
+const RRType& PyRRType_ToRRType(const PyObject* rrtype_obj);
+
+
+} // namespace python
+} // namespace dns
+} // namespace isc
+#endif // __PYTHON_RRTYPE_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/python/tests/Makefile.am b/src/lib/dns/python/tests/Makefile.am
index 61d7df6..d1273f3 100644
--- a/src/lib/dns/python/tests/Makefile.am
+++ b/src/lib/dns/python/tests/Makefile.am
@@ -24,7 +24,7 @@ EXTRA_DIST += testutil.py
# required by loadable python modules.
LIBRARY_PATH_PLACEHOLDER =
if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$$$(ENV_LIBRARY_PATH)
endif
# test using command-line arguments, so use check-local target instead of TESTS
diff --git a/src/lib/dns/python/tsig_python.cc b/src/lib/dns/python/tsig_python.cc
index db93a08..0764e33 100644
--- a/src/lib/dns/python/tsig_python.cc
+++ b/src/lib/dns/python/tsig_python.cc
@@ -37,23 +37,18 @@ using namespace isc::util::python;
using namespace isc::dns;
using namespace isc::dns::python;
-//
-// Definition of the classes
-//
-
// For each class, we need a struct, a helper functions (init, destroy,
// and static wrappers around the methods we export), a list of methods,
// and a type description
-//
-// TSIGContext
-//
-
-// Trivial constructor.
-s_TSIGContext::s_TSIGContext() : cppobj(NULL) {
-}
-
namespace {
+// The s_* Class simply covers one instantiation of the object
+class s_TSIGContext : public PyObject {
+public:
+ s_TSIGContext() : cppobj(NULL) {};
+ TSIGContext* cppobj;
+};
+
// Shortcut type which would be convenient for adding class variables safely.
typedef CPPPyObjectContainer<s_TSIGContext, TSIGContext> TSIGContextContainer;
@@ -101,23 +96,23 @@ int
TSIGContext_init(s_TSIGContext* self, PyObject* args) {
try {
// "From key" constructor
- const s_TSIGKey* tsigkey_obj;
+ const PyObject* tsigkey_obj;
if (PyArg_ParseTuple(args, "O!", &tsigkey_type, &tsigkey_obj)) {
- self->cppobj = new TSIGContext(*tsigkey_obj->cppobj);
+ self->cppobj = new TSIGContext(PyTSIGKey_ToTSIGKey(tsigkey_obj));
return (0);
}
// "From key param + keyring" constructor
PyErr_Clear();
- const s_Name* keyname_obj;
- const s_Name* algname_obj;
- const s_TSIGKeyRing* keyring_obj;
+ const PyObject* keyname_obj;
+ const PyObject* algname_obj;
+ const PyObject* keyring_obj;
if (PyArg_ParseTuple(args, "O!O!O!", &name_type, &keyname_obj,
&name_type, &algname_obj, &tsigkeyring_type,
&keyring_obj)) {
- self->cppobj = new TSIGContext(*keyname_obj->cppobj,
- *algname_obj->cppobj,
- *keyring_obj->cppobj);
+ self->cppobj = new TSIGContext(PyName_ToName(keyname_obj),
+ PyName_ToName(algname_obj),
+ PyTSIGKeyRing_ToTSIGKeyRing(keyring_obj));
return (0);
}
} catch (const exception& ex) {
@@ -153,7 +148,7 @@ PyObject*
TSIGContext_getError(s_TSIGContext* self) {
try {
PyObjectContainer container(createTSIGErrorObject(
- self->cppobj->getError()));
+ self->cppobj->getError()));
return (Py_BuildValue("O", container.get()));
} catch (const exception& ex) {
const string ex_what =
@@ -205,13 +200,13 @@ PyObject*
TSIGContext_verify(s_TSIGContext* self, PyObject* args) {
const char* data;
Py_ssize_t data_len;
- s_TSIGRecord* py_record;
+ PyObject* py_record;
PyObject* py_maybe_none;
- TSIGRecord* record;
+ const TSIGRecord* record;
if (PyArg_ParseTuple(args, "O!y#", &tsigrecord_type, &py_record,
&data, &data_len)) {
- record = py_record->cppobj;
+ record = &PyTSIGRecord_ToTSIGRecord(py_record);
} else if (PyArg_ParseTuple(args, "Oy#", &py_maybe_none, &data,
&data_len)) {
record = NULL;
@@ -264,7 +259,7 @@ PyTypeObject tsigcontext_type = {
NULL, // tp_as_number
NULL, // tp_as_sequence
NULL, // tp_as_mapping
- NULL, // tp_hash
+ NULL, // tp_hash
NULL, // tp_call
NULL, // tp_str
NULL, // tp_getattro
@@ -307,58 +302,24 @@ PyTypeObject tsigcontext_type = {
0 // tp_version_tag
};
-// Module Initialization, all statics are initialized here
bool
-initModulePart_TSIGContext(PyObject* mod) {
- // We initialize the static description object with PyType_Ready(),
- // then add it to the module. This is not just a check! (leaving
- // this out results in segmentation faults)
- if (PyType_Ready(&tsigcontext_type) < 0) {
- return (false);
+PyTSIGContext_Check(PyObject* obj) {
+ if (obj == NULL) {
+ isc_throw(PyCPPWrapperException, "obj argument NULL in typecheck");
}
- void* p = &tsigcontext_type;
- if (PyModule_AddObject(mod, "TSIGContext",
- static_cast<PyObject*>(p)) < 0) {
- return (false);
- }
- Py_INCREF(&tsigcontext_type);
+ return (PyObject_TypeCheck(obj, &tsigcontext_type));
+}
- try {
- // Class specific exceptions
- po_TSIGContextError = PyErr_NewException("pydnspp.TSIGContextError",
- po_IscException, NULL);
- PyObjectContainer(po_TSIGContextError).installToModule(
- mod, "TSIGContextError");
-
- // Constant class variables
- installClassVariable(tsigcontext_type, "STATE_INIT",
- Py_BuildValue("I", TSIGContext::INIT));
- installClassVariable(tsigcontext_type, "STATE_SENT_REQUEST",
- Py_BuildValue("I", TSIGContext::SENT_REQUEST));
- installClassVariable(tsigcontext_type, "STATE_RECEIVED_REQUEST",
- Py_BuildValue("I", TSIGContext::RECEIVED_REQUEST));
- installClassVariable(tsigcontext_type, "STATE_SENT_RESPONSE",
- Py_BuildValue("I", TSIGContext::SENT_RESPONSE));
- installClassVariable(tsigcontext_type, "STATE_VERIFIED_RESPONSE",
- Py_BuildValue("I",
- TSIGContext::VERIFIED_RESPONSE));
-
- installClassVariable(tsigcontext_type, "DEFAULT_FUDGE",
- Py_BuildValue("H", TSIGContext::DEFAULT_FUDGE));
- } catch (const exception& ex) {
- const string ex_what =
- "Unexpected failure in TSIGContext initialization: " +
- string(ex.what());
- PyErr_SetString(po_IscException, ex_what.c_str());
- return (false);
- } catch (...) {
- PyErr_SetString(PyExc_SystemError,
- "Unexpected failure in TSIGContext initialization");
- return (false);
+TSIGContext&
+PyTSIGContext_ToTSIGContext(PyObject* tsigcontext_obj) {
+ if (tsigcontext_obj == NULL) {
+ isc_throw(PyCPPWrapperException,
+ "obj argument NULL in TSIGContext PyObject conversion");
}
-
- return (true);
+ s_TSIGContext* tsigcontext = static_cast<s_TSIGContext*>(tsigcontext_obj);
+ return (*tsigcontext->cppobj);
}
+
} // namespace python
} // namespace dns
} // namespace isc
diff --git a/src/lib/dns/python/tsig_python.h b/src/lib/dns/python/tsig_python.h
index f9b4f7b..e4e9fff 100644
--- a/src/lib/dns/python/tsig_python.h
+++ b/src/lib/dns/python/tsig_python.h
@@ -23,19 +23,31 @@ class TSIGContext;
namespace python {
-// The s_* Class simply covers one instantiation of the object
-class s_TSIGContext : public PyObject {
-public:
- s_TSIGContext();
- TSIGContext* cppobj;
-};
-
extern PyTypeObject tsigcontext_type;
// Class specific exceptions
extern PyObject* po_TSIGContextError;
-bool initModulePart_TSIGContext(PyObject* mod);
+/// \brief Checks if the given python object is a TSIGContext object
+///
+/// \exception PyCPPWrapperException if obj is NULL
+///
+/// \param obj The object to check the type of
+/// \return true if the object is of type TSIGContext, false otherwise
+bool PyTSIGContext_Check(PyObject* obj);
+
+/// \brief Returns a reference to the TSIGContext object contained within the given
+/// Python object.
+///
+/// \note The given object MUST be of type TSIGContext; this can be checked with
+/// either the right call to ParseTuple("O!"), or with PyTSIGContext_Check()
+///
+/// \note This is not a copy; if the TSIGContext is needed when the PyObject
+/// may be destroyed, the caller must copy it itself.
+///
+/// \param tsigcontext_obj The tsigcontext object to convert
+TSIGContext& PyTSIGContext_ToTSIGContext(PyObject* tsigcontext_obj);
+
} // namespace python
} // namespace dns
diff --git a/src/lib/dns/python/tsig_rdata_python.cc b/src/lib/dns/python/tsig_rdata_python.cc
index 4e4f287..6ec0f09 100644
--- a/src/lib/dns/python/tsig_rdata_python.cc
+++ b/src/lib/dns/python/tsig_rdata_python.cc
@@ -12,6 +12,7 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
+#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <string>
@@ -32,23 +33,19 @@ using namespace isc::dns;
using namespace isc::dns::rdata;
using namespace isc::dns::python;
-//
-// Definition of the classes
-//
-
// For each class, we need a struct, a helper functions (init, destroy,
// and static wrappers around the methods we export), a list of methods,
// and a type description
-//
-// TSIG RDATA
-//
+namespace {
+// The s_* Class simply covers one instantiation of the object
+class s_TSIG : public PyObject {
+public:
+ s_TSIG() : cppobj(NULL) {};
+ const rdata::any::TSIG* cppobj;
+};
-// Trivial constructor.
-s_TSIG::s_TSIG() : cppobj(NULL) {
-}
-namespace {
// Shortcut type which would be convenient for adding class variables safely.
typedef CPPPyObjectContainer<s_TSIG, any::TSIG> TSIGContainer;
@@ -235,7 +232,7 @@ TSIG_toWire(const s_TSIG* const self, PyObject* args) {
self, args));
}
-PyObject*
+PyObject*
TSIG_richcmp(const s_TSIG* const self,
const s_TSIG* const other,
const int op)
@@ -302,7 +299,7 @@ PyTypeObject tsig_type = {
NULL, // tp_as_number
NULL, // tp_as_sequence
NULL, // tp_as_mapping
- NULL, // tp_hash
+ NULL, // tp_hash
NULL, // tp_call
TSIG_str, // tp_str
NULL, // tp_getattro
@@ -340,30 +337,31 @@ PyTypeObject tsig_type = {
0 // tp_version_tag
};
-// Module Initialization, all statics are initialized here
-bool
-initModulePart_TSIG(PyObject* mod) {
- // We initialize the static description object with PyType_Ready(),
- // then add it to the module. This is not just a check! (leaving
- // this out results in segmentation faults)
- if (PyType_Ready(&tsig_type) < 0) {
- return (false);
- }
- void* p = &tsig_type;
- if (PyModule_AddObject(mod, "TSIG", static_cast<PyObject*>(p)) < 0) {
- return (false);
- }
- Py_INCREF(&tsig_type);
-
- return (true);
-}
-
PyObject*
createTSIGObject(const any::TSIG& source) {
- TSIGContainer container = PyObject_New(s_TSIG, &tsig_type);
+ TSIGContainer container(PyObject_New(s_TSIG, &tsig_type));
container.set(new any::TSIG(source));
return (container.release());
}
+
+bool
+PyTSIG_Check(PyObject* obj) {
+ if (obj == NULL) {
+ isc_throw(PyCPPWrapperException, "obj argument NULL in typecheck");
+ }
+ return (PyObject_TypeCheck(obj, &tsig_type));
+}
+
+const any::TSIG&
+PyTSIG_ToTSIG(const PyObject* tsig_obj) {
+ if (tsig_obj == NULL) {
+ isc_throw(PyCPPWrapperException,
+ "obj argument NULL in TSIG PyObject conversion");
+ }
+ const s_TSIG* tsig = static_cast<const s_TSIG*>(tsig_obj);
+ return (*tsig->cppobj);
+}
+
} // namespace python
} // namespace dns
} // namespace isc
diff --git a/src/lib/dns/python/tsig_rdata_python.h b/src/lib/dns/python/tsig_rdata_python.h
index e5e0c6c..a84d9e8 100644
--- a/src/lib/dns/python/tsig_rdata_python.h
+++ b/src/lib/dns/python/tsig_rdata_python.h
@@ -27,17 +27,8 @@ class TSIG;
namespace python {
-// The s_* Class simply covers one instantiation of the object
-class s_TSIG : public PyObject {
-public:
- s_TSIG();
- const rdata::any::TSIG* cppobj;
-};
-
extern PyTypeObject tsig_type;
-bool initModulePart_TSIG(PyObject* mod);
-
/// This is A simple shortcut to create a python TSIG object (in the
/// form of a pointer to PyObject) with minimal exception safety.
/// On success, it returns a valid pointer to PyObject with a reference
@@ -47,6 +38,26 @@ bool initModulePart_TSIG(PyObject* mod);
/// followed by necessary setup for python exception.
PyObject* createTSIGObject(const rdata::any::TSIG& source);
+/// \brief Checks if the given python object is a TSIG object
+///
+/// \exception PyCPPWrapperException if obj is NULL
+///
+/// \param obj The object to check the type of
+/// \return true if the object is of type TSIG, false otherwise
+bool PyTSIG_Check(PyObject* obj);
+
+/// \brief Returns a reference to the TSIG object contained within the given
+/// Python object.
+///
+/// \note The given object MUST be of type TSIG; this can be checked with
+/// either the right call to ParseTuple("O!"), or with PyTSIG_Check()
+///
+/// \note This is not a copy; if the TSIG is needed when the PyObject
+/// may be destroyed, the caller must copy it itself.
+///
+/// \param tsig_obj The tsig object to convert
+const rdata::any::TSIG& PyTSIG_ToTSIG(const PyObject* tsig_obj);
+
} // namespace python
} // namespace dns
} // namespace isc
diff --git a/src/lib/dns/python/tsigerror_python.cc b/src/lib/dns/python/tsigerror_python.cc
index 0ad4716..7a0217e 100644
--- a/src/lib/dns/python/tsigerror_python.cc
+++ b/src/lib/dns/python/tsigerror_python.cc
@@ -30,26 +30,21 @@ using namespace isc::util::python;
using namespace isc::dns;
using namespace isc::dns::python;
-//
-// Definition of the classes
-//
-
// For each class, we need a struct, a helper functions (init, destroy,
// and static wrappers around the methods we export), a list of methods,
// and a type description
-//
-// TSIGError
-//
-
-// Trivial constructor.
-s_TSIGError::s_TSIGError() : cppobj(NULL) {
-}
-
// Import pydoc text
#include "tsigerror_python_inc.cc"
namespace {
+// The s_* Class simply covers one instantiation of the object
+class s_TSIGError : public PyObject {
+public:
+ s_TSIGError() : cppobj(NULL) {};
+ const TSIGError* cppobj;
+};
+
// Shortcut type which would be convenient for adding class variables safely.
typedef CPPPyObjectContainer<s_TSIGError, TSIGError> TSIGErrorContainer;
@@ -107,9 +102,9 @@ TSIGError_init(s_TSIGError* self, PyObject* args) {
// Constructor from Rcode
PyErr_Clear();
- s_Rcode* py_rcode;
+ PyObject* py_rcode;
if (PyArg_ParseTuple(args, "O!", &rcode_type, &py_rcode)) {
- self->cppobj = new TSIGError(*py_rcode->cppobj);
+ self->cppobj = new TSIGError(PyRcode_ToRcode(py_rcode));
return (0);
}
} catch (const isc::OutOfRange& ex) {
@@ -172,13 +167,8 @@ TSIGError_str(PyObject* self) {
PyObject*
TSIGError_toRcode(const s_TSIGError* const self) {
- typedef CPPPyObjectContainer<s_Rcode, Rcode> RcodePyObjectContainer;
-
try {
- RcodePyObjectContainer rcode_container(PyObject_New(s_Rcode,
- &rcode_type));
- rcode_container.set(new Rcode(self->cppobj->toRcode()));
- return (rcode_container.release());
+ return (createRcodeObject(self->cppobj->toRcode()));
} catch (const exception& ex) {
const string ex_what =
"Failed to convert TSIGError to Rcode: " + string(ex.what());
@@ -190,7 +180,7 @@ TSIGError_toRcode(const s_TSIGError* const self) {
return (NULL);
}
-PyObject*
+PyObject*
TSIGError_richcmp(const s_TSIGError* const self,
const s_TSIGError* const other,
const int op)
@@ -252,7 +242,7 @@ PyTypeObject tsigerror_type = {
NULL, // tp_as_number
NULL, // tp_as_sequence
NULL, // tp_as_mapping
- NULL, // tp_hash
+ NULL, // tp_hash
NULL, // tp_call
// THIS MAY HAVE TO BE CHANGED TO NULL:
TSIGError_str, // tp_str
@@ -290,78 +280,9 @@ PyTypeObject tsigerror_type = {
0 // tp_version_tag
};
-namespace {
-// Trivial shortcut to create and install TSIGError constants.
-inline void
-installTSIGErrorConstant(const char* name, const TSIGError& val) {
- TSIGErrorContainer container(PyObject_New(s_TSIGError, &tsigerror_type));
- container.installAsClassVariable(tsigerror_type, name, new TSIGError(val));
-}
-}
-
-// Module Initialization, all statics are initialized here
-bool
-initModulePart_TSIGError(PyObject* mod) {
- // We initialize the static description object with PyType_Ready(),
- // then add it to the module. This is not just a check! (leaving
- // this out results in segmentation faults)
- if (PyType_Ready(&tsigerror_type) < 0) {
- return (false);
- }
- void* p = &tsigerror_type;
- if (PyModule_AddObject(mod, "TSIGError", static_cast<PyObject*>(p)) < 0) {
- return (false);
- }
- Py_INCREF(&tsigerror_type);
-
- try {
- // Constant class variables
- // Error codes (bare values)
- installClassVariable(tsigerror_type, "BAD_SIG_CODE",
- Py_BuildValue("H", TSIGError::BAD_SIG_CODE));
- installClassVariable(tsigerror_type, "BAD_KEY_CODE",
- Py_BuildValue("H", TSIGError::BAD_KEY_CODE));
- installClassVariable(tsigerror_type, "BAD_TIME_CODE",
- Py_BuildValue("H", TSIGError::BAD_TIME_CODE));
-
- // Error codes (constant objects)
- installTSIGErrorConstant("NOERROR", TSIGError::NOERROR());
- installTSIGErrorConstant("FORMERR", TSIGError::FORMERR());
- installTSIGErrorConstant("SERVFAIL", TSIGError::SERVFAIL());
- installTSIGErrorConstant("NXDOMAIN", TSIGError::NXDOMAIN());
- installTSIGErrorConstant("NOTIMP", TSIGError::NOTIMP());
- installTSIGErrorConstant("REFUSED", TSIGError::REFUSED());
- installTSIGErrorConstant("YXDOMAIN", TSIGError::YXDOMAIN());
- installTSIGErrorConstant("YXRRSET", TSIGError::YXRRSET());
- installTSIGErrorConstant("NXRRSET", TSIGError::NXRRSET());
- installTSIGErrorConstant("NOTAUTH", TSIGError::NOTAUTH());
- installTSIGErrorConstant("NOTZONE", TSIGError::NOTZONE());
- installTSIGErrorConstant("RESERVED11", TSIGError::RESERVED11());
- installTSIGErrorConstant("RESERVED12", TSIGError::RESERVED12());
- installTSIGErrorConstant("RESERVED13", TSIGError::RESERVED13());
- installTSIGErrorConstant("RESERVED14", TSIGError::RESERVED14());
- installTSIGErrorConstant("RESERVED15", TSIGError::RESERVED15());
- installTSIGErrorConstant("BAD_SIG", TSIGError::BAD_SIG());
- installTSIGErrorConstant("BAD_KEY", TSIGError::BAD_KEY());
- installTSIGErrorConstant("BAD_TIME", TSIGError::BAD_TIME());
- } catch (const exception& ex) {
- const string ex_what =
- "Unexpected failure in TSIGError initialization: " +
- string(ex.what());
- PyErr_SetString(po_IscException, ex_what.c_str());
- return (false);
- } catch (...) {
- PyErr_SetString(PyExc_SystemError,
- "Unexpected failure in TSIGError initialization");
- return (false);
- }
-
- return (true);
-}
-
PyObject*
createTSIGErrorObject(const TSIGError& source) {
- TSIGErrorContainer container = PyObject_New(s_TSIGError, &tsigerror_type);
+ TSIGErrorContainer container(PyObject_New(s_TSIGError, &tsigerror_type));
container.set(new TSIGError(source));
return (container.release());
}
diff --git a/src/lib/dns/python/tsigerror_python.h b/src/lib/dns/python/tsigerror_python.h
index 735a480..0b5b630 100644
--- a/src/lib/dns/python/tsigerror_python.h
+++ b/src/lib/dns/python/tsigerror_python.h
@@ -23,17 +23,8 @@ class TSIGError;
namespace python {
-// The s_* Class simply covers one instantiation of the object
-class s_TSIGError : public PyObject {
-public:
- s_TSIGError();
- const TSIGError* cppobj;
-};
-
extern PyTypeObject tsigerror_type;
-bool initModulePart_TSIGError(PyObject* mod);
-
/// This is A simple shortcut to create a python TSIGError object (in the
/// form of a pointer to PyObject) with minimal exception safety.
/// On success, it returns a valid pointer to PyObject with a reference
@@ -42,6 +33,7 @@ bool initModulePart_TSIGError(PyObject* mod);
/// This function is expected to be called with in a try block
/// followed by necessary setup for python exception.
PyObject* createTSIGErrorObject(const TSIGError& source);
+
} // namespace python
} // namespace dns
} // namespace isc
diff --git a/src/lib/dns/python/tsigkey_python.cc b/src/lib/dns/python/tsigkey_python.cc
index f0906cb..cf79c1a 100644
--- a/src/lib/dns/python/tsigkey_python.cc
+++ b/src/lib/dns/python/tsigkey_python.cc
@@ -31,10 +31,6 @@ using namespace isc::util::python;
using namespace isc::dns;
using namespace isc::dns::python;
-//
-// Definition of the classes
-//
-
// For each class, we need a struct, a helper functions (init, destroy,
// and static wrappers around the methods we export), a list of methods,
// and a type description
@@ -43,11 +39,14 @@ using namespace isc::dns::python;
// TSIGKey
//
+namespace {
// The s_* Class simply covers one instantiation of the object
+class s_TSIGKey : public PyObject {
+public:
+ s_TSIGKey() : cppobj(NULL) {};
+ TSIGKey* cppobj;
+};
-s_TSIGKey::s_TSIGKey() : cppobj(NULL) {}
-
-namespace {
//
// We declare the functions here, the definitions are below
// the type definition of the object, since both can use the other
@@ -96,8 +95,8 @@ TSIGKey_init(s_TSIGKey* self, PyObject* args) {
}
PyErr_Clear();
- const s_Name* key_name;
- const s_Name* algorithm_name;
+ const PyObject* key_name;
+ const PyObject* algorithm_name;
PyObject* bytes_obj;
const char* secret;
Py_ssize_t secret_len;
@@ -107,8 +106,8 @@ TSIGKey_init(s_TSIGKey* self, PyObject* args) {
if (secret_len == 0) {
secret = NULL;
}
- self->cppobj = new TSIGKey(*key_name->cppobj,
- *algorithm_name->cppobj,
+ self->cppobj = new TSIGKey(PyName_ToName(key_name),
+ PyName_ToName(algorithm_name),
secret, secret_len);
return (0);
}
@@ -196,7 +195,7 @@ PyTypeObject tsigkey_type = {
NULL, // tp_as_number
NULL, // tp_as_sequence
NULL, // tp_as_mapping
- NULL, // tp_hash
+ NULL, // tp_hash
NULL, // tp_call
NULL, // tp_str
NULL, // tp_getattro
@@ -233,49 +232,20 @@ PyTypeObject tsigkey_type = {
0 // tp_version_tag
};
-// Module Initialization, all statics are initialized here
bool
-initModulePart_TSIGKey(PyObject* mod) {
- // We initialize the static description object with PyType_Ready(),
- // then add it to the module. This is not just a check! (leaving
- // this out results in segmentation faults)
- if (PyType_Ready(&tsigkey_type) < 0) {
- return (false);
- }
- void* p = &tsigkey_type;
- if (PyModule_AddObject(mod, "TSIGKey", static_cast<PyObject*>(p)) != 0) {
- return (false);
- }
- Py_INCREF(&tsigkey_type);
-
- try {
- // Constant class variables
- installClassVariable(tsigkey_type, "HMACMD5_NAME",
- createNameObject(TSIGKey::HMACMD5_NAME()));
- installClassVariable(tsigkey_type, "HMACSHA1_NAME",
- createNameObject(TSIGKey::HMACSHA1_NAME()));
- installClassVariable(tsigkey_type, "HMACSHA256_NAME",
- createNameObject(TSIGKey::HMACSHA256_NAME()));
- installClassVariable(tsigkey_type, "HMACSHA224_NAME",
- createNameObject(TSIGKey::HMACSHA224_NAME()));
- installClassVariable(tsigkey_type, "HMACSHA384_NAME",
- createNameObject(TSIGKey::HMACSHA384_NAME()));
- installClassVariable(tsigkey_type, "HMACSHA512_NAME",
- createNameObject(TSIGKey::HMACSHA512_NAME()));
- } catch (const exception& ex) {
- const string ex_what =
- "Unexpected failure in TSIGKey initialization: " +
- string(ex.what());
- PyErr_SetString(po_IscException, ex_what.c_str());
- return (false);
- } catch (...) {
- PyErr_SetString(PyExc_SystemError,
- "Unexpected failure in TSIGKey initialization");
- return (false);
+PyTSIGKey_Check(PyObject* obj) {
+ if (obj == NULL) {
+ isc_throw(PyCPPWrapperException, "obj argument NULL in typecheck");
}
+ return (PyObject_TypeCheck(obj, &tsigkey_type));
+}
- return (true);
+const TSIGKey&
+PyTSIGKey_ToTSIGKey(const PyObject* tsigkey_obj) {
+ const s_TSIGKey* tsigkey = static_cast<const s_TSIGKey*>(tsigkey_obj);
+ return (*tsigkey->cppobj);
}
+
} // namespace python
} // namespace dns
} // namespace isc
@@ -287,13 +257,14 @@ initModulePart_TSIGKey(PyObject* mod) {
// TSIGKeyRing
//
+namespace {
// The s_* Class simply covers one instantiation of the object
+class s_TSIGKeyRing : public PyObject {
+public:
+ s_TSIGKeyRing() : cppobj(NULL) {};
+ TSIGKeyRing* cppobj;
+};
-// The s_* Class simply covers one instantiation of the object
-
-s_TSIGKeyRing::s_TSIGKeyRing() : cppobj(NULL) {}
-
-namespace {
//
// We declare the functions here, the definitions are below
// the type definition of the object, since both can use the other
@@ -329,7 +300,7 @@ TSIGKeyRing_init(s_TSIGKeyRing* self, PyObject* args) {
"Invalid arguments to TSIGKeyRing constructor");
return (-1);
}
-
+
self->cppobj = new(nothrow) TSIGKeyRing();
if (self->cppobj == NULL) {
PyErr_SetString(po_IscException, "Allocating TSIGKeyRing failed");
@@ -354,7 +325,7 @@ TSIGKeyRing_size(const s_TSIGKeyRing* const self) {
PyObject*
TSIGKeyRing_add(const s_TSIGKeyRing* const self, PyObject* args) {
s_TSIGKey* tsigkey;
-
+
if (PyArg_ParseTuple(args, "O!", &tsigkey_type, &tsigkey)) {
try {
const TSIGKeyRing::Result result =
@@ -374,11 +345,11 @@ TSIGKeyRing_add(const s_TSIGKeyRing* const self, PyObject* args) {
PyObject*
TSIGKeyRing_remove(const s_TSIGKeyRing* self, PyObject* args) {
- s_Name* key_name;
+ PyObject* key_name;
if (PyArg_ParseTuple(args, "O!", &name_type, &key_name)) {
const TSIGKeyRing::Result result =
- self->cppobj->remove(*key_name->cppobj);
+ self->cppobj->remove(PyName_ToName(key_name));
return (Py_BuildValue("I", result));
}
@@ -390,13 +361,14 @@ TSIGKeyRing_remove(const s_TSIGKeyRing* self, PyObject* args) {
PyObject*
TSIGKeyRing_find(const s_TSIGKeyRing* self, PyObject* args) {
- s_Name* key_name;
- s_Name* algorithm_name;
+ PyObject* key_name;
+ PyObject* algorithm_name;
if (PyArg_ParseTuple(args, "O!O!", &name_type, &key_name,
&name_type, &algorithm_name)) {
const TSIGKeyRing::FindResult result =
- self->cppobj->find(*key_name->cppobj, *algorithm_name->cppobj);
+ self->cppobj->find(PyName_ToName(key_name),
+ PyName_ToName(algorithm_name));
if (result.key != NULL) {
s_TSIGKey* key = PyObject_New(s_TSIGKey, &tsigkey_type);
if (key == NULL) {
@@ -436,7 +408,7 @@ PyTypeObject tsigkeyring_type = {
NULL, // tp_as_number
NULL, // tp_as_sequence
NULL, // tp_as_mapping
- NULL, // tp_hash
+ NULL, // tp_hash
NULL, // tp_call
NULL, // tp_str
NULL, // tp_getattro
@@ -473,27 +445,24 @@ PyTypeObject tsigkeyring_type = {
};
bool
-initModulePart_TSIGKeyRing(PyObject* mod) {
- if (PyType_Ready(&tsigkeyring_type) < 0) {
- return (false);
+PyTSIGKeyRing_Check(PyObject* obj) {
+ if (obj == NULL) {
+ isc_throw(PyCPPWrapperException, "obj argument NULL in typecheck");
}
- Py_INCREF(&tsigkeyring_type);
- void* p = &tsigkeyring_type;
- if (PyModule_AddObject(mod, "TSIGKeyRing",
- static_cast<PyObject*>(p)) != 0) {
- Py_DECREF(&tsigkeyring_type);
- return (false);
- }
-
- addClassVariable(tsigkeyring_type, "SUCCESS",
- Py_BuildValue("I", TSIGKeyRing::SUCCESS));
- addClassVariable(tsigkeyring_type, "EXIST",
- Py_BuildValue("I", TSIGKeyRing::EXIST));
- addClassVariable(tsigkeyring_type, "NOTFOUND",
- Py_BuildValue("I", TSIGKeyRing::NOTFOUND));
+ return (PyObject_TypeCheck(obj, &tsigkeyring_type));
+}
- return (true);
+const TSIGKeyRing&
+PyTSIGKeyRing_ToTSIGKeyRing(const PyObject* tsigkeyring_obj) {
+ if (tsigkeyring_obj == NULL) {
+ isc_throw(PyCPPWrapperException,
+ "obj argument NULL in TSIGKeyRing PyObject conversion");
+ }
+ const s_TSIGKeyRing* tsigkeyring =
+ static_cast<const s_TSIGKeyRing*>(tsigkeyring_obj);
+ return (*tsigkeyring->cppobj);
}
+
} // namespace python
} // namespace dns
} // namespace isc
diff --git a/src/lib/dns/python/tsigkey_python.h b/src/lib/dns/python/tsigkey_python.h
index 51b3ae7..6c3d2e3 100644
--- a/src/lib/dns/python/tsigkey_python.h
+++ b/src/lib/dns/python/tsigkey_python.h
@@ -24,24 +24,46 @@ class TSIGKeyRing;
namespace python {
-// The s_* Class simply covers one instantiation of the object
-class s_TSIGKey : public PyObject {
-public:
- s_TSIGKey();
- TSIGKey* cppobj;
-};
-
-class s_TSIGKeyRing : public PyObject {
-public:
- s_TSIGKeyRing();
- TSIGKeyRing* cppobj;
-};
-
extern PyTypeObject tsigkey_type;
extern PyTypeObject tsigkeyring_type;
-bool initModulePart_TSIGKey(PyObject* mod);
-bool initModulePart_TSIGKeyRing(PyObject* mod);
+/// \brief Checks if the given python object is a TSIGKey object
+///
+/// \exception PyCPPWrapperException if obj is NULL
+///
+/// \param obj The object to check the type of
+/// \return true if the object is of type TSIGKey, false otherwise
+bool PyTSIGKey_Check(PyObject* obj);
+
+/// \brief Returns a reference to the TSIGKey object contained within the given
+/// Python object.
+///
+/// \note The given object MUST be of type TSIGKey; this can be checked with
+/// either the right call to ParseTuple("O!"), or with PyTSIGKey_Check()
+///
+/// \note This is not a copy; if the TSIGKey is needed when the PyObject
+/// may be destroyed, the caller must copy it itself.
+///
+/// \param tsigkey_obj The tsigkey object to convert
+const TSIGKey& PyTSIGKey_ToTSIGKey(const PyObject* tsigkey_obj);
+
+/// \brief Checks if the given python object is a TSIGKeyRing object
+///
+/// \param obj The object to check the type of
+/// \return true if the object is of type TSIGKeyRing, false otherwise
+bool PyTSIGKeyRing_Check(PyObject* obj);
+
+/// \brief Returns a reference to the TSIGKeyRing object contained within the given
+/// Python object.
+///
+/// \note The given object MUST be of type TSIGKeyRing; this can be checked with
+/// either the right call to ParseTuple("O!"), or with PyTSIGKeyRing_Check()
+///
+/// \note This is not a copy; if the TSIGKeyRing is needed when the PyObject
+/// may be destroyed, the caller must copy it itself.
+///
+/// \param tsigkeyring_obj The tsigkeyring object to convert
+const TSIGKeyRing& PyTSIGKeyRing_ToTSIGKeyRing(const PyObject* tsigkeyring_obj);
} // namespace python
} // namespace dns
diff --git a/src/lib/dns/python/tsigrecord_python.cc b/src/lib/dns/python/tsigrecord_python.cc
index 8a78b5e..c754dd2 100644
--- a/src/lib/dns/python/tsigrecord_python.cc
+++ b/src/lib/dns/python/tsigrecord_python.cc
@@ -32,10 +32,6 @@ using namespace isc::util::python;
using namespace isc::dns;
using namespace isc::dns::python;
-//
-// Definition of the classes
-//
-
// For each class, we need a struct, a helper functions (init, destroy,
// and static wrappers around the methods we export), a list of methods,
// and a type description
@@ -44,11 +40,14 @@ using namespace isc::dns::python;
// TSIGRecord
//
-// Trivial constructor.
-s_TSIGRecord::s_TSIGRecord() : cppobj(NULL) {
-}
-
namespace {
+// The s_* Class simply covers one instantiation of the object
+class s_TSIGRecord : public PyObject {
+public:
+ s_TSIGRecord() : cppobj(NULL) {};
+ TSIGRecord* cppobj;
+};
+
// Shortcut type which would be convenient for adding class variables safely.
typedef CPPPyObjectContainer<s_TSIGRecord, TSIGRecord> TSIGRecordContainer;
@@ -102,11 +101,12 @@ PyMethodDef TSIGRecord_methods[] = {
int
TSIGRecord_init(s_TSIGRecord* self, PyObject* args) {
try {
- const s_Name* py_name;
- const s_TSIG* py_tsig;
+ const PyObject* py_name;
+ const PyObject* py_tsig;
if (PyArg_ParseTuple(args, "O!O!", &name_type, &py_name,
&tsig_type, &py_tsig)) {
- self->cppobj = new TSIGRecord(*py_name->cppobj, *py_tsig->cppobj);
+ self->cppobj = new TSIGRecord(PyName_ToName(py_name),
+ PyTSIG_ToTSIG(py_tsig));
return (0);
}
} catch (const exception& ex) {
@@ -226,7 +226,7 @@ PyTypeObject tsigrecord_type = {
NULL, // tp_as_number
NULL, // tp_as_sequence
NULL, // tp_as_mapping
- NULL, // tp_hash
+ NULL, // tp_hash
NULL, // tp_call
TSIGRecord_str, // tp_str
NULL, // tp_getattro
@@ -262,50 +262,32 @@ PyTypeObject tsigrecord_type = {
0 // tp_version_tag
};
-// Module Initialization, all statics are initialized here
+PyObject*
+createTSIGRecordObject(const TSIGRecord& source) {
+ TSIGRecordContainer container(PyObject_New(s_TSIGRecord, &tsigrecord_type));
+ container.set(new TSIGRecord(source));
+ return (container.release());
+}
+
bool
-initModulePart_TSIGRecord(PyObject* mod) {
- // We initialize the static description object with PyType_Ready(),
- // then add it to the module. This is not just a check! (leaving
- // this out results in segmentation faults)
- if (PyType_Ready(&tsigrecord_type) < 0) {
- return (false);
- }
- void* p = &tsigrecord_type;
- if (PyModule_AddObject(mod, "TSIGRecord", static_cast<PyObject*>(p)) < 0) {
- return (false);
+PyTSIGRecord_Check(PyObject* obj) {
+ if (obj == NULL) {
+ isc_throw(PyCPPWrapperException, "obj argument NULL in typecheck");
}
- Py_INCREF(&tsigrecord_type);
+ return (PyObject_TypeCheck(obj, &tsigrecord_type));
+}
- // The following template is the typical procedure for installing class
- // variables. If the class doesn't have a class variable, remove the
- // entire try-catch clauses.
- try {
- // Constant class variables
- installClassVariable(tsigrecord_type, "TSIG_TTL",
- Py_BuildValue("I", 0));
- } catch (const exception& ex) {
- const string ex_what =
- "Unexpected failure in TSIGRecord initialization: " +
- string(ex.what());
- PyErr_SetString(po_IscException, ex_what.c_str());
- return (false);
- } catch (...) {
- PyErr_SetString(PyExc_SystemError,
- "Unexpected failure in TSIGRecord initialization");
- return (false);
+const TSIGRecord&
+PyTSIGRecord_ToTSIGRecord(PyObject* tsigrecord_obj) {
+ if (tsigrecord_obj == NULL) {
+ isc_throw(PyCPPWrapperException,
+ "obj argument NULL in TSIGRecord PyObject conversion");
}
-
- return (true);
+ s_TSIGRecord* tsigrecord = static_cast<s_TSIGRecord*>(tsigrecord_obj);
+ return (*tsigrecord->cppobj);
}
-PyObject*
-createTSIGRecordObject(const TSIGRecord& source) {
- TSIGRecordContainer container = PyObject_New(s_TSIGRecord,
- &tsigrecord_type);
- container.set(new TSIGRecord(source));
- return (container.release());
-}
+
} // namespace python
} // namespace dns
} // namespace isc
diff --git a/src/lib/dns/python/tsigrecord_python.h b/src/lib/dns/python/tsigrecord_python.h
index e0a3526..d6252e1 100644
--- a/src/lib/dns/python/tsigrecord_python.h
+++ b/src/lib/dns/python/tsigrecord_python.h
@@ -23,17 +23,9 @@ class TSIGRecord;
namespace python {
-// The s_* Class simply covers one instantiation of the object
-class s_TSIGRecord : public PyObject {
-public:
- s_TSIGRecord();
- TSIGRecord* cppobj;
-};
extern PyTypeObject tsigrecord_type;
-bool initModulePart_TSIGRecord(PyObject* mod);
-
/// This is A simple shortcut to create a python TSIGRecord object (in the
/// form of a pointer to PyObject) with minimal exception safety.
/// On success, it returns a valid pointer to PyObject with a reference
@@ -43,6 +35,26 @@ bool initModulePart_TSIGRecord(PyObject* mod);
/// followed by necessary setup for python exception.
PyObject* createTSIGRecordObject(const TSIGRecord& source);
+/// \brief Checks if the given python object is a TSIGRecord object
+///
+/// \exception PyCPPWrapperException if obj is NULL
+///
+/// \param obj The object to check the type of
+/// \return true if the object is of type TSIGRecord, false otherwise
+bool PyTSIGRecord_Check(PyObject* obj);
+
+/// \brief Returns a reference to the TSIGRecord object contained within the given
+/// Python object.
+///
+/// \note The given object MUST be of type TSIGRecord; this can be checked with
+/// either the right call to ParseTuple("O!"), or with PyTSIGRecord_Check()
+///
+/// \note This is not a copy; if the TSIGRecord is needed when the PyObject
+/// may be destroyed, the caller must copy it itself.
+///
+/// \param rrtype_obj The rrtype object to convert
+const TSIGRecord& PyTSIGRecord_ToTSIGRecord(PyObject* tsigrecord_obj);
+
} // namespace python
} // namespace dns
} // namespace isc
diff --git a/src/lib/dns/rdata/generic/detail/txt_like.h b/src/lib/dns/rdata/generic/detail/txt_like.h
new file mode 100644
index 0000000..392a8ce
--- /dev/null
+++ b/src/lib/dns/rdata/generic/detail/txt_like.h
@@ -0,0 +1,172 @@
+// 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.
+
+#ifndef __TXT_LIKE_H
+#define __TXT_LIKE_H 1
+
+#include <stdint.h>
+
+#include <string>
+#include <vector>
+
+using namespace std;
+using namespace isc::util;
+
+template<class Type, uint16_t typeCode>class TXTLikeImpl {
+public:
+ TXTLikeImpl(InputBuffer& buffer, size_t rdata_len) {
+ if (rdata_len > MAX_RDLENGTH) {
+ isc_throw(InvalidRdataLength, "RDLENGTH too large: " << rdata_len);
+ }
+
+ if (rdata_len == 0) { // note that this couldn't happen in the loop.
+ isc_throw(DNSMessageFORMERR, "Error in parsing " <<
+ RRType(typeCode) << " RDATA: 0-length character string");
+ }
+
+ do {
+ const uint8_t len = buffer.readUint8();
+ if (rdata_len < len + 1) {
+ isc_throw(DNSMessageFORMERR, "Error in parsing " <<
+ RRType(typeCode) <<
+ " RDATA: character string length is too large: " <<
+ static_cast<int>(len));
+ }
+ vector<uint8_t> data(len + 1);
+ data[0] = len;
+ buffer.readData(&data[0] + 1, len);
+ string_list_.push_back(data);
+
+ rdata_len -= (len + 1);
+ } while (rdata_len > 0);
+ }
+
+ explicit TXTLikeImpl(const std::string& txtstr) {
+ // TBD: this is a simple, incomplete implementation that only supports
+ // a single character-string.
+
+ size_t length = txtstr.size();
+ size_t pos_begin = 0;
+
+ if (length > 1 && txtstr[0] == '"' && txtstr[length - 1] == '"') {
+ pos_begin = 1;
+ length -= 2;
+ }
+
+ if (length > MAX_CHARSTRING_LEN) {
+ isc_throw(CharStringTooLong, RRType(typeCode) <<
+ " RDATA construction from text:"
+ " string length is too long: " << length);
+ }
+
+ // TBD: right now, we don't support escaped characters
+ if (txtstr.find('\\') != string::npos) {
+ isc_throw(InvalidRdataText, RRType(typeCode) <<
+ " RDATA from text:"
+ " escaped character is currently not supported: " <<
+ txtstr);
+ }
+
+ vector<uint8_t> data;
+ data.reserve(length + 1);
+ data.push_back(length);
+ data.insert(data.end(), txtstr.begin() + pos_begin,
+ txtstr.begin() + pos_begin + length);
+ string_list_.push_back(data);
+ }
+
+ TXTLikeImpl(const TXTLikeImpl& other) :
+ string_list_(other.string_list_)
+ {}
+
+ void
+ toWire(OutputBuffer& buffer) const {
+ for (vector<vector<uint8_t> >::const_iterator it =
+ string_list_.begin();
+ it != string_list_.end();
+ ++it)
+ {
+ buffer.writeData(&(*it)[0], (*it).size());
+ }
+ }
+
+ void
+ toWire(AbstractMessageRenderer& renderer) const {
+ for (vector<vector<uint8_t> >::const_iterator it =
+ string_list_.begin();
+ it != string_list_.end();
+ ++it)
+ {
+ renderer.writeData(&(*it)[0], (*it).size());
+ }
+ }
+
+ string
+ toText() const {
+ string s;
+
+ // XXX: this implementation is not entirely correct. for example, it
+ // should escape double-quotes if they appear in the character string.
+ for (vector<vector<uint8_t> >::const_iterator it =
+ string_list_.begin();
+ it != string_list_.end();
+ ++it)
+ {
+ if (!s.empty()) {
+ s.push_back(' ');
+ }
+ s.push_back('"');
+ s.insert(s.end(), (*it).begin() + 1, (*it).end());
+ s.push_back('"');
+ }
+
+ return (s);
+ }
+
+ int
+ compare(const TXTLikeImpl& other) const {
+ // This implementation is not efficient. Revisit this (TBD).
+ OutputBuffer this_buffer(0);
+ toWire(this_buffer);
+ size_t this_len = this_buffer.getLength();
+
+ OutputBuffer other_buffer(0);
+ other.toWire(other_buffer);
+ const size_t other_len = other_buffer.getLength();
+
+ const size_t cmplen = min(this_len, other_len);
+ const int cmp = memcmp(this_buffer.getData(), other_buffer.getData(),
+ cmplen);
+ if (cmp != 0) {
+ return (cmp);
+ } else {
+ return ((this_len == other_len) ? 0 :
+ (this_len < other_len) ? -1 : 1);
+ }
+ }
+
+private:
+ /// Note: this is a prototype version; we may reconsider
+ /// this representation later.
+ std::vector<std::vector<uint8_t> > string_list_;
+};
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
+
+#endif // __TXT_LIKE_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rdata/generic/rrsig_46.cc b/src/lib/dns/rdata/generic/rrsig_46.cc
index fc8e340..59ff030 100644
--- a/src/lib/dns/rdata/generic/rrsig_46.cc
+++ b/src/lib/dns/rdata/generic/rrsig_46.cc
@@ -244,7 +244,7 @@ RRSIG::compare(const Rdata& other) const {
}
const RRType&
-RRSIG::typeCovered() {
+RRSIG::typeCovered() const {
return (impl_->covered_);
}
diff --git a/src/lib/dns/rdata/generic/rrsig_46.h b/src/lib/dns/rdata/generic/rrsig_46.h
index b8e6306..b32c17f 100644
--- a/src/lib/dns/rdata/generic/rrsig_46.h
+++ b/src/lib/dns/rdata/generic/rrsig_46.h
@@ -40,7 +40,7 @@ public:
~RRSIG();
// specialized methods
- const RRType& typeCovered();
+ const RRType& typeCovered() const;
private:
RRSIGImpl* impl_;
};
diff --git a/src/lib/dns/rdata/generic/spf_99.cc b/src/lib/dns/rdata/generic/spf_99.cc
new file mode 100644
index 0000000..492de98
--- /dev/null
+++ b/src/lib/dns/rdata/generic/spf_99.cc
@@ -0,0 +1,87 @@
+// Copyright (C) 2010 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 <stdint.h>
+#include <string.h>
+
+#include <string>
+#include <vector>
+
+#include <util/buffer.h>
+#include <dns/exceptions.h>
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+
+using namespace std;
+using namespace isc::util;
+
+// BEGIN_ISC_NAMESPACE
+// BEGIN_RDATA_NAMESPACE
+
+#include <dns/rdata/generic/detail/txt_like.h>
+
+SPF&
+SPF::operator=(const SPF& source) {
+ if (impl_ == source.impl_) {
+ return (*this);
+ }
+
+ SPFImpl* newimpl = new SPFImpl(*source.impl_);
+ delete impl_;
+ impl_ = newimpl;
+
+ return (*this);
+}
+
+SPF::~SPF() {
+ delete impl_;
+}
+
+SPF::SPF(InputBuffer& buffer, size_t rdata_len) :
+ impl_(new SPFImpl(buffer, rdata_len))
+{}
+
+SPF::SPF(const std::string& txtstr) :
+ impl_(new SPFImpl(txtstr))
+{}
+
+SPF::SPF(const SPF& other) :
+ Rdata(), impl_(new SPFImpl(*other.impl_))
+{}
+
+void
+SPF::toWire(OutputBuffer& buffer) const {
+ impl_->toWire(buffer);
+}
+
+void
+SPF::toWire(AbstractMessageRenderer& renderer) const {
+ impl_->toWire(renderer);
+}
+
+string
+SPF::toText() const {
+ return (impl_->toText());
+}
+
+int
+SPF::compare(const Rdata& other) const {
+ const SPF& other_txt = dynamic_cast<const SPF&>(other);
+
+ return (impl_->compare(*other_txt.impl_));
+}
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
diff --git a/src/lib/dns/rdata/generic/spf_99.h b/src/lib/dns/rdata/generic/spf_99.h
new file mode 100644
index 0000000..956adb9
--- /dev/null
+++ b/src/lib/dns/rdata/generic/spf_99.h
@@ -0,0 +1,52 @@
+// Copyright (C) 2010 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.
+
+// BEGIN_HEADER_GUARD
+
+#include <stdint.h>
+
+#include <string>
+#include <vector>
+
+#include <dns/rdata.h>
+
+// BEGIN_ISC_NAMESPACE
+
+// BEGIN_COMMON_DECLARATIONS
+// END_COMMON_DECLARATIONS
+
+// BEGIN_RDATA_NAMESPACE
+
+template<class Type, uint16_t typeCode> class TXTLikeImpl;
+
+class SPF : public Rdata {
+public:
+ // BEGIN_COMMON_MEMBERS
+ // END_COMMON_MEMBERS
+
+ SPF& operator=(const SPF& source);
+ ~SPF();
+
+private:
+ typedef TXTLikeImpl<SPF, 99> SPFImpl;
+ SPFImpl* impl_;
+};
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
+// END_HEADER_GUARD
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rdata/generic/txt_16.cc b/src/lib/dns/rdata/generic/txt_16.cc
index ac2ba8a..418bc05 100644
--- a/src/lib/dns/rdata/generic/txt_16.cc
+++ b/src/lib/dns/rdata/generic/txt_16.cc
@@ -30,130 +30,57 @@ using namespace isc::util;
// BEGIN_ISC_NAMESPACE
// BEGIN_RDATA_NAMESPACE
-TXT::TXT(InputBuffer& buffer, size_t rdata_len) {
- if (rdata_len > MAX_RDLENGTH) {
- isc_throw(InvalidRdataLength, "RDLENGTH too large: " << rdata_len);
- }
+#include <dns/rdata/generic/detail/txt_like.h>
- if (rdata_len == 0) { // note that this couldn't happen in the loop.
- isc_throw(DNSMessageFORMERR,
- "Error in parsing TXT RDATA: 0-length character string");
+TXT&
+TXT::operator=(const TXT& source) {
+ if (impl_ == source.impl_) {
+ return (*this);
}
- do {
- const uint8_t len = buffer.readUint8();
- if (rdata_len < len + 1) {
- isc_throw(DNSMessageFORMERR,
- "Error in parsing TXT RDATA: character string length "
- "is too large: " << static_cast<int>(len));
- }
- vector<uint8_t> data(len + 1);
- data[0] = len;
- buffer.readData(&data[0] + 1, len);
- string_list_.push_back(data);
-
- rdata_len -= (len + 1);
- } while (rdata_len > 0);
-}
-
-TXT::TXT(const std::string& txtstr) {
- // TBD: this is a simple, incomplete implementation that only supports
- // a single character-string.
+ TXTImpl* newimpl = new TXTImpl(*source.impl_);
+ delete impl_;
+ impl_ = newimpl;
- size_t length = txtstr.size();
- size_t pos_begin = 0;
-
- if (length > 1 && txtstr[0] == '"' && txtstr[length - 1] == '"') {
- pos_begin = 1;
- length -= 2;
- }
+ return (*this);
+}
- if (length > MAX_CHARSTRING_LEN) {
- isc_throw(CharStringTooLong, "TXT RDATA construction from text: "
- "string length is too long: " << length);
- }
+TXT::~TXT() {
+ delete impl_;
+}
- // TBD: right now, we don't support escaped characters
- if (txtstr.find('\\') != string::npos) {
- isc_throw(InvalidRdataText, "TXT RDATA from text: "
- "escaped character is currently not supported: " << txtstr);
- }
+TXT::TXT(InputBuffer& buffer, size_t rdata_len) :
+ impl_(new TXTImpl(buffer, rdata_len))
+{}
- vector<uint8_t> data;
- data.reserve(length + 1);
- data.push_back(length);
- data.insert(data.end(), txtstr.begin() + pos_begin,
- txtstr.begin() + pos_begin + length);
- string_list_.push_back(data);
-}
+TXT::TXT(const std::string& txtstr) :
+ impl_(new TXTImpl(txtstr))
+{}
TXT::TXT(const TXT& other) :
- Rdata(), string_list_(other.string_list_)
+ Rdata(), impl_(new TXTImpl(*other.impl_))
{}
void
TXT::toWire(OutputBuffer& buffer) const {
- for (vector<vector<uint8_t> >::const_iterator it = string_list_.begin();
- it != string_list_.end();
- ++it)
- {
- buffer.writeData(&(*it)[0], (*it).size());
- }
+ impl_->toWire(buffer);
}
void
TXT::toWire(AbstractMessageRenderer& renderer) const {
- for (vector<vector<uint8_t> >::const_iterator it = string_list_.begin();
- it != string_list_.end();
- ++it)
- {
- renderer.writeData(&(*it)[0], (*it).size());
- }
+ impl_->toWire(renderer);
}
string
TXT::toText() const {
- string s;
-
- // XXX: this implementation is not entirely correct. for example, it
- // should escape double-quotes if they appear in the character string.
- for (vector<vector<uint8_t> >::const_iterator it = string_list_.begin();
- it != string_list_.end();
- ++it)
- {
- if (!s.empty()) {
- s.push_back(' ');
- }
- s.push_back('"');
- s.insert(s.end(), (*it).begin() + 1, (*it).end());
- s.push_back('"');
- }
-
- return (s);
+ return (impl_->toText());
}
int
TXT::compare(const Rdata& other) const {
const TXT& other_txt = dynamic_cast<const TXT&>(other);
- // This implementation is not efficient. Revisit this (TBD).
- OutputBuffer this_buffer(0);
- toWire(this_buffer);
- size_t this_len = this_buffer.getLength();
-
- OutputBuffer other_buffer(0);
- other_txt.toWire(other_buffer);
- const size_t other_len = other_buffer.getLength();
-
- const size_t cmplen = min(this_len, other_len);
- const int cmp = memcmp(this_buffer.getData(), other_buffer.getData(),
- cmplen);
- if (cmp != 0) {
- return (cmp);
- } else {
- return ((this_len == other_len) ? 0 :
- (this_len < other_len) ? -1 : 1);
- }
+ return (impl_->compare(*other_txt.impl_));
}
// END_RDATA_NAMESPACE
diff --git a/src/lib/dns/rdata/generic/txt_16.h b/src/lib/dns/rdata/generic/txt_16.h
index b4c791f..d99d69b 100644
--- a/src/lib/dns/rdata/generic/txt_16.h
+++ b/src/lib/dns/rdata/generic/txt_16.h
@@ -28,14 +28,19 @@
// BEGIN_RDATA_NAMESPACE
+template<class Type, uint16_t typeCode> class TXTLikeImpl;
+
class TXT : public Rdata {
public:
// BEGIN_COMMON_MEMBERS
// END_COMMON_MEMBERS
+
+ TXT& operator=(const TXT& source);
+ ~TXT();
+
private:
- /// Note: this is a prototype version; we may reconsider
- /// this representation later.
- std::vector<std::vector<uint8_t> > string_list_;
+ typedef TXTLikeImpl<TXT, 16> TXTImpl;
+ TXTImpl* impl_;
};
// END_RDATA_NAMESPACE
diff --git a/src/lib/dns/rdata/in_1/dhcid_49.cc b/src/lib/dns/rdata/in_1/dhcid_49.cc
new file mode 100644
index 0000000..0a9a23c
--- /dev/null
+++ b/src/lib/dns/rdata/in_1/dhcid_49.cc
@@ -0,0 +1,145 @@
+// Copyright (C) 2010 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 <stdint.h>
+#include <string.h>
+
+#include <string>
+
+#include <exceptions/exceptions.h>
+
+#include <util/buffer.h>
+#include <util/encode/hex.h>
+#include <dns/exceptions.h>
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+
+using namespace std;
+using namespace isc::util;
+
+// BEGIN_ISC_NAMESPACE
+// BEGIN_RDATA_NAMESPACE
+
+/// \brief Constructor from string.
+///
+/// \param dhcid_str A base-64 representation of the DHCID binary data.
+/// The data is considered to be opaque, but a sanity check is performed.
+///
+/// <b>Exceptions</b>
+///
+/// \c dhcid_str must be a valid BASE-64 string, otherwise an exception
+/// of class \c isc::BadValue will be thrown;
+/// the binary data should consist of at leat of 3 octets as per RFC4701:
+/// < 2 octets > Identifier type code
+/// < 1 octet > Digest type code
+/// < n octets > Digest (length depends on digest type)
+/// If the data is less than 3 octets (i.e. it cannot contain id type code and
+/// digest type code), an exception of class \c InvalidRdataLength is thrown.
+DHCID::DHCID(const string& dhcid_str) {
+ istringstream iss(dhcid_str);
+ stringbuf digestbuf;
+
+ iss >> &digestbuf;
+ isc::util::encode::decodeHex(digestbuf.str(), digest_);
+
+ // RFC4701 states DNS software should consider the RDATA section to
+ // be opaque, but there must be at least three bytes in the data:
+ // < 2 octets > Identifier type code
+ // < 1 octet > Digest type code
+ if (digest_.size() < 3) {
+ isc_throw(InvalidRdataLength, "DHCID length " << digest_.size() <<
+ " too short, need at least 3 bytes");
+ }
+}
+
+/// \brief Constructor from wire-format data.
+///
+/// \param buffer A buffer storing the wire format data.
+/// \param rdata_len The length of the RDATA in bytes
+///
+/// <b>Exceptions</b>
+/// \c InvalidRdataLength is thrown if \c rdata_len is than minimum of 3 octets
+DHCID::DHCID(InputBuffer& buffer, size_t rdata_len) {
+ if (rdata_len < 3) {
+ isc_throw(InvalidRdataLength, "DHCID length " << rdata_len <<
+ " too short, need at least 3 bytes");
+ }
+
+ digest_.resize(rdata_len);
+ buffer.readData(&digest_[0], rdata_len);
+}
+
+/// \brief The copy constructor.
+///
+/// This trivial copy constructor never throws an exception.
+DHCID::DHCID(const DHCID& other) : Rdata(), digest_(other.digest_)
+{}
+
+/// \brief Render the \c DHCID in the wire format.
+///
+/// \param buffer An output buffer to store the wire data.
+void
+DHCID::toWire(OutputBuffer& buffer) const {
+ buffer.writeData(&digest_[0], digest_.size());
+}
+
+/// \brief Render the \c DHCID in the wire format into a
+/// \c MessageRenderer object.
+///
+/// \param renderer DNS message rendering context that encapsulates the
+/// output buffer in which the \c DHCID is to be stored.
+void
+DHCID::toWire(AbstractMessageRenderer& renderer) const {
+ renderer.writeData(&digest_[0], digest_.size());
+}
+
+/// \brief Convert the \c DHCID to a string.
+///
+/// This method returns a \c std::string object representing the \c DHCID.
+///
+/// \return A string representation of \c DHCID.
+string
+DHCID::toText() const {
+ return (isc::util::encode::encodeHex(digest_));
+}
+
+/// \brief Compare two instances of \c DHCID RDATA.
+///
+/// See documentation in \c Rdata.
+int
+DHCID::compare(const Rdata& other) const {
+ const DHCID& other_dhcid = dynamic_cast<const DHCID&>(other);
+
+ size_t this_len = digest_.size();
+ size_t other_len = other_dhcid.digest_.size();
+ size_t cmplen = min(this_len, other_len);
+ int cmp = memcmp(&digest_[0], &other_dhcid.digest_[0], cmplen);
+ if (cmp != 0) {
+ return (cmp);
+ } else {
+ return ((this_len == other_len) ? 0 : (this_len < other_len) ? -1 : 1);
+ }
+}
+
+/// \brief Accessor method to get the DHCID digest
+///
+/// \return A reference to the binary DHCID data
+const std::vector<uint8_t>&
+DHCID::getDigest() const {
+ return (digest_);
+}
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
diff --git a/src/lib/dns/rdata/in_1/dhcid_49.h b/src/lib/dns/rdata/in_1/dhcid_49.h
new file mode 100644
index 0000000..919395f
--- /dev/null
+++ b/src/lib/dns/rdata/in_1/dhcid_49.h
@@ -0,0 +1,58 @@
+// Copyright (C) 2010 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.
+
+// BEGIN_HEADER_GUARD
+
+#include <string>
+#include <vector>
+
+#include <dns/rdata.h>
+
+// BEGIN_ISC_NAMESPACE
+
+// BEGIN_COMMON_DECLARATIONS
+// END_COMMON_DECLARATIONS
+
+// BEGIN_RDATA_NAMESPACE
+
+/// \brief \c rdata::DHCID class represents the DHCID RDATA as defined %in
+/// RFC4701.
+///
+/// This class implements the basic interfaces inherited from the abstract
+/// \c rdata::Rdata class, and provides trivial accessors specific to the
+/// DHCID RDATA.
+class DHCID : public Rdata {
+public:
+ // BEGIN_COMMON_MEMBERS
+ // END_COMMON_MEMBERS
+
+ /// \brief Return the digest.
+ ///
+ /// This method never throws an exception.
+ const std::vector<uint8_t>& getDigest() const;
+
+private:
+ /// \brief Private data representation
+ ///
+ /// Opaque data at least 3 octets long as per RFC4701.
+ ///
+ std::vector<uint8_t> digest_;
+};
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
+// END_HEADER_GUARD
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/python/isc/acl/tests/Makefile.am b/src/lib/python/isc/acl/tests/Makefile.am
index 87781d7..42867b2 100644
--- a/src/lib/python/isc/acl/tests/Makefile.am
+++ b/src/lib/python/isc/acl/tests/Makefile.am
@@ -7,7 +7,7 @@ EXTRA_DIST = $(PYTESTS)
# required by loadable python modules.
LIBRARY_PATH_PLACEHOLDER =
if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/acl/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/acl/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$$$(ENV_LIBRARY_PATH)
endif
# test using command-line arguments, so use check-local target instead of TESTS
diff --git a/src/lib/python/isc/datasrc/sqlite3_ds.py b/src/lib/python/isc/datasrc/sqlite3_ds.py
index a77645a..fd63741 100644
--- a/src/lib/python/isc/datasrc/sqlite3_ds.py
+++ b/src/lib/python/isc/datasrc/sqlite3_ds.py
@@ -33,44 +33,63 @@ def create(cur):
Arguments:
cur - sqlite3 cursor.
"""
- cur.execute("CREATE TABLE schema_version (version INTEGER NOT NULL)")
- cur.execute("INSERT INTO schema_version VALUES (1)")
- cur.execute("""CREATE TABLE zones (id INTEGER PRIMARY KEY,
- name STRING NOT NULL COLLATE NOCASE,
- rdclass STRING NOT NULL COLLATE NOCASE DEFAULT 'IN',
- dnssec BOOLEAN NOT NULL DEFAULT 0)""")
- cur.execute("CREATE INDEX zones_byname ON zones (name)")
- cur.execute("""CREATE TABLE records (id INTEGER PRIMARY KEY,
- zone_id INTEGER NOT NULL,
- name STRING NOT NULL COLLATE NOCASE,
- rname STRING NOT NULL COLLATE NOCASE,
- ttl INTEGER NOT NULL,
- rdtype STRING NOT NULL COLLATE NOCASE,
- sigtype STRING COLLATE NOCASE,
- rdata STRING NOT NULL)""")
- cur.execute("CREATE INDEX records_byname ON records (name)")
- cur.execute("CREATE INDEX records_byrname ON records (rname)")
- cur.execute("""CREATE TABLE nsec3 (id INTEGER PRIMARY KEY,
- zone_id INTEGER NOT NULL,
- hash STRING NOT NULL COLLATE NOCASE,
- owner STRING NOT NULL COLLATE NOCASE,
- ttl INTEGER NOT NULL,
- rdtype STRING NOT NULL COLLATE NOCASE,
- rdata STRING NOT NULL)""")
- cur.execute("CREATE INDEX nsec3_byhash ON nsec3 (hash)")
-
-def open(dbfile):
+ # We are creating the database because it apparently had not been at
+ # the time we tried to read from it. However, another process may have
+ # had the same idea, resulting in a potential race condition.
+ # Therefore, we obtain an exclusive lock before we create anything
+ # When we have it, we check *again* whether the database has been
+ # initialized. If not, we do so.
+
+ # If the database is perpetually locked, it'll time out automatically
+ # and we just let it fail.
+ cur.execute("BEGIN EXCLUSIVE TRANSACTION")
+ try:
+ cur.execute("SELECT version FROM schema_version")
+ row = cur.fetchone()
+ except sqlite3.OperationalError:
+ cur.execute("CREATE TABLE schema_version (version INTEGER NOT NULL)")
+ cur.execute("INSERT INTO schema_version VALUES (1)")
+ cur.execute("""CREATE TABLE zones (id INTEGER PRIMARY KEY,
+ name STRING NOT NULL COLLATE NOCASE,
+ rdclass STRING NOT NULL COLLATE NOCASE DEFAULT 'IN',
+ dnssec BOOLEAN NOT NULL DEFAULT 0)""")
+ cur.execute("CREATE INDEX zones_byname ON zones (name)")
+ cur.execute("""CREATE TABLE records (id INTEGER PRIMARY KEY,
+ zone_id INTEGER NOT NULL,
+ name STRING NOT NULL COLLATE NOCASE,
+ rname STRING NOT NULL COLLATE NOCASE,
+ ttl INTEGER NOT NULL,
+ rdtype STRING NOT NULL COLLATE NOCASE,
+ sigtype STRING COLLATE NOCASE,
+ rdata STRING NOT NULL)""")
+ cur.execute("CREATE INDEX records_byname ON records (name)")
+ cur.execute("CREATE INDEX records_byrname ON records (rname)")
+ cur.execute("""CREATE TABLE nsec3 (id INTEGER PRIMARY KEY,
+ zone_id INTEGER NOT NULL,
+ hash STRING NOT NULL COLLATE NOCASE,
+ owner STRING NOT NULL COLLATE NOCASE,
+ ttl INTEGER NOT NULL,
+ rdtype STRING NOT NULL COLLATE NOCASE,
+ rdata STRING NOT NULL)""")
+ cur.execute("CREATE INDEX nsec3_byhash ON nsec3 (hash)")
+ row = [1]
+ cur.execute("COMMIT TRANSACTION")
+ return row
+
+def open(dbfile, connect_timeout=5.0):
""" Open a database, if the database is not yet set up, call create
to do so. It may raise Sqlite3DSError if failed to open sqlite3
database file or find bad database schema version in the database.
Arguments:
dbfile - the filename for the sqlite3 database.
+ connect_timeout - timeout for opening the database or acquiring locks
+ defaults to sqlite3 module's default of 5.0 seconds
Return sqlite3 connection, sqlite3 cursor.
"""
try:
- conn = sqlite3.connect(dbfile)
+ conn = sqlite3.connect(dbfile, timeout=connect_timeout)
cur = conn.cursor()
except Exception as e:
fail = "Failed to open " + dbfile + ": " + e.args[0]
@@ -80,10 +99,13 @@ def open(dbfile):
try:
cur.execute("SELECT version FROM schema_version")
row = cur.fetchone()
- except:
- create(cur)
- conn.commit()
- row = [1]
+ except sqlite3.OperationalError:
+ # temporarily disable automatic transactions so
+ # we can do our own
+ iso_lvl = conn.isolation_level
+ conn.isolation_level = None
+ row = create(cur)
+ conn.isolation_level = iso_lvl
if row == None or row[0] != 1:
raise Sqlite3DSError("Bad database schema version")
diff --git a/src/lib/python/isc/datasrc/tests/sqlite3_ds_test.py b/src/lib/python/isc/datasrc/tests/sqlite3_ds_test.py
index 707994f..10c61cf 100644
--- a/src/lib/python/isc/datasrc/tests/sqlite3_ds_test.py
+++ b/src/lib/python/isc/datasrc/tests/sqlite3_ds_test.py
@@ -23,8 +23,9 @@ TESTDATA_PATH = os.environ['TESTDATA_PATH'] + os.sep
TESTDATA_WRITE_PATH = os.environ['TESTDATA_WRITE_PATH'] + os.sep
READ_ZONE_DB_FILE = TESTDATA_PATH + "example.com.sqlite3"
-WRITE_ZONE_DB_FILE = TESTDATA_WRITE_PATH + "example.com.out.sqlite3"
BROKEN_DB_FILE = TESTDATA_PATH + "brokendb.sqlite3"
+WRITE_ZONE_DB_FILE = TESTDATA_WRITE_PATH + "example.com.out.sqlite3"
+NEW_DB_FILE = TESTDATA_WRITE_PATH + "new_db.sqlite3"
def example_reader():
my_zone = [
@@ -91,5 +92,52 @@ class TestSqlite3_ds(unittest.TestCase):
# and make sure lock does not stay
sqlite3_ds.load(WRITE_ZONE_DB_FILE, ".", example_reader)
+class NewDBFile(unittest.TestCase):
+ def tearDown(self):
+ # remove the created database after every test
+ if (os.path.exists(NEW_DB_FILE)):
+ os.remove(NEW_DB_FILE)
+
+ def setUp(self):
+ # remove the created database before every test too, just
+ # in case a test got aborted half-way, and cleanup didn't occur
+ if (os.path.exists(NEW_DB_FILE)):
+ os.remove(NEW_DB_FILE)
+
+ def test_new_db(self):
+ self.assertFalse(os.path.exists(NEW_DB_FILE))
+ sqlite3_ds.open(NEW_DB_FILE)
+ self.assertTrue(os.path.exists(NEW_DB_FILE))
+
+ def test_new_db_locked(self):
+ self.assertFalse(os.path.exists(NEW_DB_FILE))
+ con = sqlite3.connect(NEW_DB_FILE);
+ con.isolation_level = None
+ cur = con.cursor()
+ cur.execute("BEGIN IMMEDIATE TRANSACTION")
+
+ # load should now fail, since the database is locked,
+ # and the open() call needs an exclusive lock
+ self.assertRaises(sqlite3.OperationalError,
+ sqlite3_ds.open, NEW_DB_FILE, 0.1)
+
+ con.rollback()
+ cur.close()
+ con.close()
+ self.assertTrue(os.path.exists(NEW_DB_FILE))
+
+ # now that we closed our connection, load should work again
+ sqlite3_ds.open(NEW_DB_FILE)
+
+ # the database should now have been created, and a new load should
+ # not require an exclusive lock anymore, so we lock it again
+ con = sqlite3.connect(NEW_DB_FILE);
+ cur = con.cursor()
+ cur.execute("BEGIN IMMEDIATE TRANSACTION")
+ sqlite3_ds.open(NEW_DB_FILE, 0.1)
+ con.rollback()
+ cur.close()
+ con.close()
+
if __name__ == '__main__':
unittest.main()
diff --git a/src/lib/python/isc/log/tests/Makefile.am b/src/lib/python/isc/log/tests/Makefile.am
index 6bb67de..a23887c 100644
--- a/src/lib/python/isc/log/tests/Makefile.am
+++ b/src/lib/python/isc/log/tests/Makefile.am
@@ -1,6 +1,8 @@
PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
-PYTESTS = log_test.py
-EXTRA_DIST = $(PYTESTS) log_console.py.in console.out check_output.sh
+PYTESTS_GEN = log_console.py
+PYTESTS_NOGEN = log_test.py
+noinst_SCRIPTS = $(PYTESTS_GEN)
+EXTRA_DIST = console.out check_output.sh $(PYTESTS_NOGEN)
# If necessary (rare cases), explicitly specify paths to dynamic libraries
# required by loadable python modules.
@@ -10,7 +12,9 @@ LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cc/.
endif
# test using command-line arguments, so use check-local target instead of TESTS
+# We need to run the cycle twice, because once the files are in builddir, once in srcdir
check-local:
+ chmod +x $(abs_builddir)/log_console.py
$(LIBRARY_PATH_PLACEHOLDER) \
env PYTHONPATH=$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/lib/python/isc/log \
$(abs_srcdir)/check_output.sh $(abs_builddir)/log_console.py $(abs_srcdir)/console.out
@@ -19,10 +23,18 @@ if ENABLE_PYTHON_COVERAGE
rm -f .coverage
${LN_S} $(abs_top_srcdir)/.coverage .coverage
endif
- for pytest in $(PYTESTS) ; do \
+ for pytest in $(PYTESTS_NOGEN) ; do \
echo Running test: $$pytest ; \
$(LIBRARY_PATH_PLACEHOLDER) \
env PYTHONPATH=$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/lib/python/isc/log:$(abs_top_builddir)/src/lib/log/python/.libs \
B10_TEST_PLUGIN_DIR=$(abs_top_srcdir)/src/bin/cfgmgr/plugins \
$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
+ done ; \
+ for pytest in $(PYTESTS_GEN) ; do \
+ echo Running test: $$pytest ; \
+ chmod +x $(abs_builddir)/$$pytest ; \
+ $(LIBRARY_PATH_PLACEHOLDER) \
+ env PYTHONPATH=$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/lib/python/isc/log:$(abs_top_builddir)/src/lib/log/python/.libs \
+ B10_TEST_PLUGIN_DIR=$(abs_top_srcdir)/src/bin/cfgmgr/plugins \
+ $(PYCOVERAGE_RUN) $(abs_builddir)/$$pytest || exit ; \
done
diff --git a/src/lib/python/isc/notify/tests/Makefile.am b/src/lib/python/isc/notify/tests/Makefile.am
index 1427d93..00a8d3c 100644
--- a/src/lib/python/isc/notify/tests/Makefile.am
+++ b/src/lib/python/isc/notify/tests/Makefile.am
@@ -6,7 +6,7 @@ EXTRA_DIST = $(PYTESTS)
# required by loadable python modules.
LIBRARY_PATH_PLACEHOLDER =
if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$$$(ENV_LIBRARY_PATH)
endif
# test using command-line arguments, so use check-local target instead of TESTS
diff --git a/src/lib/util/python/pycppwrapper_util.h b/src/lib/util/python/pycppwrapper_util.h
index 3f396e2..462e715 100644
--- a/src/lib/util/python/pycppwrapper_util.h
+++ b/src/lib/util/python/pycppwrapper_util.h
@@ -293,7 +293,7 @@ protected:
/// \c PyObject_New() to the caller.
template <typename PYSTRUCT, typename CPPCLASS>
struct CPPPyObjectContainer : public PyObjectContainer {
- CPPPyObjectContainer(PYSTRUCT* obj) : PyObjectContainer(obj) {}
+ explicit CPPPyObjectContainer(PYSTRUCT* obj) : PyObjectContainer(obj) {}
// This method associates a C++ object with the corresponding python
// object enclosed in this class.
diff --git a/src/lib/util/python/wrapper_template.cc b/src/lib/util/python/wrapper_template.cc
index a703731..426ced5 100644
--- a/src/lib/util/python/wrapper_template.cc
+++ b/src/lib/util/python/wrapper_template.cc
@@ -299,8 +299,8 @@ initModulePart_ at CPPCLASS@(PyObject* mod) {
PyObject*
create at CPPCLASS@Object(const @CPPCLASS@& source) {
- @CPPCLASS at Container container =
- PyObject_New(s_ at CPPCLASS@, &@cppclass at _type);
+ @CPPCLASS at Container container(PyObject_New(s_ at CPPCLASS@,
+ &@cppclass at _type));
container.set(new @CPPCLASS@(source));
return (container.release());
}
diff --git a/src/lib/util/python/wrapper_template.h b/src/lib/util/python/wrapper_template.h
index d68a658..be701e1 100644
--- a/src/lib/util/python/wrapper_template.h
+++ b/src/lib/util/python/wrapper_template.h
@@ -37,15 +37,15 @@ bool initModulePart_ at CPPCLASS@(PyObject* mod);
// Note: this utility function works only when @CPPCLASS@ is a copy
// constructable.
// And, it would only be useful when python binding needs to create this
-// object frequently. Otherwise, it would (or should) probably better to
+// object frequently. Otherwise, it would (or should) probably be better to
// remove the declaration and definition of this function.
//
-/// This is A simple shortcut to create a python @CPPCLASS@ object (in the
+/// This is a simple shortcut to create a python @CPPCLASS@ object (in the
/// form of a pointer to PyObject) with minimal exception safety.
/// On success, it returns a valid pointer to PyObject with a reference
/// counter of 1; if something goes wrong it throws an exception (it never
/// returns a NULL pointer).
-/// This function is expected to be called with in a try block
+/// This function is expected to be called within a try block
/// followed by necessary setup for python exception.
PyObject* create at CPPCLASS@Object(const @CPPCLASS@& source);
More information about the bind10-changes
mailing list