BIND 10 trac2967, updated. e6bd8c1c59c87dbbed3160350e063031839d6425 [2967] fixed an unpythonic emptyness test
BIND 10 source code commits
bind10-changes at lists.isc.org
Tue Jul 2 21:37:12 UTC 2013
The branch, trac2967 has been updated
discards 753d1c06361fdbd36e2cb56eab48154594142fb3 (commit)
discards caa92a75d165ff476a6e655a114b5ac7e16beb54 (commit)
discards cb6662dbc8ff3f1be543f449305b4d28860b66d8 (commit)
discards 8e0b50a3dddf0148ea52a096c1875aaff78a8688 (commit)
discards f3d19839bb0347c5796c2fbbe0c1675d8e326224 (commit)
discards d60f2832267b6371cf792c577136e6a41cf1b3d6 (commit)
discards 9579d58bf9a8e4a8a45340bfa8d419ad768e0880 (commit)
discards 5422d3a42d78615dedb715533e8f308dbf05af28 (commit)
discards baa881945da8862a8144799ae5a6902a6b15030f (commit)
discards 2893a15da290c257e7ebd589ebd53560cae4cb44 (commit)
discards 7275e08a8ab21e37ee86cda913606dde68482e8a (commit)
discards 81b053d43466ab63c91048865fd21aa7fc66e4b7 (commit)
via e6bd8c1c59c87dbbed3160350e063031839d6425 (commit)
via 680e748149d99afa27cf4a5a7de83d1652db06f9 (commit)
via 2ae1f990955e7a2b5aabef0ec93448c6c7e8c89f (commit)
via 338cfe287476bdbb2867cf3e745738435a0e2462 (commit)
via 23059244dc6933f7eed78bf46b6f7f8233301618 (commit)
via 5c03ce929bbda1f0fb271b35b5b3dbe1a12696c9 (commit)
via 6d40ecfc230408137edcc43f2653c70fe12589d6 (commit)
via ed2fb666188ea2c005b36d767bb7bb2861178dcc (commit)
via f33bdd59c6a8c8ea883f11578b463277d01c2b70 (commit)
via a3e8b0ad37ebb999e29c390332045bc9babfdc3c (commit)
via 45e404c1b58487d180e91d4b4eb0f5059902b190 (commit)
via c04fb71fa44c2a458aac57ae54eeb1711c017a49 (commit)
via e773dac499b952107dfc5e34290ec421b8bbf7c6 (commit)
via 5e61d601d9424aac561c0661061e19188ed8d93f (commit)
via 41d5b2ead11a334e89a7f04ab38571e2795083d0 (commit)
via 0fc40f1677c6bdaf1e2920c6f86b2bc6c15b4cb0 (commit)
via 1a0742f1fd61234a6ec395729b0c4c4cad64aaac (commit)
via e7c52fec35a1f6a49351f58877d21fd43b4082e0 (commit)
via eac5e751473e238dee1ebf16491634a1fbea25e2 (commit)
via 351b82d0ed7d964803be52b433706377d7b2f780 (commit)
via e42db3d2834457c04e19c256a67f4059cd36860d (commit)
via 971e77005e139347d5d4fedf0cc87bfb26b2685f (commit)
via 6e99f2cb85dd4722f7a5c2d15ebf2458cecc7b32 (commit)
via fa4e16aaa9ee6e680606f0211c71c92669b8e74f (commit)
via 61dcb03fbe2c9a4e3df6e8afe269c0d94f00d925 (commit)
via ed780cbba41428341c5956ff5b032d829d059cab (commit)
via 58f2a7f08242fad52c397bbf7681b209dc84c896 (commit)
via 407655388fe055d2a8a950ca31e36a9102b59682 (commit)
via 41e40c5cb4a62d32889fbbaf704792add24b02d2 (commit)
via 1949be3727367e2b4f9a399f3678a8fa1e969bc1 (commit)
via ee58a132fe6313d7d2d9ecc7eba8b455975ebac7 (commit)
via e15f18aff64c8f7481546e877f059290653b9926 (commit)
via 6916c32a3f5f409a01157d1957ebd3c9e296ed99 (commit)
via ee90ee28b431aac9b513330e31032a20d2c0ac27 (commit)
via c3ef6db1a2b58b3732f1d2aa169127ade79fcb77 (commit)
via 5fe6fd9977984ddc71d71266afb41f963012ddf9 (commit)
via a4ff0d97db3b04c67d3bdd246c04d1dc4c47fb4d (commit)
via 4afd4b896c591245f51b992296039ffd5b3991b5 (commit)
via 489316fd7418436e94de387ce9477c18e2b4da59 (commit)
via 8b4019ff1577d1d32e9400c2baba77c948d0bfe4 (commit)
via 603b0180bc4c1e9b7501c9e3292cad6f3427bf65 (commit)
via ac446f02802bff0a91af4caa6e6717b8f0ff350c (commit)
via dc6150ca4dd1d7dc44bcadb34acecb2d9176a9eb (commit)
via c4004066611a83ea5589af57bf07172da5a4aa94 (commit)
via aa0265f874883431407a6ed6a2f0d543fc4e8507 (commit)
via d8991bf8ed720a316f7506c1dd9db7de5c57ad4d (commit)
via 95d6573794927ff46242625736d894ca4758f55a (commit)
via 464664e3237b5dcb61deaf9ba4b5224bb11f412e (commit)
via d05d7aa36d0f8f87b94dba114134b50ca37eabff (commit)
via cef64b540e2353c080a8938ccf1353a11eced58a (commit)
via 1095506737bb6cc7cf8b0c00ea0803ffcbc98ad5 (commit)
via 0d3ac3452be35e57346715a296e05deb48a03ccd (commit)
via d878168fcd942b66b3f88ffd162010dd86ab85a1 (commit)
via 8354cca4763eceb0dfd3270823012511c14512cc (commit)
via 454dbb49604074ec1ee9136598b3ef0ccf3af256 (commit)
via 20b0efb43cff8020da949df03ce13d58ee6e4eac (commit)
via 7f644c2532ecf0f3dab826a091fbe900dc5352f4 (commit)
via 5b0a2957d3a806ce84ad86dd7789edf3ef495d92 (commit)
via a81a52389a3ce477db992da27cf09cb1adc0e49e (commit)
via 6d42b333f446eccc9d0204bcc04df38fed0c31db (commit)
via b4f1cd645efbda886e123943b5e14222b40e7a9c (commit)
via 204733844d2984ed49eb8e2da89666f6a1729732 (commit)
via d399683b0d058126c2889d090b6067dcac0de859 (commit)
via b178f2a38b94200e35758f6e8d7e570a0cfc41ec (commit)
via aa6d3a22e29241cb6b8efb228821a99fd0b26240 (commit)
via c18cb310851ea2f558fd2b6103c9f5badd4cd337 (commit)
via 6e42b90971b377261c72d51c38bf4a8dc336664a (commit)
via 382705e83e65a261d1dc3b98407226c1340bc90d (commit)
via fcbb1a926c62a986d97566002bf318822161a5e4 (commit)
via f1c399008dea27eed1200e392afd08929c5f9f96 (commit)
via 213d905ed8661b9d1f5ce214c6f391e702d518d7 (commit)
via c5b11e54a93c528d549a66206f0ff2c2ddf4273d (commit)
via 5eea9d893ec4ac21e89f8b3b0f3e68af625eed48 (commit)
via 5d9916e93986dbeb890cbce2782d67b9f535adb4 (commit)
via f999dcb8704206c2cc1ea09ff9a4ca76a9e5affd (commit)
via 6d1d496ef090cfc188842a1fd1f9d0ca1b695826 (commit)
via a63b3077dd102512b9002e204a955f5b8d783ff4 (commit)
via 16156f814b54dffdc0304016aa41b0746c613d3a (commit)
via 361853d37c33abd413b48f9bc1c80578840aa901 (commit)
via b4b1eca7131cedca72a8973718881c1f18d2010f (commit)
via 5e8a8cc152ef5cd151f99389f475b2a16832fb9c (commit)
via 570fc14873220c13f62e622fd3f227a73ae9a106 (commit)
via 010bda2e96f5763e6eec226ac1cee96bce2588e5 (commit)
via befc536e55419f950791c6b32398ed6416d1abdd (commit)
via fac95b174e553ac8eba5805c47541530e9fbb2e1 (commit)
via dd35de3a43c6aec1cb67135da6195e8eabf028e8 (commit)
via 0af5b54f1c40328d94b053bbac09811bbdf3b116 (commit)
via 1e82366eb6afcaf56fefd13f3158487f02935e13 (commit)
via 7341eefceabf675af89a4514b89f0a45ba4a711d (commit)
via 29827fea890f2657705256b70bd38e6295d37588 (commit)
via b99e16aa62aae9000905822f4a76bec8fff3591a (commit)
via 7a55d6839c96bc41cb440e30822e219911ed2ea1 (commit)
via df2c166741be35284c9a7711b28513fa0f5a128c (commit)
via ee27d69e45c9632a482d610b28268f22af85ab5f (commit)
via de12157bbf027dd4bdf90201a4699c1a7287a819 (commit)
via 3b778c9a251f470985f330efa1a314869f5cca1d (commit)
via edf1ae8f84a9bd9ff5725c80c9f61872c8658fc3 (commit)
via 87fc0368a47156638c189fbf071471ee0d9c4463 (commit)
via 2b6d8c99eedfc5e77477461963bff7ae031192bf (commit)
via b021224a6314396eccfb9c7e0af223474079e864 (commit)
via 18155a69d96d21d845f990010d4e6fdf9589fd6a (commit)
via d1da93e647dfe42cebf516f34d1e5eb732f9cfc5 (commit)
via 5f57054ae173dcbf802f1d7c03a485d7d72e1814 (commit)
via 9a3ddf1e2bfa2546bfcc7df6d9b11bfbdb5cf35f (commit)
via ae6f706d85ae90ca971d2cfef2abfe7e5bc7856d (commit)
via 42e6736faa21f98de53a4ac0bc52306041581d91 (commit)
via 348d7dcb7f31831e5db9c282c213aec0795d1ff2 (commit)
via 19e016eb1b80db43645aca734b1f511ff2b142fb (commit)
via c730f9ab1b00b3611d82d0c00a2f0faf56b8162a (commit)
via 253803efbad0e147f37f6ce7a07949bbb79909f5 (commit)
via 9df9a672eb672bd201e3762f3f321082e7e1d17c (commit)
via ced15db4ec415ce080457136acebf66bebd7bf7d (commit)
via db2f7c545136ec475f2afda44ff182b195e2ed28 (commit)
via f57669ee937a15733a04e069aae01f3fff920f0d (commit)
via c7115f5b90dc167552fe2de1178def62a948afe6 (commit)
via 38ff6231fc72640e2d67de0583a6af1bb98a1e3d (commit)
via f3599de81907489d828748988c660d644257f0fe (commit)
via 968625c32a07347edd50550da5fcf13c17b61d87 (commit)
via 51861a16ff9aa86bda48baaca5cac0c37eb10dfc (commit)
via 0507bba4a8dcbd4a3e7f0f6ad03eb5df6068201f (commit)
via 7e343de52c027ac35f231f906cfdd19500e8b7c1 (commit)
via 46544d453c97522254fcbef13fb57c67be622cb5 (commit)
via bdf66e1261cd868e356392e478b0361fcca349b8 (commit)
via d6b484dc1a2689bf7fc8f1cfe2454ddb025b2a01 (commit)
via be6122f520871344324be1ceb9f118195d508bba (commit)
via 8cb8350f576413a4a5f0cf6fda366219ab316b89 (commit)
via 472b197fab494c18fa3ed25018176997da20c22a (commit)
via 8c4f48f99c0a25c37b15f7819aa5c01a3a1b6844 (commit)
via bd4a777c870eb78b1cdfc6df2c8e13ea93480c12 (commit)
via 5485e7248939b67d6dc94b18925cee753df14390 (commit)
via df956ae5a69124cdfd7704ef0582adfe254df802 (commit)
via 7c2793d41b23377529c667f89ca1afb69238a7a1 (commit)
via 780a3b3bd3492e500154e53e6835d82eb2eb3131 (commit)
via 5b41c700bd362d76ce19b032c75662f64b5a9ca8 (commit)
via 8f594ed3b5bcd0d29c1ddb2fcc0f91854503606e (commit)
via 3a7fba8e0764537e4782b8625b548bd199e30e28 (commit)
via 7f026c48836d9366316469190e0f82b882b228c0 (commit)
via 1a8db0437ffc3367de7762447cfa1f4efa83cfd7 (commit)
via ff10ffe92269cc3cdf85826fd81e2c01dd909801 (commit)
via b6f5648870e85b8451b624391ad22bcb04a219d2 (commit)
via 43fae7e83655a6bdb1e1b57654436ffaa8fad8c2 (commit)
via 007341505307a53b43802c747fe09435346bbf46 (commit)
via f57c476f33531dfdbffe892b0462338872f64870 (commit)
via 7abee20ad8cfd20c3e33f58c414798276b48484b (commit)
via 253dc4476ac2052dcd9af515ea4955b09c91cc7d (commit)
via 741e96ebecac59ad257288094eab660661344b39 (commit)
via fc951496b21edbdb9c6ac53ab8ed596035177d54 (commit)
via 430c0c7e0c2b84bbb8d8ef5a2d27daecb5af69a9 (commit)
via d6b2905297b0ab859ed0dfcf67eaff340e7eb1dc (commit)
via 2419d18342284ebc4e7099e65f9a8ce2d3dce15c (commit)
via d2b107586db7c2deaecba212c891d231d7e54a07 (commit)
via b8679425f97da697e4a8766b773fb9d400af6d79 (commit)
via bee3ea9fabdc724d9751c073ecf6f71f7492c286 (commit)
via e5d3eeb0edd4f47af44fee8a5e7ec1b165d8db30 (commit)
via 55f676522e6df80e3293ac5c0f845805f622f3a8 (commit)
via 9368ade9509ac5a2278c52d4c36c3a84d872531e (commit)
via 45087a113407dbb6b657f6e410dcf8158219effe (commit)
via 3a874c84baf65289d49be69c041b8340c946617a (commit)
via 51c802b42eafb9d5da4c7f681051cb5fcfbd3df1 (commit)
via b037963e65fcc56bd6ffd847bfb41d172ad1af1e (commit)
via a9d957c18a7428dfa6d57e00baea49652302b4f0 (commit)
via 8780867306315f587012fd7ec9c623a0c439ffa4 (commit)
via a5c5c9e3456d94ee0465a6665dc1c2aafeb21091 (commit)
via ad2c0ba137e17bcf43b9be5a286d99696fd3009b (commit)
via 78af6ba875985a8c33eb1dd54adbe35fc720574b (commit)
via a0b8bc3685dcffca7713bea4d6d738827c045be0 (commit)
via c855ce32bf071dae01559afa743e757100d9a441 (commit)
via 11cdeec052d94483964f379d822e58eb58ffe522 (commit)
via 40d9424ef15d3c6d484a03772fa15d57781161f8 (commit)
via 7ffc29a5c462f9359d6059e6b14b33653f6da0ad (commit)
via a169232953e34690749708a29b9f6c22e94b2dbf (commit)
via 65b0c0b8a22a6f066ed6d9cae81f1de3cbca4682 (commit)
via 7775ce49e0e07406db6d7dc158b7c01060aa93dd (commit)
via 3c7a0170392994d0c6c67b40cfa8b0d902d5312f (commit)
via b63fddd9ded87b34a2f9072d118bbfaca420c045 (commit)
via 922c6e4ecb69334bdcdac0c9fa82c4615c828609 (commit)
via a2a6bcce71abf5bbd7ee1da2d375a85c297ee21e (commit)
via a9f39355b94fe20924aa02f849e35e7000c67631 (commit)
via 2cd1c10416f27e6b054c3fe4726ea771f63d0bb2 (commit)
via 2652e68f004f5c329b4979e93bbc020fb59af32d (commit)
via 421e22c7f1437c632a7e9aa904662a9344b13446 (commit)
via 0fdc68bb68b017223f35a3dc39e5255a5f255d10 (commit)
via 3a98bed98afbf27ec36a941936d1bf11c656178d (commit)
via 668a507bdb0dd52368621fe7b8aed41b796a9529 (commit)
via b14e0fb808dd161c89a094924f153393b3fc1348 (commit)
via fc735da7e6210f87e4e5a36fce91eb990b8bbbc8 (commit)
via 3a0d2b90ef3766a8a105a8a9b04c92b8620eaeb1 (commit)
via edfad4a9bb65049599adaf9cf68baedf7bc072b7 (commit)
via e93ef80d1c7e0f431f37f75c68f45c3aa78b70f7 (commit)
via 0f00c3880a0cb7498cd5adb86831f779d57d321f (commit)
via 696f1e012969f47880ecf8c5be0c5935a99e3501 (commit)
via ea778b2bd22edec0b971791ae59008328163542f (commit)
via bd74cbbdfc7828d5517089d1c9d4f2768b7bc7de (commit)
via 7448e48ddae3c48408bc95457e21d0ca189a9ea2 (commit)
via 310c6201416fe8b57c691a9bbb045d781c601fea (commit)
via 32f964bd9bf8ab5ee58b409a3006779b9a504b03 (commit)
via ea28774bc7f3586c2ef33028a88f2c51cfa9b691 (commit)
via 3d291f42cdb186682983aa833a1a67cb9e6a8434 (commit)
via 3174827557c878ee62a0624aa3ac93cc3fd37bef (commit)
via 95cb383de7f191fdd4e8796ef52b469be90e5523 (commit)
via b2f0388d5396ee32c6e8b6593130fe87e26153ae (commit)
via efe8fae1cf4f3a272c8da437113f1c7b90c9607a (commit)
via 9206de78991d2dcbbfec451e896dba0006bc5b71 (commit)
via a43f9f2860f156daa1a8caf95d6c0ff1a95323a9 (commit)
via ebe5c03858d0e41ae7e3a8477399d0f54712569a (commit)
via 1d9ea23f74983eb952733a9cacd5cdc6d99db5d4 (commit)
via a66549d4455e23d4486d9287157790ccd49a942c (commit)
via 007b1f5cd7b48bccf501aa624325388e360aa828 (commit)
via e65eaed0d786f8939852b6368de827f7294d396c (commit)
via 983f11cfdd11153b234ff7626c5ff1dccb1baeb2 (commit)
via ab3f32eee4e5eb1d696ac88392497e52b994fbbb (commit)
via 11de0085d24cdb5dfcc5a158cd30699cfde90159 (commit)
via 81847206409e7cb4bb69bda8a22f7fc54a7a6b2a (commit)
via 2ca7ef10889eccf7ebe70f1662fed83c33f62c19 (commit)
via 2987b196ad617e5cc8f759717edae50866162888 (commit)
via 554c01b75c2e1a16bebd00a2d1476b0c85f70ef3 (commit)
via 054d97ceb5c12455b1272e00a860e111f67b6ce9 (commit)
via 425062cfead674420744cee41784e4d894f9ac2d (commit)
via 0fa66c2662c0f8eac63ed44668f90d8b687859c4 (commit)
via 1b1c061696198766e9f6821e69cb1658f83c2030 (commit)
via 2379e411e9671d4ecbee71ca5a9663053a40d091 (commit)
via 94f2a0246ef707722666dc81c68cb58ac85414d4 (commit)
via 8ec2caa761eac2681485217645e7e328924c3c7f (commit)
via c27421b7e7b41247b41aca0de903f328048ab920 (commit)
via dac0b87d5b0466e3aaf0f49ec7b2362606c03415 (commit)
via e626839cc9e9a74aa7c7b560c4fc2997394f24fc (commit)
via c8050a3b7d7e3060c59f14c777af5a8ad675f234 (commit)
via dde7e27a9effb9702e99f2145254ae13821d7667 (commit)
via bd3f52daf52aeeb006cc7ac420f11bdc9cd284ed (commit)
via 157d04befe4e1b7a3a9d3fe34d2a39af5709ef1a (commit)
via 21034c5291df20ab7b9ebf79c16336af25e16594 (commit)
via 3ffa4a979da3b91b0c721d931f0d9ad333f076a8 (commit)
via 9f797b9baa3972fc391a6bfbd2a6a9bae6fcf375 (commit)
via d0080895a3a34b3e6d02c38021700e7a02245afc (commit)
via c16bf9a2b17f26c73b5be8aa9a7b4f3570b8f3c1 (commit)
via 3cc9fc1217967770551c6acb3ba8f6c6a0387864 (commit)
via 56e4d09d7b49fa6322c3ed52a2c38898d8a27046 (commit)
via fbe7def71c07f6f36af8f3d71f1bc83cb14c9e3a (commit)
via f7b60d9d56ce0447fad6f6b6ac241477535e7235 (commit)
via ed508fe48ff9a13463ef0bf800a74f7a9dec14f6 (commit)
via 5543f4c43126a4477807ab2c043907cab8f7b04b (commit)
via 6e4e55d9952dd59962c161d2fc2552176f805244 (commit)
via d1bbd4878ba48cd9ebc623b1e8666f425c86e84b (commit)
via 49305bca03037ecf7cc2f91f75c4fd1c2d8bd5d1 (commit)
via 056ca56102ee79dbc19629cdc6720d864b7a7e8d (commit)
via 797308ff2aedadc0281c8ca9c84daaa3c6d6d42c (commit)
via 74625ee6146a2b28c4caaacef2fb9c9ba9876dd6 (commit)
via dc488cb9679c44617fdac1aebb6a821bddd39736 (commit)
via e05af734b0416593a0978c3ab22bfd2ee1d991ed (commit)
via 88ada22821235de67f0d3efa32394b83d11e3b79 (commit)
via e81de23719368f0fcea05d1607c2ffafb76f0359 (commit)
via 9bbd16649e750e9a4c7ce5fec0265f91b10cc4fb (commit)
via d6c6fe485c6ed9a4ed9a2efb4792870a58b33c7a (commit)
via 9d30cc97c91754a94f15ae288220401fb11cfc06 (commit)
via 15810521bfeea89e77334df66512863911a4d03c (commit)
via af91bbf6f4104b9731264270e2f1356b0ec74981 (commit)
via 171e04fdad35ccb630a4255efba73af01536f9de (commit)
via 779d5913cc017664e72d4e6008b1e7dced1a6ae4 (commit)
via 523e679dd68695f1815cfb5e0a31cc7bf4f742f6 (commit)
via 41439e23c82d769ae7e1bb04453723a8770e6c9a (commit)
via e12bb75e5e456ecaaa738936aea36fc255d342f7 (commit)
via c7c8f0af322c42e62e6f5168e554e56776230a79 (commit)
via 7035e189a7f94e8284c1da4eb538e6db24fafca4 (commit)
via c049cba60db67b2ab0beeaccb690d77bd0c63689 (commit)
via dcef553fe87fd8c1be039394ad258a20aa8cd0b0 (commit)
via dcd88c02239b954f9617f998852c4399ff1f9a75 (commit)
via 9d2bd293b53d41efc0eb27c3f7e6cff01d264c8d (commit)
via 2cba268b3ae90e44b92227ebbbfa15e0fd3bdb5b (commit)
via 7028594590cdbe96b995ffb104a79551844999ee (commit)
via 45baa7aba39e666d0eed45e140567ce3be62066e (commit)
via a786fa02df431025c8419ccc7d0e8f4023e6eaba (commit)
via 92ebcb9a94e233362660b3d3f29df319da919286 (commit)
via 3a6139c33b7db517e76723d631ee49d47dc691dd (commit)
via 0df2936c8f985071e97fb17d9685266fc0d443a5 (commit)
via b1c398bbc0d7b7b066347cff13ea8fb310bf4f10 (commit)
via b9fefbedb20b98f3a8a39550a9a1183208eb10ea (commit)
via 777c26581b971935e9d77cc1b70f0909ab3534e6 (commit)
via 347a2846eaacb84791fedcda6537d2fd8be6160f (commit)
via bc6a657a16a55350992cbe251f010d0a46d11dea (commit)
via 946f8117826ae2df528c76901f6e1426cdf614e0 (commit)
via a555e12ddf75657f9ba417f1bc9366c8ba094854 (commit)
via ab1890f4594c873aca0b7f483234de5cd3e5e01c (commit)
via 71376fdfd9be70f5dc7f28fcec5bb75fbeb27ccd (commit)
via 226b4d1a1e9b94d5eb9eb92e6a35f24a1ea1d0bf (commit)
via fae036eecd789c8815f8f52f5954835ef9fcfa82 (commit)
via efea64180e770421ac70e4a607e0b5587433b55a (commit)
via cbd8f4ddcdff128ec9f545b35151a8c64affb063 (commit)
via f61a5e73721ee68e4fb330f59a15fe5baf8fadb2 (commit)
via 3d96ea46c8191bff66e8ec93b00414f6a78a66f5 (commit)
via 358984aecfd6a7944624577f931e09c3fd53febb (commit)
via 86d1383a1a5998ce4fa7cac17b9f15bde7d19712 (commit)
via 38beb040c82df7d6502f3f25b52773aeecad6041 (commit)
via c5352f69e7fe7b9071066b4ce40d47c2d7948a1f (commit)
via 71eec22b3e41413651107079d9f9b5cf34d2c7ca (commit)
via 9b1ceb841266b4df04ac717186c86b22f77fe314 (commit)
via b18071c40b073c7f38643dfe19c7d10f2127abfc (commit)
via fe14d0cfc32360547afc55ceded8c69123a70214 (commit)
via faffa474e24d5e3bedc06088a388a0965d5059af (commit)
via 9163e6f7a614dddc6d1cbb7b9e879b802fda14ce (commit)
via 58335cecf1afa60e325b5ce952b23b40416bd4b9 (commit)
via 632a5596298f9d253693631606e026e01d8e7fdd (commit)
via 1ceeb74c1eba0ce65ea05a55fc86149d3fc733f8 (commit)
via 35c2010ce56c5fafebad8574184d083d1182534d (commit)
via 19030355897c6185f75193f299c8666b595970ae (commit)
via b3befd1fe7fed482817a7ccf05baa6292fd7a56a (commit)
via 9a2922daaac4af5b378144b518a1b1fec767c798 (commit)
via c68b2f25695d5fc4c64d8a45a7414695a8281c53 (commit)
via 71de47f8d14d274ae2223e56423d6022a3cf0369 (commit)
via 5cc7f89d8f2fc1ebb563556fa7e250ceb3d4aedb (commit)
via bb08c0bcb4791c3c39f68ae8cefc2e9402ae9b92 (commit)
via d2460249eb2a7340b8adbdf12306744ba255eb6d (commit)
via 2935657cc5e260f54be045b28d7184adb406d3d7 (commit)
via 77010a2090eb1cade512f03e880df4cca13e9eb4 (commit)
via d340d8278228a062664eda13926f5af3daef8b35 (commit)
via f26eae2533b0b2c6591ad6aa9116dc9ba00c462a (commit)
via 70c715efff4255744322403c03a7be1e64d99c5e (commit)
via 6a4ee80ea2995124b737bd718f16f1baadb5c7dd (commit)
via 508e3554d5167a27ed1287a3903f851bfaf11a17 (commit)
via 61a6cfae29c657daedd334fc2a157d57e85948a3 (commit)
via c188affb416fc02d45a0932fec1e788303425d88 (commit)
via 64dec90474339bab08323f8b76cde47ddd2d8f73 (commit)
via 4bed4a498354aab3faf234bc76b3f0fbc5a94fe3 (commit)
via 6ebe6bc5875d21ad1d822c251462cebe3e769aee (commit)
via 6bcd96718c01bdbe0d563f6c67c46b441147ba60 (commit)
via c1d7e3f9bfa473478a1070467bf0773f02e54b6e (commit)
via a8186631e5cd811b2ebf8df6d837182ea39177ea (commit)
via 6ca9ce763678cd8ce47680d083eeee299889427a (commit)
via 40e0617397c03de943d81e05541c4de17885f6f0 (commit)
via 33352a6b9af110cb06c40c8cfbecc26b5e3e89c2 (commit)
via e567e51a23beed32f03870d9885e462e1bdc276c (commit)
via d65d61499f0f2ca9547d90e3c36fcd83d55d8ba8 (commit)
via 8c00b35f6427ba14e85e7c335367418b28599154 (commit)
via e6a262a1e5a0091b06840a46a20a53eed38eee42 (commit)
via 64bc2cfab72d67bf9e4f3cd19fd6ebb586208e67 (commit)
via e37828dd1c90541e6e6d7f8d373d95a25ce6bbea (commit)
via 923462dbabee6917d6693e62c80bd0f7c8fd980b (commit)
via e686a5339d6927d4ef7bf35a8e5d9c2ddfa98149 (commit)
via 89b1ca9494abdcaf3ef2beec3fcb015b3328fea7 (commit)
via d3a4dc9a80f85583f71c789555deba8fae2f0381 (commit)
via 1ad0a17cfbc48bec8620958e3495720355b34f81 (commit)
via ba0004b0092cf2d7652b9a66212774d19f91517f (commit)
via 29488dd4130d944487bb2fb813401773ea155428 (commit)
via 9deba8e6b563fbeeb98af85b89aee5c9f4878a03 (commit)
via 26f53e4d06284cf0854ec06d14b5071b26996347 (commit)
via d219feca6015a99293d37d030ad791263432f533 (commit)
via bf1e13564ab29539beb465d212a51e895e640915 (commit)
via 63e17482bdd2e68e56f2642267169a101af9e012 (commit)
via 6ac0a0914155c3379e6608d3b6bc884ba1e59527 (commit)
via 82679565adb6001d2b445cc932935d9a0561fafc (commit)
via 367da9bd6d92f7fcef7505bf8c54b1a702146eef (commit)
via 192787144f8338dfc6443e6907ce2274fda95ca9 (commit)
via fdb231c9e31422dea878922607a0268675f50ec6 (commit)
via bc9125aec50bb75fcf601eb2e0c9bcb5dd3a1a1b (commit)
via 9869caead03573be43747c30c4f38929c975b409 (commit)
via a756e890b5e9dee98694bb88d3714613c0af686e (commit)
via e9799d37bb8a0d145d0080c8ac93781c08f150e6 (commit)
via a713b09fa81eb688de94ca720a304138ed48f96b (commit)
via fb3a565eeded13fcb8f746229c6a55aa7f1d000c (commit)
via 38293a1f2ca44708f117d870ec1c6e50600fc2c4 (commit)
via 70cc3db9edd5e1b30d8efb8107e57de021fdaac3 (commit)
via 5d221a4027cdd09be11a45cd30bea2ef5b67bbed (commit)
via 07738f9d7122d58dd7e42b7fc414f46c3f5ede35 (commit)
This update added new revisions after undoing existing revisions. That is
to say, the old revision is not a strict subset of the new revision. This
situation occurs when you --force push a change and generate a repository
containing something like this:
* -- * -- B -- O -- O -- O (753d1c06361fdbd36e2cb56eab48154594142fb3)
\
N -- N -- N (e6bd8c1c59c87dbbed3160350e063031839d6425)
When this happens we assume that you've already had alert emails for all
of the O revisions, and so we here report only the revisions in the N
branch from the common base, B.
Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.
- Log -----------------------------------------------------------------
commit e6bd8c1c59c87dbbed3160350e063031839d6425
Author: Paul Selkirk <pselkirk at isc.org>
Date: Thu Jun 27 18:48:32 2013 -0400
[2967] fixed an unpythonic emptyness test
commit 680e748149d99afa27cf4a5a7de83d1652db06f9
Author: Paul Selkirk <pselkirk at isc.org>
Date: Wed Jun 26 19:42:36 2013 -0400
[2967] removed Auth/database_file from lettuce slave configs
but not master configs or xfrin_bind10.feature, since xfrout hasn't
been converted to the general data source yet
commit 2ae1f990955e7a2b5aabef0ec93448c6c7e8c89f
Author: Paul Selkirk <pselkirk at isc.org>
Date: Wed Jun 26 19:39:56 2013 -0400
[2967] cleanup: removed unneeded AUTH_MODULE_NAME
commit 338cfe287476bdbb2867cf3e745738435a0e2462
Author: Paul Selkirk <pselkirk at isc.org>
Date: Tue Jun 25 14:43:27 2013 -0400
[2967] cleanup and slight refactoring
commit 23059244dc6933f7eed78bf46b6f7f8233301618
Author: Paul Selkirk <pselkirk at isc.org>
Date: Thu Jun 20 16:25:17 2013 -0400
[2967] convert to general datasrc config
commit 5c03ce929bbda1f0fb271b35b5b3dbe1a12696c9
Author: Paul Selkirk <pselkirk at isc.org>
Date: Sun Jun 16 00:24:42 2013 -0400
[2967] refactoring: remove unneeded ZonemgrRefresh._zone_mgr_is_empty
commit 6d40ecfc230408137edcc43f2653c70fe12589d6
Author: Paul Selkirk <pselkirk at isc.org>
Date: Sun Jun 16 00:09:56 2013 -0400
[2967] fix line wrap problems, no code changes
-----------------------------------------------------------------------
Summary of changes:
ChangeLog | 70 +-
Makefile.am | 3 +-
configure.ac | 21 +
doc/Doxyfile-xml | 7 +
doc/Makefile.am | 4 +-
doc/design/Makefile.am | 1 +
doc/design/datasrc/.gitignore | 3 +
doc/design/datasrc/Makefile.am | 30 +
doc/design/datasrc/auth-local.txt | 137 +++
doc/design/datasrc/auth-mapped.txt | 98 ++
doc/design/datasrc/data-source-classes.txt | 364 ++++++
doc/design/datasrc/memmgr-mapped-init.txt | 132 +++
doc/design/datasrc/memmgr-mapped-reload.txt | 92 ++
doc/design/datasrc/overview.txt | 68 ++
doc/design/resolver/01-scaling-across-cores | 347 ++++++
.../resolver/02-mixed-recursive-authority-setup | 150 +++
doc/design/resolver/03-cache-algorithm | 22 +
doc/design/resolver/03-cache-algorithm.txt | 256 ++++
doc/design/resolver/README | 5 +
src/bin/Makefile.am | 2 +-
src/bin/auth/auth_srv.cc | 5 +-
src/bin/auth/b10-auth.xml.pre | 14 +-
src/bin/auth/datasrc_clients_mgr.h | 2 +-
src/bin/auth/statistics.cc.pre | 9 +-
src/bin/auth/statistics.h | 18 +
src/bin/auth/statistics_msg_items.def | 1 +
src/bin/auth/tests/statistics_unittest.cc.pre | 58 +
src/bin/bind10/init.py.in | 99 +-
src/bin/bind10/run_bind10.sh.in | 2 +-
src/bin/bind10/tests/init_test.py.in | 55 +-
src/bin/cfgmgr/plugins/tests/datasrc_test.py | 9 -
src/bin/cmdctl/cmdctl.py.in | 1 -
src/bin/d2/.gitignore | 4 +-
src/bin/d2/Makefile.am | 10 +-
src/bin/d2/d2_cfg_mgr.cc | 132 +++
src/bin/d2/d2_cfg_mgr.h | 175 +++
src/bin/d2/d2_config.cc | 605 ++++++++++
src/bin/d2/d2_config.h | 941 +++++++++++++++
src/bin/d2/d2_controller.h | 2 +-
src/bin/d2/d2_messages.mes | 35 +-
src/bin/d2/d2_process.cc | 15 +-
src/bin/d2/d2_process.h | 4 +-
src/bin/d2/d2_update_message.cc | 221 ++++
src/bin/d2/d2_update_message.h | 337 ++++++
src/bin/d2/d2_zone.cc | 36 +
src/bin/d2/d2_zone.h | 117 ++
src/bin/d2/d_cfg_mgr.cc | 240 ++++
src/bin/d2/d_cfg_mgr.h | 331 ++++++
src/bin/d2/d_controller.cc | 2 +-
src/bin/d2/d_controller.h | 8 +-
src/bin/d2/d_process.h | 24 +-
src/bin/d2/dhcp-ddns.spec | 210 +++-
src/bin/d2/ncr_msg.cc | 485 ++++++++
src/bin/d2/ncr_msg.h | 503 ++++++++
src/bin/d2/tests/Makefile.am | 15 +
src/bin/d2/tests/d2_cfg_mgr_unittests.cc | 1238 ++++++++++++++++++++
src/bin/d2/tests/d2_controller_unittests.cc | 28 +-
src/bin/d2/tests/d2_process_unittests.cc | 17 +-
src/bin/d2/tests/d2_update_message_unittests.cc | 591 ++++++++++
src/bin/d2/tests/d2_zone_unittests.cc | 75 ++
src/bin/d2/tests/d_cfg_mgr_unittests.cc | 386 ++++++
src/bin/d2/tests/d_test_stubs.cc | 126 +-
src/bin/d2/tests/d_test_stubs.h | 215 +++-
src/bin/d2/tests/ncr_unittests.cc | 428 +++++++
src/bin/d2/tests/test_data_files_config.h.in | 17 +
src/bin/dhcp4/dhcp4_srv.cc | 3 +-
src/bin/dhcp6/dhcp6_srv.cc | 3 +-
src/bin/memmgr/.gitignore | 4 +
src/bin/memmgr/Makefile.am | 62 +
src/bin/memmgr/b10-memmgr.xml | 109 ++
src/bin/memmgr/memmgr.py.in | 214 ++++
.../dhcp-ddns.spec => memmgr/memmgr.spec.pre.in} | 10 +-
src/bin/memmgr/memmgr_messages.mes | 51 +
.../server_common => bin/memmgr}/tests/Makefile.am | 24 +-
src/bin/memmgr/tests/memmgr_test.py | 220 ++++
src/bin/msgq/msgq.py.in | 113 +-
src/bin/msgq/msgq.spec | 14 +-
src/bin/msgq/tests/msgq_run_test.py | 56 +
src/bin/msgq/tests/msgq_test.py | 187 ++-
src/bin/resolver/resolver.cc | 3 +-
src/bin/zonemgr/zonemgr.py.in | 1 +
src/lib/asiodns/io_fetch.cc | 3 +-
src/lib/cc/data.cc | 16 +-
src/lib/cc/tests/data_unittests.cc | 19 +
src/lib/config/config_data.h | 2 +-
src/lib/datasrc/client_list.cc | 38 +-
src/lib/datasrc/client_list.h | 25 +-
src/lib/datasrc/datasrc_messages.mes | 5 +
src/lib/datasrc/factory.cc | 4 +-
src/lib/datasrc/factory.h | 15 +-
src/lib/datasrc/memory/domaintree.h | 10 +-
src/lib/datasrc/memory/zone_table.cc | 9 +-
src/lib/datasrc/memory/zone_table.h | 25 +-
src/lib/datasrc/memory/zone_writer.cc | 9 +-
src/lib/datasrc/memory/zone_writer.h | 2 +-
src/lib/datasrc/tests/client_list_unittest.cc | 139 ++-
src/lib/datasrc/tests/database_unittest.cc | 3 +
src/lib/datasrc/tests/factory_unittest.cc | 2 +
src/lib/datasrc/tests/memory/testdata/Makefile.am | 1 +
.../datasrc/tests/memory/testdata/template.zone | 4 +
.../tests/memory/zone_data_loader_unittest.cc | 2 +
.../datasrc/tests/memory/zone_table_unittest.cc | 40 +-
.../datasrc/tests/memory/zone_writer_unittest.cc | 87 ++
src/lib/dhcp/option_custom.h | 1 +
src/lib/dhcp/option_definition.cc | 8 -
src/lib/dhcp/option_definition.h | 9 -
src/lib/dhcpsrv/dhcp_parsers.cc | 2 +-
src/lib/dns/master_lexer.h | 4 +-
src/lib/dns/message.h | 8 +-
src/lib/dns/python/pydnspp_common.cc | 3 +-
src/lib/dns/rdata.cc | 3 +
src/lib/dns/rdata/any_255/tsig_250.cc | 10 +-
src/lib/dns/rdata/generic/detail/ds_like.h | 12 +-
src/lib/dns/rdata/generic/dlv_32769.cc | 2 +-
src/lib/dns/rdata/generic/dnskey_48.cc | 2 +-
src/lib/dns/rdata/generic/ds_43.cc | 2 +-
src/lib/dns/rdata/generic/nsec3_50.cc | 2 +-
src/lib/dns/rdata/generic/nsec3param_51.cc | 2 +-
src/lib/dns/rdata/generic/nsec_47.cc | 2 +-
src/lib/dns/rdata/generic/rrsig_46.cc | 2 +-
src/lib/dns/rdata/generic/spf_99.cc | 2 +-
src/lib/dns/rdata/generic/sshfp_44.cc | 2 +-
src/lib/dns/rdata/generic/txt_16.cc | 2 +-
src/lib/dns/rdata/in_1/srv_33.cc | 2 +-
src/lib/dns/rrttl.cc | 6 +-
src/lib/dns/serial.h | 5 +-
src/lib/dns/tsigkey.cc | 2 +-
src/lib/log/logger_impl.h | 3 +-
src/lib/log/logger_manager.h | 4 +-
src/lib/log/message_exception.h | 4 +-
src/lib/log/message_reader.cc | 2 +-
src/lib/python/isc/Makefile.am | 2 +-
src/lib/python/isc/datasrc/Makefile.am | 4 +
.../isc/datasrc/configurableclientlist_inc.cc | 134 +++
.../isc/datasrc/configurableclientlist_python.cc | 221 ++--
src/lib/python/isc/datasrc/datasrc.cc | 7 +
src/lib/python/isc/datasrc/iterator_python.cc | 2 +-
src/lib/python/isc/datasrc/tests/Makefile.am | 18 +-
.../python/isc/datasrc/tests/clientlist_test.py | 262 ++++-
.../python/isc/datasrc/tests/testdata/Makefile.am | 12 +
.../datasrc/tests/testdata/example.com-broken.zone | 6 +
.../isc/datasrc/zonetable_accessor_python.cc | 6 +-
.../isc/datasrc/zonetable_iterator_python.cc | 2 +-
src/lib/python/isc/datasrc/zonewriter_inc.cc | 103 ++
.../{iterator_python.cc => zonewriter_python.cc} | 197 ++--
src/lib/python/isc/datasrc/zonewriter_python.h | 50 +
src/lib/python/isc/ddns/libddns_messages.mes | 10 +
src/lib/python/isc/ddns/session.py | 23 +
src/lib/python/isc/ddns/tests/session_tests.py | 10 +
src/lib/python/isc/log_messages/Makefile.am | 2 +
src/lib/python/isc/log_messages/memmgr_messages.py | 1 +
src/lib/python/isc/memmgr/Makefile.am | 10 +
src/lib/python/isc/{bind10 => memmgr}/__init__.py | 0
src/lib/python/isc/memmgr/builder.py | 99 ++
src/lib/python/isc/memmgr/datasrc_info.py | 220 ++++
src/lib/python/isc/memmgr/tests/Makefile.am | 34 +
src/lib/python/isc/memmgr/tests/builder_tests.py | 121 ++
.../python/isc/memmgr/tests/datasrc_info_tests.py | 192 +++
src/lib/python/isc/server_common/Makefile.am | 4 +-
.../python/isc/server_common/bind10_server.py.in | 275 +++++
.../isc/server_common/server_common_messages.mes | 20 +
src/lib/python/isc/server_common/tests/.gitignore | 2 +
src/lib/python/isc/server_common/tests/Makefile.am | 2 +
.../isc/server_common/tests/bind10_server_test.py | 294 +++++
src/lib/resolve/recursive_query.cc | 5 -
src/lib/server_common/portconfig.cc | 3 +
src/lib/statistics/counter.h | 4 +-
src/lib/testutils/mockups.h | 2 +
src/lib/util/Makefile.am | 4 +
src/lib/util/buffer.h | 18 +-
src/lib/util/hooks/callout_handle.cc | 121 ++
src/lib/util/hooks/callout_handle.h | 353 ++++++
src/lib/util/hooks/callout_manager.cc | 182 +++
src/lib/util/hooks/callout_manager.h | 301 +++++
src/lib/util/hooks/library_handle.cc | 39 +
src/lib/util/hooks/library_handle.h | 115 ++
src/lib/util/hooks/server_hooks.cc | 125 ++
src/lib/util/hooks/server_hooks.h | 207 ++++
src/lib/util/memory_segment_mapped.cc | 2 +
src/lib/util/python/.gitignore | 1 +
src/lib/util/python/Makefile.am | 2 +-
src/lib/util/python/doxygen2pydoc.py.in | 680 +++++++++++
src/lib/util/random/qid_gen.h | 2 +
src/lib/util/tests/Makefile.am | 4 +
src/lib/util/tests/buffer_unittest.cc | 5 +
src/lib/util/tests/callout_handle_unittest.cc | 329 ++++++
src/lib/util/tests/callout_manager_unittest.cc | 765 ++++++++++++
src/lib/util/tests/handles_unittest.cc | 917 +++++++++++++++
.../util/tests/memory_segment_mapped_unittest.cc | 6 +-
src/lib/util/tests/server_hooks_unittest.cc | 242 ++++
src/lib/util/unittests/mock_socketsession.h | 7 +-
.../configurations/example.org.inmem.config | 6 +
tests/lettuce/features/auth_badzone.feature | 2 +-
tests/lettuce/features/ddns_system.feature | 36 +
tests/lettuce/features/example.feature | 2 +-
tests/lettuce/features/nsec3_auth.feature | 28 +-
tests/lettuce/features/queries.feature | 17 +-
tests/lettuce/features/resolver_basic.feature | 6 +-
tests/lettuce/features/terrain/querying.py | 12 +-
tests/tools/perfdhcp/stats_mgr.h | 13 +-
200 files changed, 17642 insertions(+), 566 deletions(-)
create mode 100644 doc/Doxyfile-xml
create mode 100644 doc/design/Makefile.am
create mode 100644 doc/design/datasrc/.gitignore
create mode 100644 doc/design/datasrc/Makefile.am
create mode 100644 doc/design/datasrc/auth-local.txt
create mode 100644 doc/design/datasrc/auth-mapped.txt
create mode 100644 doc/design/datasrc/data-source-classes.txt
create mode 100644 doc/design/datasrc/memmgr-mapped-init.txt
create mode 100644 doc/design/datasrc/memmgr-mapped-reload.txt
create mode 100644 doc/design/datasrc/overview.txt
create mode 100644 doc/design/resolver/01-scaling-across-cores
create mode 100644 doc/design/resolver/02-mixed-recursive-authority-setup
create mode 100644 doc/design/resolver/03-cache-algorithm
create mode 100644 doc/design/resolver/03-cache-algorithm.txt
create mode 100644 doc/design/resolver/README
create mode 100644 src/bin/d2/d2_cfg_mgr.cc
create mode 100644 src/bin/d2/d2_cfg_mgr.h
create mode 100644 src/bin/d2/d2_config.cc
create mode 100644 src/bin/d2/d2_config.h
create mode 100644 src/bin/d2/d2_update_message.cc
create mode 100644 src/bin/d2/d2_update_message.h
create mode 100644 src/bin/d2/d2_zone.cc
create mode 100644 src/bin/d2/d2_zone.h
create mode 100644 src/bin/d2/d_cfg_mgr.cc
create mode 100644 src/bin/d2/d_cfg_mgr.h
create mode 100644 src/bin/d2/ncr_msg.cc
create mode 100644 src/bin/d2/ncr_msg.h
create mode 100644 src/bin/d2/tests/d2_cfg_mgr_unittests.cc
create mode 100644 src/bin/d2/tests/d2_update_message_unittests.cc
create mode 100644 src/bin/d2/tests/d2_zone_unittests.cc
create mode 100644 src/bin/d2/tests/d_cfg_mgr_unittests.cc
create mode 100644 src/bin/d2/tests/ncr_unittests.cc
create mode 100644 src/bin/d2/tests/test_data_files_config.h.in
create mode 100644 src/bin/memmgr/.gitignore
create mode 100644 src/bin/memmgr/Makefile.am
create mode 100644 src/bin/memmgr/b10-memmgr.xml
create mode 100755 src/bin/memmgr/memmgr.py.in
copy src/bin/{d2/dhcp-ddns.spec => memmgr/memmgr.spec.pre.in} (53%)
create mode 100644 src/bin/memmgr/memmgr_messages.mes
copy src/{lib/python/isc/server_common => bin/memmgr}/tests/Makefile.am (56%)
create mode 100755 src/bin/memmgr/tests/memmgr_test.py
create mode 100644 src/lib/datasrc/tests/memory/testdata/template.zone
create mode 100644 src/lib/python/isc/datasrc/configurableclientlist_inc.cc
create mode 100644 src/lib/python/isc/datasrc/tests/testdata/Makefile.am
create mode 100644 src/lib/python/isc/datasrc/tests/testdata/example.com-broken.zone
create mode 100644 src/lib/python/isc/datasrc/zonewriter_inc.cc
copy src/lib/python/isc/datasrc/{iterator_python.cc => zonewriter_python.cc} (57%)
create mode 100644 src/lib/python/isc/datasrc/zonewriter_python.h
create mode 100644 src/lib/python/isc/log_messages/memmgr_messages.py
create mode 100644 src/lib/python/isc/memmgr/Makefile.am
copy src/lib/python/isc/{bind10 => memmgr}/__init__.py (100%)
create mode 100644 src/lib/python/isc/memmgr/builder.py
create mode 100644 src/lib/python/isc/memmgr/datasrc_info.py
create mode 100644 src/lib/python/isc/memmgr/tests/Makefile.am
create mode 100644 src/lib/python/isc/memmgr/tests/builder_tests.py
create mode 100644 src/lib/python/isc/memmgr/tests/datasrc_info_tests.py
create mode 100644 src/lib/python/isc/server_common/bind10_server.py.in
create mode 100644 src/lib/python/isc/server_common/tests/.gitignore
create mode 100755 src/lib/python/isc/server_common/tests/bind10_server_test.py
create mode 100644 src/lib/util/hooks/callout_handle.cc
create mode 100644 src/lib/util/hooks/callout_handle.h
create mode 100644 src/lib/util/hooks/callout_manager.cc
create mode 100644 src/lib/util/hooks/callout_manager.h
create mode 100644 src/lib/util/hooks/library_handle.cc
create mode 100644 src/lib/util/hooks/library_handle.h
create mode 100644 src/lib/util/hooks/server_hooks.cc
create mode 100644 src/lib/util/hooks/server_hooks.h
create mode 100755 src/lib/util/python/doxygen2pydoc.py.in
create mode 100644 src/lib/util/tests/callout_handle_unittest.cc
create mode 100644 src/lib/util/tests/callout_manager_unittest.cc
create mode 100644 src/lib/util/tests/handles_unittest.cc
create mode 100644 src/lib/util/tests/server_hooks_unittest.cc
-----------------------------------------------------------------------
diff --git a/ChangeLog b/ChangeLog
index fe779ea..7b3bdcd 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,9 +1,67 @@
-627 [func] tmark
- Logger name for DHCP-DDNS has been changed from "d2_logger" to "dhcpddns".
- In addition, its log messages now use two suffixes, DCTL_ for logs the
- emanate from the underlying base classes, and DHCP_DDNS_ for logs which
- emanate from DHCP-DDNS specific code
- (trac #2978 git 5aec5fb20b0486574226f89bd877267cb9116921)
+637. [func] [tmark]
+ Added initial implementation of NameChangeRequest,
+ which embodies DNS update requests sent to DHCP-DDNS
+ by its clients.
+ (trac3007 git f33bdd59c6a8c8ea883f11578b463277d01c2b70)
+
+636. [func] [tmark]
+ Added the initial implementation of configuration parsing for
+ DCHP-DDNS.
+ (Trac #2957, git c04fb71fa44c2a458aac57ae54eeb1711c017a49)
+
+635. [func] marcin
+ b10-dhcp-ddns: Implemented DNS Update message construction.
+ (Trac #2796, git eac5e751473e238dee1ebf16491634a1fbea25e2)
+
+634. [bug] muks
+ When processing DDNS updates, we now check the zone more
+ thoroughly with the received zone data updates to check if it is
+ valid. If the zone fails validation, we reply with SERVFAIL
+ rcode. So, while previously we may have allowed more zone data
+ cases without checking which resulted in invalid zones, such
+ update requests are now rejected.
+ (Trac #2759, git d8991bf8ed720a316f7506c1dd9db7de5c57ad4d)
+
+633. [func] jinmei
+ b10-memmgr: a new BIND 10 module that manages shared memory
+ segments for DNS zone data. At this point it's runnable but does
+ nothing really meaningful for end users; it was added to the
+ master branch for further development.
+ (Trac #2854, git d05d7aa36d0f8f87b94dba114134b50ca37eabff)
+
+632. [bug] marcin
+ perfdhcp: Fixed a bug in whereby the application was sporadically
+ crashing when timed out packets were garbage collected.
+ (Trac #2979, git 6d42b333f446eccc9d0204bcc04df38fed0c31db)
+
+631. [bug] muks
+ Applied a patch by Tomas Hozza to fix a couple of compile errors
+ on Fedora 19 development release.
+ (Trac #3001, git 6e42b90971b377261c72d51c38bf4a8dc336664a)
+
+630. [bug] muks
+ If there is a problem loading the backend module for a type of
+ data source, b10-auth would not serve any zones. This behaviour
+ has been changed now so that it serves zones from all other usable
+ data sources that were configured.
+ (Trac #2947, git 9a3ddf1e2bfa2546bfcc7df6d9b11bfbdb5cf35f)
+
+629. [func] stephen
+ Added first part of the hooks framework.
+ (Trac #2794, git d2b107586db7c2deaecba212c891d231d7e54a07)
+
+628. [func] y-aharen
+ b10-auth: A new statistics item 'qryrecursion' has been introduced.
+ The counter is for the number of queries (OpCode=Query) with Recursion
+ Desired (RD) bit on.
+ (Trac #2796, git 3d291f42cdb186682983aa833a1a67cb9e6a8434)
+
+627. [func] tmark
+ Logger name for DHCP-DDNS has been changed from "d2_logger" to
+ "dhcpddns". In addition, its log messages now use two suffixes,
+ DCTL_ for logs the emanate from the underlying base classes, and
+ DHCP_DDNS_ for logs which emanate from DHCP-DDNS specific code
+ (Trac #2978, git 5aec5fb20b0486574226f89bd877267cb9116921)
626. [func] tmark
Created the initial implementation of DHCP-DDNS service
diff --git a/Makefile.am b/Makefile.am
index 10ef321..392b985 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -110,7 +110,8 @@ report-coverage: report-cpp-coverage report-python-coverage
# for static C++ check using cppcheck (when available)
cppcheck:
- cppcheck --enable=all --suppressions src/cppcheck-suppress.lst --inline-suppr \
+ cppcheck -I./src/lib -I./src/bin --enable=all --suppressions \
+ src/cppcheck-suppress.lst --inline-suppr \
--quiet --error-exitcode=1 \
--template '{file}:{line}: check_fail: {message} ({severity},{id})' \
src
diff --git a/configure.ac b/configure.ac
index fc216c4..6841a52 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1132,6 +1132,14 @@ AC_ARG_ENABLE(logger-checks, [AC_HELP_STRING([--enable-logger-checks],
AM_CONDITIONAL(ENABLE_LOGGER_CHECKS, test x$enable_logger_checks != xno)
AM_COND_IF([ENABLE_LOGGER_CHECKS], [AC_DEFINE([ENABLE_LOGGER_CHECKS], [1], [Check logger messages?])])
+# Check for asciidoc
+AC_PATH_PROG(ASCIIDOC, asciidoc, no)
+AM_CONDITIONAL(HAVE_ASCIIDOC, test "x$ASCIIDOC" != "xno")
+
+# Check for plantuml
+AC_PATH_PROG(PLANTUML, plantuml, no)
+AM_CONDITIONAL(HAVE_PLANTUML, test "x$PLANTUML" != "xno")
+
# Check for valgrind
AC_PATH_PROG(VALGRIND, valgrind, no)
AM_CONDITIONAL(HAVE_VALGRIND, test "x$VALGRIND" != "xno")
@@ -1171,6 +1179,8 @@ AM_COND_IF([HAVE_OPTRESET], [AC_DEFINE([HAVE_OPTRESET], [1], [Check for optreset
AC_CONFIG_FILES([Makefile
doc/Makefile
doc/guide/Makefile
+ doc/design/Makefile
+ doc/design/datasrc/Makefile
compatcheck/Makefile
src/Makefile
src/bin/Makefile
@@ -1192,6 +1202,8 @@ AC_CONFIG_FILES([Makefile
src/bin/loadzone/Makefile
src/bin/loadzone/tests/Makefile
src/bin/loadzone/tests/correct/Makefile
+ src/bin/memmgr/Makefile
+ src/bin/memmgr/tests/Makefile
src/bin/msgq/Makefile
src/bin/msgq/tests/Makefile
src/bin/auth/Makefile
@@ -1245,6 +1257,7 @@ AC_CONFIG_FILES([Makefile
src/lib/python/isc/util/cio/tests/Makefile
src/lib/python/isc/datasrc/Makefile
src/lib/python/isc/datasrc/tests/Makefile
+ src/lib/python/isc/datasrc/tests/testdata/Makefile
src/lib/python/isc/dns/Makefile
src/lib/python/isc/cc/Makefile
src/lib/python/isc/cc/cc_generated/Makefile
@@ -1264,6 +1277,8 @@ AC_CONFIG_FILES([Makefile
src/lib/python/isc/bind10/tests/Makefile
src/lib/python/isc/ddns/Makefile
src/lib/python/isc/ddns/tests/Makefile
+ src/lib/python/isc/memmgr/Makefile
+ src/lib/python/isc/memmgr/tests/Makefile
src/lib/python/isc/xfrin/Makefile
src/lib/python/isc/xfrin/tests/Makefile
src/lib/python/isc/server_common/Makefile
@@ -1377,6 +1392,8 @@ AC_OUTPUT([doc/version.ent
src/bin/loadzone/loadzone.py
src/bin/usermgr/run_b10-cmdctl-usermgr.sh
src/bin/usermgr/b10-cmdctl-usermgr.py
+ src/bin/memmgr/memmgr.py
+ src/bin/memmgr/memmgr.spec.pre
src/bin/msgq/msgq.py
src/bin/msgq/run_msgq.sh
src/bin/auth/auth.spec.pre
@@ -1388,6 +1405,7 @@ AC_OUTPUT([doc/version.ent
src/bin/dhcp4/spec_config.h.pre
src/bin/dhcp6/spec_config.h.pre
src/bin/d2/spec_config.h.pre
+ src/bin/d2/tests/test_data_files_config.h
src/bin/tests/process_rename_test.py
src/lib/config/tests/data_def_unittests_config.h
src/lib/python/isc/config/tests/config_test
@@ -1395,6 +1413,7 @@ AC_OUTPUT([doc/version.ent
src/lib/python/isc/notify/tests/notify_out_test
src/lib/python/isc/log/tests/log_console.py
src/lib/python/isc/log_messages/work/__init__.py
+ src/lib/python/isc/server_common/bind10_server.py
src/lib/dns/gen-rdatacode.py
src/lib/python/bind10_config.py
src/lib/cc/session_config.h.pre
@@ -1408,6 +1427,7 @@ AC_OUTPUT([doc/version.ent
src/lib/log/tests/logger_lock_test.sh
src/lib/log/tests/severity_test.sh
src/lib/log/tests/tempdir.h
+ src/lib/util/python/doxygen2pydoc.py
src/lib/util/python/mkpywrapper.py
src/lib/util/python/gen_wiredata.py
src/lib/server_common/tests/data_path.h
@@ -1439,6 +1459,7 @@ AC_OUTPUT([doc/version.ent
chmod +x src/lib/log/tests/local_file_test.sh
chmod +x src/lib/log/tests/logger_lock_test.sh
chmod +x src/lib/log/tests/severity_test.sh
+ chmod +x src/lib/util/python/doxygen2pydoc.py
chmod +x src/lib/util/python/mkpywrapper.py
chmod +x src/lib/util/python/gen_wiredata.py
chmod +x src/lib/python/isc/log/tests/log_console.py
diff --git a/doc/Doxyfile-xml b/doc/Doxyfile-xml
new file mode 100644
index 0000000..ae5be8a
--- /dev/null
+++ b/doc/Doxyfile-xml
@@ -0,0 +1,7 @@
+# This is a doxygen configuration for generating XML output as well as HTML.
+#
+# Inherit everything from our default Doxyfile except GENERATE_XML, which
+# will be reset to YES
+
+ at INCLUDE = Doxyfile
+GENERATE_XML = YES
diff --git a/doc/Makefile.am b/doc/Makefile.am
index 3120280..4af8188 100644
--- a/doc/Makefile.am
+++ b/doc/Makefile.am
@@ -1,6 +1,6 @@
-SUBDIRS = guide
+SUBDIRS = guide design
-EXTRA_DIST = version.ent.in differences.txt
+EXTRA_DIST = version.ent.in differences.txt Doxyfile Doxyfile-xml
devel:
mkdir -p html
diff --git a/doc/design/Makefile.am b/doc/design/Makefile.am
new file mode 100644
index 0000000..e0c888e
--- /dev/null
+++ b/doc/design/Makefile.am
@@ -0,0 +1 @@
+SUBDIRS = datasrc
diff --git a/doc/design/datasrc/.gitignore b/doc/design/datasrc/.gitignore
new file mode 100644
index 0000000..065b83e
--- /dev/null
+++ b/doc/design/datasrc/.gitignore
@@ -0,0 +1,3 @@
+/*.html
+/*.png
+/*.xml
diff --git a/doc/design/datasrc/Makefile.am b/doc/design/datasrc/Makefile.am
new file mode 100644
index 0000000..d339cd7
--- /dev/null
+++ b/doc/design/datasrc/Makefile.am
@@ -0,0 +1,30 @@
+UML_FILES = \
+ overview.txt \
+ auth-local.txt \
+ auth-mapped.txt \
+ memmgr-mapped-init.txt \
+ memmgr-mapped-reload.txt
+
+TEXT_FILES = \
+ data-source-classes.txt
+
+devel: $(patsubst %.txt, %.png, $(UML_FILES)) $(patsubst %.txt, %.html, $(TEXT_FILES))
+
+.txt.html:
+if HAVE_ASCIIDOC
+ $(AM_V_GEN) $(ASCIIDOC) -n $<
+else
+ @echo "*** asciidoc is required to regenerate $(@) ***"; exit 1;
+endif
+
+.txt.png:
+if HAVE_PLANTUML
+ $(AM_V_GEN) $(PLANTUML) $<
+else
+ @echo "*** plantuml is required to regenerate $(@) ***"; exit 1;
+endif
+
+CLEANFILES = \
+ $(patsubst %.txt, %.png, $(UML_FILES)) \
+ $(patsubst %.txt, %.html, $(TEXT_FILES)) \
+ $(patsubst %.txt, %.xml, $(TEXT_FILES))
diff --git a/doc/design/datasrc/auth-local.txt b/doc/design/datasrc/auth-local.txt
new file mode 100644
index 0000000..79d7399
--- /dev/null
+++ b/doc/design/datasrc/auth-local.txt
@@ -0,0 +1,137 @@
+ at startuml
+
+participant auth as "b10-auth"
+[-> auth: new/initial config\n(datasrc cfg)
+activate auth
+
+participant list as "Configurable\nClientList"
+create list
+auth -> list: <<construct>>
+
+auth -> list: configure(cfg)
+activate list
+
+create CacheConfig
+list -> CacheConfig: <<construct>> (cfg)
+
+participant zt_segment as "ZoneTable\nSegment\n(Local)"
+create zt_segment
+list -> zt_segment: <<construct>>
+activate zt_segment
+
+create ZoneTable
+zt_segment -> ZoneTable: <<construct>>
+
+deactivate zt_segment
+
+list -> zt_segment: isWritable()
+activate zt_segment
+note over zt_segment: Local segments are\nalways writable
+zt_segment --> list: true
+deactivate zt_segment
+
+loop for each zone in CacheConfig
+list -> CacheConfig: getLoadAction()
+activate CacheConfig
+
+create LoadAction
+CacheConfig -> LoadAction: <<construct>>
+
+participant LoadAction.2
+
+CacheConfig --> list : LoadAction
+
+deactivate CacheConfig
+
+create ZoneWriter
+list -> ZoneWriter: <<construct>> (load_action)
+
+participant ZoneWriter.2
+
+list -> ZoneWriter: load()
+activate ZoneWriter
+ZoneWriter -> LoadAction: (funcall)
+activate LoadAction
+
+create ZoneData
+LoadAction -> ZoneData: <<construct>> via helpers
+
+participant ZoneData.2
+
+LoadAction --> ZoneWriter: ZoneData
+deactivate LoadAction
+deactivate ZoneWriter
+
+list -> ZoneWriter: install()
+activate ZoneWriter
+
+ZoneWriter -> ZoneTable: addZone(ZoneData)
+activate ZoneTable
+ZoneTable --> ZoneWriter: NULL (no old data)
+deactivate ZoneTable
+
+deactivate ZoneWriter
+
+end
+
+deactivate list
+deactivate auth
+
+...
+
+[-> auth: reload\n(zonename)
+activate auth
+
+auth -> list: getCachedZoneWriter\n(zone_name)
+activate list
+
+list -> CacheConfig: getLoadAction()
+activate CacheConfig
+
+create LoadAction.2
+CacheConfig -> LoadAction.2: <<construct>>
+
+CacheConfig --> list : LoadAction.2
+
+deactivate CacheConfig
+
+create ZoneWriter.2
+list -> ZoneWriter.2: <<construct>> (load_action)
+
+list --> auth: ZoneWriter.2
+
+deactivate list
+
+
+auth -> ZoneWriter.2: load()
+activate ZoneWriter.2
+ZoneWriter.2 -> LoadAction.2: (funcall)
+activate LoadAction.2
+
+create ZoneData.2
+LoadAction.2 -> ZoneData.2: <<construct>> via helpers
+
+LoadAction.2 --> ZoneWriter.2: ZoneData.2
+deactivate LoadAction.2
+deactivate ZoneWriter.2
+
+auth -> ZoneWriter.2: install()
+activate ZoneWriter.2
+
+ZoneWriter.2 -> ZoneTable: addZone(ZoneData.2)
+activate ZoneTable
+ZoneTable --> ZoneWriter.2: ZoneData (old data)
+deactivate ZoneTable
+
+deactivate ZoneWriter.2
+
+auth -> ZoneWriter.2: cleanup()
+activate ZoneWriter.2
+
+ZoneWriter.2 -> ZoneData: <<destroy>>
+destroy ZoneData
+deactivate ZoneWriter.2
+
+deactivate auth
+
+ at enduml
diff --git a/doc/design/datasrc/auth-mapped.txt b/doc/design/datasrc/auth-mapped.txt
new file mode 100644
index 0000000..d656220
--- /dev/null
+++ b/doc/design/datasrc/auth-mapped.txt
@@ -0,0 +1,98 @@
+ at startuml
+
+participant auth as "b10-auth"
+[-> auth: new/initial config\n(datasrc cfg)
+activate auth
+
+participant list as "Configurable\nClientList"
+create list
+auth -> list: <<construct>>
+
+auth -> list: configure(cfg)
+activate list
+
+create CacheConfig
+list -> CacheConfig: <<construct>> (cfg)
+
+participant zt_segment as "ZoneTable\nSegment\n(Mapped)"
+create zt_segment
+list -> zt_segment: <<construct>>
+
+list -> zt_segment: isWritable()
+activate zt_segment
+note over zt_segment: Segment not writable\nwhen not reset
+zt_segment --> list: false
+deactivate zt_segment
+
+deactivate list
+
+auth -> list: getStatus()
+activate list
+list --> auth: DataSourceStatus[]
+deactivate list
+
+[<- auth: subscribe to\nmemmgr group
+
+deactivate auth
+
+...
+
+[-> auth: command from\nmemmgr\n(datasrc_name,\nsegmentparam)
+activate auth
+
+auth -> list: resetMemorySegment\n(datasrc_name,\nREAD_ONLY,\nsegmentparam)
+activate list
+
+list -> zt_segment: reset\n(READ_ONLY,\nsegmentparam)
+activate zt_segment
+
+participant segment as "Memory\nSegment\n(Mapped)"
+create segment
+zt_segment -> segment: <<construct>>
+
+deactivate zt_segment
+deactivate list
+deactivate auth
+
+...
+
+[-> auth: command from\nmemmgr\n(datasrc_name,\nsegmentparam)
+activate auth
+
+auth -> list: resetMemorySegment\n(datasrc_name,\nREAD_ONLY,\nsegmentparam)
+activate list
+
+list -> zt_segment: reset\n(READ_ONLY,\nsegmentparam)
+activate zt_segment
+
+zt_segment -> segment: <<destroy>>
+destroy segment
+
+participant segment.2 as "Memory\nSegment\n(Mapped)\n2"
+create segment.2
+zt_segment -> segment.2: <<construct>>
+
+deactivate zt_segment
+deactivate list
+deactivate auth
+
+...
+
+[-> auth: reload\n(zonename)
+activate auth
+
+auth -> list: getCachedZoneWriter\n(zone_name)
+activate list
+
+list -> zt_segment: isWritable()
+activate zt_segment
+note over zt_segment: Segment not writable\nas it is READ_ONLY
+zt_segment --> list: false
+deactivate zt_segment
+
+list --> auth: CACHE_NOT_WRITABLE
+deactivate list
+
+deactivate auth
+
+ at enduml
diff --git a/doc/design/datasrc/data-source-classes.txt b/doc/design/datasrc/data-source-classes.txt
new file mode 100644
index 0000000..ac7e5a9
--- /dev/null
+++ b/doc/design/datasrc/data-source-classes.txt
@@ -0,0 +1,364 @@
+Data Source Library Classes
+===========================
+
+About this document
+-------------------
+
+This memo describes major classes used in the data source library,
+mainly focusing on handling in-memory cache with consideration of the
+shared memory support. It will give an overview of the entire design
+architecture and some specific details of how these classes are expected
+to be used.
+
+Before reading, the higher level inter-module protocol should be understood:
+http://bind10.isc.org/wiki/SharedMemoryIPC
+
+Overall relationships between classes
+-------------------------------------
+
+The following diagram shows major classes in the data source library
+related to in-memory caches and their relationship.
+
+image::overview.png[Class diagram showing overview of relationships]
+
+Major design decisions of this architecture are:
+
+* Keep each class as concise as possible, each focusing on one or
+ small set of responsibilities. Smaller classes are generally easier
+ to understand (at the cost of understanding how they work in the
+ "big picture" of course) and easier to test.
+
+* On a related point, minimize dependency to any single class. A
+ monolithic class on which many others are dependent is generally
+ difficult to maintain because you'll need to ensure a change to the
+ monolithic class doesn't break anything on any other classes.
+
+* Use polymorphism for any "fluid" behavior, and hide specific details
+ under abstract interfaces so implementation details won't be
+ directly referenced from any other part of the library.
+ Specifically, the underlying memory segment type (local, mapped, and
+ possibly others) and the source of in-memory data (master file or
+ other data source) are hidden via a kind of polymorphism.
+
+* Separate classes directly used by applications from classes that
+ implement details. Make the former classes as generic as possible,
+ agnostic about implementation specific details such as the memory
+ segment type (or, ideally and where possible, whether it's for
+ in-memory cache or the underlying data source).
+
+The following give a summarized description of these classes.
+
+* `ConfigurableClientList`: The front end to application classes. An
+ application that uses the data source library generally maintains
+ one or more `ConfigurableClientList` object (usually one per RR
+ class, or when we support views, probably one per view). This class
+ is a container of sets of data source related classes, providing
+ accessor to these classes and also acting as a factory of other
+ related class objects. Note: Due to internal implementation
+ reasons, there is a base class for `ConfigurableClientList` named
+ `ClientList` in the C++ version, and applications are expected to
+ use the latter. But conceptually `ConfigurableClientList` is an
+ independent value class; the inheritance is not for polymorphism.
+ Note also that the Python version doesn't have the base class.
+
+* `DataSourceInfo`: this is a straightforward tuple of set of class
+ objects corresponding to a single data source, including
+ `DataSourceClient`, `CacheConfig`, and `ZoneTableSegment`.
+ `ConfigurableClientList` maintains a list of `DataSourceInfo`, one
+ for each data source specified in its configuration.
+
+* `DataSourceClient`: The front end class to applications for a single
+ data source. Applications will get a specific `DataSourceClient`
+ object by `ConfigurableClientList::find()`.
+ `DataSourceClient` itself is a set of factories for various
+ operations on the data source such as lookup or update.
+
+* `CacheConfig`: library internal representation of in-memory cache
+ configuration for a data source. It knows which zones are to be
+ cached and where the zone data (RRs) should come from, either from a
+ master file or other data source. With this knowledge it will
+ create an appropriate `LoadAction` object. Note that `CacheConfig`
+ isn't aware of the underlying memory segment type for the in-memory
+ data. It's intentionally separated from this class (see the
+ conciseness and minimal-dependency design decisions above).
+
+* `ZoneTableSegment`: when in-memory cache is enabled, it provides
+ memory-segment-type independent interface to the in-memory data.
+ This is an abstract base class (see polymorphism in the design
+ decisions) and inherited by segment-type specific subclasses:
+ `ZoneTableSegmentLocal` and `ZoneTableSegmentMapped` (and possibly
+ others). Any subclass of `ZoneTableSegment` is expected to maintain
+ the specific type of `MemorySegment` object.
+
+* `ZoneWriter`: a frontend utility class for applications to update
+ in-memory zone data (currently it can only load a whole zone and
+ replace any existing zone content with a new one, but this should be
+ extended so it can handle partial updates).
+ Applications will get a specific `ZoneWriter`
+ object by `ConfigurableClientList::getCachedZoneWriter()`.
+ `ZoneWriter` is constructed with `ZoneableSegment` and `LoadAction`.
+ Since these are abstract classes, `ZoneWriter` doesn't have to be
+ aware of "fluid" details. It's only responsible for "somehow" preparing
+ `ZoneData` for a new version of a specified zone using `LoadAction`,
+ and installing it in the `ZoneTable` (which can be accessed via
+ `ZoneTableSegment`).
+
+* `DataSourceStatus`: created by `ConfigurableClientList::getStatus()`,
+ a straightforward tuple that represents some status information of a
+ specific data source managed in the `ConfigurableClientList`.
+ `getStatus()` generates `DataSourceStatus` for all data sources
+ managed in it, and returns them as a vector.
+
+* `ZoneTableAccessor`, `ZoneTableIterator`: frontend classes to get
+ access to the conceptual "zone table" (a set of zones) stored in a
+ specific data source. In particular, `ZoneTableIterator` allows
+ applications to iterate over all zones (by name) stored in the
+ specific data source.
+ Applications will get a specific `ZoneTableAccessor`
+ object by `ConfigurableClientList::getZoneTableAccessor()`,
+ and get an iterator object by calling `getIterator` on the accessor.
+ These are abstract classes and provide unified interfaces
+ independent from whether it's for in-memory cached zones or "real"
+ underlying data source. But the initial implementation only
+ provides the in-memory cache version of subclass (see the next
+ item).
+
+* `ZoneTableAccessorCache`, `ZoneTableIteratorCache`: implementation
+ classes of `ZoneTableAccessor` and `ZoneTableIterator` for in-memory
+ cache. They refer to `CacheConfig` to get a list of zones to be
+ cached.
+
+* `ZoneTableHeader`, `ZoneTable`: top-level interface to actual
+ in-memory data. These were separated based on a prior version of
+ the design (http://bind10.isc.org/wiki/ScalableZoneLoadDesign) where
+ `ZoneTableHeader` may contain multiple `ZoneTable`s. It's
+ one-to-one relationship in the latest version (of implementation),
+ so we could probably unify them as a cleanup.
+
+* `ZoneData`: representing the in-memory content of a single zone.
+ `ZoneTable` contains (zero, one or) multiple `ZoneData` objects.
+
+* `RdataSet`: representing the in-memory content of (data of) a single
+ RRset.
+ `ZoneData` contains `RdataSet`s corresponding to the RRsets stored
+ in the zone.
+
+* `LoadAction`: a "polymorphic" functor that implements loading zone
+ data into memory. It hides from its user (i.e., `ZoneWriter`)
+ details about the source of the data: master file or other data
+ source (and perhaps some others). The "polymorphism" is actually
+ realized as different implementations of the functor interface, not
+ class inheritance (but conceptually the effect and goal is the
+ same). Note: there's a proposal to replace `LoadAction` with
+ a revised `ZoneDataLoader`, although the overall concept doesn't
+ change. See Trac ticket #2912.
+
+* `ZoneDataLoader` and `ZoneDataUpdater`: helper classes for the
+ `LoadAction` functor(s). These work independently from the source
+ of data, taking a sequence of RRsets objects, converting them
+ into the in-memory data structures (`RdataSet`), and installing them
+ into a newly created `ZoneData` object.
+
+Sequence for auth module using local memory segment
+---------------------------------------------------
+
+In the remaining sections, we explain how the classes shown in the
+previous section work together through their methods for commonly
+intended operations.
+
+The following sequence diagram shows the case for the authoritative
+DNS server module to maintain "local" in-memory data. Note that
+"auth" is a conceptual "class" (not actually implemented as a C++
+class) to represent the server application behavior. For the purpose
+of this document that should be sufficient. The same note applies to
+all examples below.
+
+image::auth-local.png[Sequence diagram for auth server using local memory segment]
+
+1. On startup, the auth module creates a `ConfigurableClientList`
+ for each RR class specified in the configuration for "data_sources"
+ module. It then calls `ConfigurableClientList::configure()`
+ for the given configuration of that RR class.
+
+2. For each data source, `ConfigurableClientList` creates a
+ `CacheConfig` object with the corresponding cache related
+ configuration.
+
+3. If in-memory cache is enabled for the data source,
+ `ZoneTableSegment` is also created. In this scenario the cache
+ type is specified as "local" in the configuration, so a functor
+ creates `ZoneTableSegmentLocal` as the actual instance.
+ In this case its `ZoneTable` is immediately created, too.
+
+4. `ConfigurableClientList` checks if the created `ZoneTableSegment` is
+ writable. It is always so for "local" type of segments. So
+ `ConfigurableClientList` immediately loads zones to be cached into
+ memory. For each such zone, it first gets the appropriate
+ `LoadAction` through `CacheConfig`, then creates `ZoneWriter` with
+ the `LoadAction`, and loads the data using the writer.
+
+5. If the auth module receives a "reload" command for a cached zone
+ from other module (xfrin, an end user, etc), it calls
+ `ConfigurableClientList::getCachedZoneWriter` to load and install
+ the new version of the zone. The same loading sequence takes place
+ except that the user of the writer is the auth module.
+ Also, the old version of the zone data is destroyed at the end of
+ the process.
+
+Sequence for auth module using mapped memory segment
+----------------------------------------------------
+
+This is an example for the authoritative server module that uses
+mapped type memory segment for in-memory data.
+
+image::auth-mapped.png[Sequence diagram for auth server using mapped memory segment]
+
+1. The sequence is the same to the point of creating `CacheConfig`.
+
+2. But in this case a `ZoneTableSegmentMapped` object is created based
+ on the configuration of the cache type. This type of
+ `ZoneTableSegment` is initially empty and isn't even associated
+ with a `MemorySegment` (and therefore considered non-writable).
+
+3. `ConfigurableClientList` checks if the zone table segment is
+ writable to know whether to load zones into memory by itself,
+ but as `ZoneTableSegment::isWritable()` returns false, it skips
+ the loading.
+
+4. The auth module gets the status of each data source, and notices
+ there's a `WAITING` state of segment. So it subscribes to the
+ "Memmgr" group on a command session and waits for an update
+ from the memory manager (memmgr) module. (See also the note at the
+ end of the section)
+
+5. When the auth module receives an update command from memmgr, it
+ calls `ConfigurableClientList::resetMemorySegment()` with the command
+ argument and the segment mode of `READ_ONLY`.
+ Note that the auth module handles the command argument as mostly
+ opaque data; it's not expected to deal with details of segment
+ type-specific behavior.
+
+6. `ConfigurableClientList::resetMemorySegment()` subsequently calls
+ `reset()` method on the corresponding `ZoneTableSegment` with the
+ given parameters.
+ In the case of `ZoneTableSegmentMapped`, it creates a new
+ `MemorySegment` object for the mapped type, which internally maps
+ the specific file into memory.
+ memmgr is expected to have prepared all necessary data in the file,
+ so all the data are immediately ready for use (i.e., there
+ shouldn't be any explicit load operation).
+
+7. When a change is made in the mapped data, memmgr will send another
+ update command with parameters for new mapping. The auth module
+ calls `ConfigurableClientList::resetMemorySegment()`, and the
+ underlying memory segment is swapped with a new one. The old
+ memory segment object is destroyed. Note that
+ this "destroy" just means unmapping the memory region; the data
+ stored in the file are intact.
+
+8. If the auth module happens to receive a reload command from other
+ module, it could call
+ `ConfigurableClientList::getCachedZoneWriter()`
+ to reload the data by itself, just like in the previous section.
+ In this case, however, the writability check of
+ `getCachedZoneWriter()` fails (the segment was created as
+ `READ_ONLY` and is non-writable), so loading won't happen.
+
+NOTE: While less likely in practice, it's possible that the same auth
+module uses both "local" and "mapped" (and even others) type of
+segments for different data sources. In such cases the sequence is
+either the one in this or previous section depending on the specified
+segment type in the configuration. The auth module itself isn't aware
+of per segment-type details, but changes the behavior depending on the
+segment state of each data source at step 4 above: if it's `WAITING`,
+it means the auth module needs help from memmgr (that's all the auth
+module should know; it shouldn't be bothered with further details such
+as mapped file names); if it's something else, the auth module doesn't
+have to do anything further.
+
+Sequence for memmgr module initialization using mapped memory segment
+---------------------------------------------------------------------
+
+This sequence shows the common initialization sequence for the
+memory manager (memmgr) module using a mapped type memory segment.
+This is a mixture of the sequences shown in Sections 2 and 3.
+
+image::memmgr-mapped-init.png[]
+
+1. Initial sequence is the same until the application module (memmgr)
+ calls `ConfigurableClientList::getStatus()` as that for the
+ previous section.
+
+2. The memmgr module identifies the data sources whose in-memory cache
+ type is "mapped". (Unlike other application modules, the memmgr
+ should know what such types means due to its exact responsibility).
+ For each such data source, it calls
+ `ConfigurableClientList::resetMemorySegment` with the READ_WRITE
+ mode and other mapped-type specific parameters. memmgr should be
+ able to generate the parameters from its own configuration and
+ other data source specific information (such as the RR class and
+ data source name).
+
+3. The `ConfigurableClientList` class calls
+ `ZoneTableSegment::reset()` on the corresponding zone table
+ segment with the given parameters. In this case, since the mode is
+ READ_WRITE, a new `ZoneTable` will be created (assuming this is a
+ very first time initialization; if there's already a zone table
+ in the segment, it will be used).
+
+4. The memmgr module then calls
+ `ConfigurableClientList::getZoneTableAccessor()`, and calls the
+ `getItertor()` method on it to get a list of zones for which
+ zone data are to be loaded into the memory segment.
+
+5. The memmgr module loads the zone data for each such zone. This
+ sequence is the same as shown in Section 2.
+
+6. On loading all zone data, the memmgr module sends an update command
+ to all interested modules (such as auth) in the segment, and waits
+ for acknowledgment from all of them.
+
+7. Then it calls `ConfigurableClientList::resetMemorySegment()` for
+ this data source with almost the same parameter as step 2 above,
+ but with a different mapped file name. This will make a swap of
+ the underlying memory segment with a new mapping. The old
+ `MemorySegment` object will be destroyed, but as explained in the
+ previous section, it simply means unmapping the file.
+
+8. The memmgr loads the zone data into the newly mapped memory region
+ by repeating the sequence shown in step 5.
+
+9. The memmgr repeats all this sequence for data sources that use
+ "mapped" segment for in-memory cache. Note: it could handle
+ multiple data sources in parallel, e.g., while waiting for
+ acknowledgment from other modules.
+
+Sequence for memmgr module to reload a zone using mapped memory segment
+-----------------------------------------------------------------------
+
+This example is a continuation of the previous section, describing how
+the memory manager reloads a zone in mapped memory segment.
+
+image::memmgr-mapped-reload.png[]
+
+1. When the memmgr module receives a reload command from other module,
+ it calls `ConfigurableClientList::getCachedZoneWriter()` for the
+ specified zone name. This method checks the writability of
+ the segment, and since it's writable (as memmgr created it in the
+ READ_WRITE mode), `getCachedZoneWriter()` succeeds and returns
+ a `ZoneWriter`.
+
+2. The memmgr module uses the writer to load the new version of zone
+ data. There is nothing specific to mapped-type segment here.
+
+3. The memmgr module then sends an update command to other modules
+ that would share this version, and waits for acknowledgment from
+ all of them.
+
+4. On getting acknowledgments, the memmgr module calls
+ `ConfigurableClientList::resetMemorySegment()` with the parameter
+ specifying the other mapped file. This will swap the underlying
+ `MemorySegment` with a newly created one, mapping the other file.
+
+5. The memmgr updates this segment, too, so the two files will contain
+ the same version of data.
diff --git a/doc/design/datasrc/memmgr-mapped-init.txt b/doc/design/datasrc/memmgr-mapped-init.txt
new file mode 100644
index 0000000..52ca783
--- /dev/null
+++ b/doc/design/datasrc/memmgr-mapped-init.txt
@@ -0,0 +1,132 @@
+ at startuml
+
+participant memmgr as "memmgr"
+[-> memmgr: new/initial config\n(datasrc cfg)
+activate memmgr
+
+participant list as "Configurable\nClientList"
+create list
+memmgr -> list: <<construct>>
+
+memmgr -> list: configure(cfg)
+activate list
+
+create CacheConfig
+list -> CacheConfig: <<construct>> (cfg)
+
+participant zt_segment as "ZoneTable\nSegment\n(Mapped)"
+create zt_segment
+list -> zt_segment: <<construct>>
+
+list -> zt_segment: isWritable()
+activate zt_segment
+note over zt_segment: Segment not writable\nwhen not reset
+zt_segment --> list: false
+deactivate zt_segment
+
+deactivate list
+
+memmgr -> list: getStatus()
+activate list
+list --> memmgr: DataSourceStatus[]
+deactivate list
+
+loop for each datasrc with mapped segment
+
+memmgr -> list: resetMemorySegment\n(datasrc_name,\nREAD_WRITE,\nsegmentparam)
+activate list
+
+list -> zt_segment: reset\n(READ_WRITE,\nsegmentparam)
+activate zt_segment
+
+participant segment as "Memory\nSegment\n(Mapped)"
+create segment
+zt_segment -> segment: <<construct>>
+
+participant segment.2 as "Memory\nSegment\n(Mapped)\n2"
+
+create ZoneTable
+zt_segment -> ZoneTable: <<construct>>
+
+deactivate zt_segment
+deactivate list
+
+memmgr -> list: getZoneTableAccessor\n(datasrc_name,\ncache=true)
+activate list
+list -> memmgr: ZoneTableAccessor
+deactivate list
+
+
+loop for each zone given by ZoneTableIterator
+
+memmgr -> list: getCachedZoneWriter\n(zone_name)
+activate list
+
+list -> CacheConfig: getLoadAction()
+activate CacheConfig
+
+create LoadAction
+CacheConfig -> LoadAction: <<construct>>
+
+CacheConfig --> list : LoadAction
+
+deactivate CacheConfig
+
+create ZoneWriter
+list -> ZoneWriter: <<construct>> (load_action)
+
+list --> memmgr: ZoneWriter
+
+deactivate list
+
+
+memmgr -> ZoneWriter: load()
+activate ZoneWriter
+ZoneWriter -> LoadAction: (funcall)
+activate LoadAction
+
+create ZoneData
+LoadAction -> ZoneData: <<construct>> via helpers
+
+LoadAction --> ZoneWriter: ZoneData
+deactivate LoadAction
+deactivate ZoneWriter
+
+memmgr -> ZoneWriter: install()
+activate ZoneWriter
+
+ZoneWriter -> ZoneTable: addZone(ZoneData)
+activate ZoneTable
+ZoneTable --> ZoneWriter: NULL (no old data)
+deactivate ZoneTable
+
+deactivate ZoneWriter
+
+end
+
+[<- memmgr: command to\nmodules\n(datasrc_name,\nsegmentparam)
+[--> memmgr: ack from all\nmodules
+
+memmgr -> list: resetMemorySegment\n(datasrc_name,\nREAD_WRITE,\nsegmentparam)
+activate list
+
+list -> zt_segment: reset\n(READ_WRITE,\nsegmentparam)
+activate zt_segment
+
+zt_segment -> segment: <<destroy>>
+destroy segment
+create segment.2
+zt_segment -> segment.2: <<construct>>
+
+deactivate zt_segment
+deactivate list
+
+note left of memmgr: load zone\nfor each zone\ngiven by\nZoneTableIterator
+
+end
+
+[<-- memmgr
+
+deactivate memmgr
+
+ at enduml
diff --git a/doc/design/datasrc/memmgr-mapped-reload.txt b/doc/design/datasrc/memmgr-mapped-reload.txt
new file mode 100644
index 0000000..83dbf46
--- /dev/null
+++ b/doc/design/datasrc/memmgr-mapped-reload.txt
@@ -0,0 +1,92 @@
+ at startuml
+
+participant memmgr as "memmgr"
+[-> memmgr: reload\n(zonename)
+activate memmgr
+
+participant list as "Configurable\nClientList"
+memmgr -> list: getCachedZoneWriter\n(zone_name)
+activate list
+
+participant CacheConfig
+
+participant zt_segment as "ZoneTable\nSegment\n(Mapped)"
+participant segment as "Memory\nSegment\n(Mapped)"
+participant segment.2 as "Memory\nSegment\n(Mapped)\n2"
+
+list -> zt_segment: isWritable()
+activate zt_segment
+zt_segment --> list: true
+deactivate zt_segment
+
+list -> CacheConfig: getLoadAction()
+activate CacheConfig
+
+participant ZoneTable
+participant ZoneWriter
+
+create LoadAction
+CacheConfig -> LoadAction: <<construct>>
+CacheConfig --> list: LoadAction
+deactivate CacheConfig
+
+create ZoneWriter
+list -> ZoneWriter: <<construct>> (LoadAction)
+list --> memmgr: ZoneWriter
+deactivate list
+
+memmgr -> ZoneWriter: load()
+activate ZoneWriter
+ZoneWriter -> LoadAction: (funcall)
+activate LoadAction
+
+participant ZoneData
+
+create ZoneData.2
+LoadAction -> ZoneData.2: <<construct>> via helpers
+
+LoadAction --> ZoneWriter: ZoneData.2
+deactivate LoadAction
+deactivate ZoneWriter
+
+memmgr -> ZoneWriter: install()
+activate ZoneWriter
+
+ZoneWriter -> ZoneTable: addZone(ZoneData.2)
+activate ZoneTable
+ZoneTable --> ZoneWriter: ZoneData (old data)
+deactivate ZoneTable
+
+deactivate ZoneWriter
+
+memmgr -> ZoneWriter: cleanup()
+activate ZoneWriter
+
+ZoneWriter -> ZoneData: <<destroy>>
+destroy ZoneData
+deactivate ZoneWriter
+
+[<- memmgr: command to\nmodules\n(datasrc_name,\nsegmentparam)
+[--> memmgr: ack from all\nmodules
+
+memmgr -> list: resetMemorySegment\n(datasrc_name,\nREAD_WRITE,\nsegmentparam)
+activate list
+
+list -> zt_segment: reset\n(READ_WRITE,\nsegmentparam)
+activate zt_segment
+
+zt_segment -> segment: <<destroy>>
+destroy segment
+create segment.2
+zt_segment -> segment.2: <<construct>>
+
+deactivate zt_segment
+deactivate list
+
+note left of memmgr: (repeat the\nsame sequence\nfor loading to the\nother segment)
+
+memmgr -> list: getCachedZoneWriter\n(zone_name)
+
+...
+
+ at enduml
diff --git a/doc/design/datasrc/overview.txt b/doc/design/datasrc/overview.txt
new file mode 100644
index 0000000..49aae9d
--- /dev/null
+++ b/doc/design/datasrc/overview.txt
@@ -0,0 +1,68 @@
+ at startuml
+
+hide members
+
+note "Automatic placement of classes\ndoesn't look good. This diagram\nhas to be improved." as n1
+
+Auth "1" *-d-> "*" ConfigurableClientList
+Auth -d-> DataSourceClient
+Auth -d-> ZoneWriter
+Auth -d-> ZoneTableAccessor
+Auth -d-> DataSourceStatus
+Auth -d-> ZoneTableIterator
+
+ConfigurableClientList "1" *-d-> "*" DataSourceInfo
+ConfigurableClientList ..> ZoneTableSegment : <<reset>>
+ConfigurableClientList ..d-> DataSourceStatus : <<create>>
+ConfigurableClientList ..> ZoneWriter : <<create>>
+ConfigurableClientList ..> ZoneTableAccessor : <<create>>
+
+DataSourceInfo "1" *-u-> "*" DataSourceClient
+DataSourceInfo "1" *-r-> "*" CacheConfig
+DataSourceInfo "1" *-d-> "*" ZoneTableSegment
+
+ZoneTableAccessor ..> ZoneTableIterator : <<create>>
+
+ZoneTableAccessorCache -> CacheConfig
+ZoneTableAccessorCache ..> ZoneTableIteratorCache : <<create>>
+ZoneTableAccessorCache -u-o ZoneTableAccessor
+
+ZoneTableIteratorCache -u-o ZoneTableIterator
+ZoneTableIteratorCache -u-> CacheConfig
+
+ZoneWriter -d-> ZoneTableSegment
+ZoneWriter ..> ZoneData : add/replace
+
+ZoneTableSegment "1" *-r-> "1" ZoneTableHeader
+ZoneTableSegment "1" *-d-> "1" MemorySegment
+
+CacheConfig ..> LoadAction
+
+LoadAction ..> ZoneData : create
+LoadAction *-> ZoneDataLoader
+
+ZoneDataLoader -> ZoneData
+ZoneDataLoader *-> ZoneDataUpdater
+ZoneDataLoader -> MemorySegment
+
+ZoneDataUpdater -> ZoneData
+ZoneDataUpdater ..> RdataSet : create
+ZoneDataUpdater ..> RdataSet : add
+
+ZoneTableHeader "1" *-d-> "1" ZoneTable
+ZoneTable "1" *-d-> "1" ZoneData
+ZoneData "1" *-d-> "1" RdataSet
+
+loadFromFile -d-o LoadAction
+IteratorLoader -d-o LoadAction
+
+MemorySegmentMapped -d-o MemorySegment
+MemorySegmentLocal -d-o MemorySegment
+
+ZoneTableSegmentMapped -d-o ZoneTableSegment
+ZoneTableSegmentLocal -d-o ZoneTableSegment
+
+ZoneTableSegmentMapped *-d-> MemorySegmentMapped
+ZoneTableSegmentLocal *-d-> MemorySegmentLocal
+
+ at enduml
diff --git a/doc/design/resolver/01-scaling-across-cores b/doc/design/resolver/01-scaling-across-cores
new file mode 100644
index 0000000..dbd962f
--- /dev/null
+++ b/doc/design/resolver/01-scaling-across-cores
@@ -0,0 +1,347 @@
+Scaling across (many) cores
+===========================
+
+Problem statement
+-----------------
+
+The general issue is how to insure that the resolver scales.
+
+Currently resolvers are CPU bound, and it seems likely that both
+instructions-per-cycle and CPU frequency will not increase radically,
+scaling will need to be across multiple cores.
+
+How can we best scale a recursive resolver across multiple cores?
+
+Image of how resolution looks like
+----------------------------------
+
+ Receive the query. @# <------------------------\
+ | |
+ | |
+ v |
+ Parse it, etc. $ |
+ | |
+ | |
+ v |
+ Look into the cache. $# |
+ Cry <---- No <---------- Is it there? -----------> Yes ---------\ |
+ | ^ | |
+ Prepare upstream query $ | | |
+ | | | |
+ v | | |
+ Send an upstream query (#) | | |
+ | | | |
+ | | | |
+ v | | |
+ Wait for answer @(#) | | |
+ | | | |
+ v | | |
+ Parse $ | | |
+ | | | |
+ v | | |
+ Is it enough? $ ----> No ---------/ | |
+ | | |
+ Yes | |
+ | | |
+ \-----------------------> Build answer $ <----------------------/ |
+ | |
+ | |
+ v |
+ Send answer # -----------------------------/
+
+This is simplified version, however. There may be other tasks (validation, for
+example), which are not drawn mostly for simplicity, as they don't produce more
+problems. The validation would be done as part of some computational task and
+they could do more lookups in the cache or upstream queries.
+
+Also, multiple queries may generate the same upstream query, so they should be
+aggregated together somehow.
+
+Legend
+~~~~~~
+ * $ - CPU intensive
+ * @ - Waiting for external event
+ * # - Possible interaction with other tasks
+
+Goals
+-----
+ * Run the CPU intensive tasks in multiple threads to allow concurrency.
+ * Minimise waiting for locks.
+ * Don't require too much memory.
+ * Minimise the number of upstream queries (both because they are slow and
+ expensive and also because we don't want to eat too much bandwidth and spam
+ the authoritative servers).
+ * Design simple enough so it can be implemented.
+
+Naïve version
+-------------
+
+Let's look at possible approaches and list their pros and cons. Many of the
+simple versions would not really work, but let's have a look at them anyway,
+because thinking about them might bring some solutions for the real versions.
+
+We take one query, handle it fully, with blocking waits for the answers. After
+this is done, we take another. The cache is private for each one process.
+
+Advantages:
+
+ * Very simple.
+ * No locks.
+
+Disadvantages:
+
+ * To scale across cores, we need to run *a lot* of processes, since they'd be
+ waiting for something most of their time. That means a lot of memory eaten,
+ because each one has its own cache. Also, running so many processes may be
+ problematic, processes are not very cheap.
+ * Many things would be asked multiple times, because the caches are not
+ shared.
+
+Threads
+~~~~~~~
+
+Some of the problems could be solved by using threads, but they'd not improve
+it much, since threads are not really cheap either (starting several hundred
+threads might not be a good idea either).
+
+Also, threads bring other problems. When we still assume separate caches (for
+caches, see below), we need to ensure safe access to logging, configuration,
+network, etc. These could be a bottleneck (eg. if we lock every time we read a
+packet from network, when there are many threads, they'll just fight over the
+lock).
+
+Supercache
+~~~~~~~~~~
+
+The problem with cache could be solved by placing a ``supercache'' between the
+resolvers and the Internet. That one would do almost no processing, it would
+just take the query, looked up in the cache and either answered from the cache
+or forwarded the query to the external world. It would store the answer and
+forward it back.
+
+The cache, if single-threaded, could be a bottle-neck. To solve it, there could
+be several approaches:
+
+Layered cache::
+ Each process has it's own small cache, which catches many queries. Then, a
+ group of processes shares another level of bigger cache, which catches most
+ of the queries that get past the private caches. We further group them and
+ each level handles less queries from each process, so they can keep up.
+ However, with each level, we add some overhead to do another lookup.
+Segmented cache::
+ We have several caches of the same level, in parallel. When we would ask a
+ cache, we hash the query and decide which cache to ask by the hash. Only that
+ cache would have that answer if any and each could run in a separate process.
+ The only problem is, could there be a pattern of queries that would skew to
+ use only one cache while the rest would be idle?
+Shared cache access::
+ A cache would be accessed by multiple processes/threads. See below for
+ details, but there's a risk of lock contention on the cache (it depends on
+ the data structure).
+
+Upstream queries
+~~~~~~~~~~~~~~~~
+
+Before doing an upstream query, we look into the cache to ensure we don't have
+the information yet. When we get the answer, we want to update the cache.
+
+This suggests the upstream queries are tightly coupled with the cache. Now,
+when we have several cache processes/threads, each can have some set of opened
+sockets which are not shared with other caches to do the lookups. This way we
+can avoid locking the upstream network communication.
+
+Also, we can have three conceptual states for data in cache, and act
+differently when it is requested.
+
+Present::
+ If it is available, in positive or negative version, we just provide the
+ answer right away.
+Not present::
+ The continuation of processing is queued somehow (blocked/callback is
+ stored/whatever). An upstream query is sent and we get to the next state.
+Waiting for answer::
+ If another query for the same thing arrives, we just queue it the same way
+ and keep waiting. When the answer comes, all the queued tasks are resumed.
+ If the TTL > 0, we store the answer and set it to ``present''.
+
+We want to do aggregation of upstream queries anyway, using cache for it saves
+some more processing and possibly locks.
+
+Multiple parallel queries
+-------------------------
+
+It seems obvious we can't afford to have a thread or process for each
+outstanding query. We need to handle multiple queries in each one at any given
+time.
+
+Coroutines
+~~~~~~~~~~
+
+The OS-level threads might be too expensive, but coroutines might be cheap
+enough. In that way, we could still write a code that would be easy to read,
+but limit the number of OS threads to reasonable number.
+
+In this model, when a query comes, a new coroutine/user-level thread is created
+for it. We use special reads and writes whenever there's an operation that
+could block. These reads and writes would internally schedule the operation
+and switch to another coroutine (if there's any ready to be executed).
+
+Each thread/process maintains its own set of coroutines and they do not
+migrate. This way, the queue of coroutines is kept lock-less, as well as any
+private caches. Only the shared caches are protected by a lock.
+
+[NOTE]
+The `coro` unit we have in the current code is *not* considered a coroutine
+library here. We would need a coroutine library where we have real stack for
+each coroutine and we switch the stacks on coroutine switch. That is possible
+with reasonable amount of dark magic (see `ucontext.h`, for example, but there
+are surely some higher-level libraries for that).
+
+There are some trouble with multiple coroutines waiting on the same event, like
+the same upstream query (possibly even coroutines from different threads), but
+it should be possible to solve.
+
+Event-based
+~~~~~~~~~~~
+
+We use events (`asio` and stuff) for writing it. Each outstanding query is an
+object with some callbacks on it. When we would do a possibly blocking
+operation, we schedule a callback to happen once the operation finishes.
+
+This is more lightweight than the coroutines (the query objects will be smaller
+than the stacks for coroutines), but it is harder to write and read for.
+
+[NOTE]
+Do not consider cross-breeding the models. That leads to space-time distortions
+and brain damage. Implementing one on top of other is OK, but mixing it in the
+same bit of code is a way do madhouse.
+
+Landlords and peasants
+~~~~~~~~~~~~~~~~~~~~~~
+
+In both the coroutines and event-based models, the cache and other shared
+things are easier to imagine as objects the working threads fight over to hold
+for a short while. In this model, it is easier to imagine each such shared
+object as something owned by a landlord that doesn't let anyone else on it,
+but you can send requests to him.
+
+A query is an object once again, with some kind of state machine.
+
+Then there are two kinds of threads. The peasants are just to do the heavy
+work. There's a global work-queue for peasants. Once a peasant is idle, it
+comes to the queue and picks up a handful of queries from there. It does as
+much on each the query as possible without requiring any shared resource.
+
+The other kind, the landlords, have a resource to watch over each. So we would
+have a cache (or several parts of cache), the sockets for accepting queries and
+answering them, possibly more. Each of these would have a separate landlord
+thread and a queue of tasks to do on the resource (look up something, send an
+answer...).
+
+Similarly, the landlord would take a handful of tasks from its queue and start
+handling them. It would possibly produce some more tasks for the peasants.
+
+The point here is, all the synchronisation is done on the queues, not on the
+shared resources themselves. And, we would append to a queues once the whole
+batch was completed. By tweaking the size of the batch, we could balance the
+lock contention, throughput and RTT. The append/remove would be a quick
+operation, and the cost of locks would amortize in the larger amount of queries
+handled per one lock operation.
+
+The possible downside is, a query needs to travel across several threads during
+its lifetime. It might turn out it is faster to move the query between cores
+than accessing the cache from several threads, since it is smaller, but it
+might be slower as well.
+
+It would be critical to make some kind of queue that is fast to append to and
+fast to take out first n items. Also, the tasks in the queues can be just
+abstract `boost::function<void (Worker&)>` functors, and each worker would just
+iterate through the queue, calling each functor. The parameter would be to
+allow easy generation of more tasks for other queues (they would be stored
+privately first, and appended to remote queues at the end of batch).
+
+Also, if we wanted to generate multiple parallel upstream queries from a single
+query, we would need to be careful. A query object would not have a lock on
+itself and the upstream queries could end up in a different caches/threads. To
+protect the original query, we would add another landlord that would aggregate
+answers together and let the query continue processing once it got enough
+answers. That way, the answers would be pushed all to the same threads and they
+could not fight over the query.
+
+[NOTE]
+This model would work only with threads, not processes.
+
+Shared caches
+-------------
+
+While it seems it is good to have some sort of L1 cache with pre-rendered
+answers (according to measurements in the #2777 ticket), we probably need some
+kind of larger shared cache.
+
+If we had just a single shared cache protected by lock, there'd be a lot of
+lock contention on the lock.
+
+Partitioning the cache
+~~~~~~~~~~~~~~~~~~~~~~
+
+We split the cache into parts, either by the layers or by parallel bits we
+switch between by a hash. If we take it to the extreme, a lock on each hash
+bucket would be this kind, though that might be wasting resources (how
+expensive is it to create a lock?).
+
+Landlords
+~~~~~~~~~
+
+The landlords do synchronizations themselves. Still, the cache would need to be
+partitioned.
+
+RCU
+~~~
+
+The RCU is a lock-less synchronization mechanism. An item is accessed through a
+pointer. An updater creates a copy of the structure (in our case, it would be
+content of single hash bucket) and then atomically replaces the pointer. The
+readers from before have the old version, the new ones get the new version.
+When all the old readers die out, the old copy is reclaimed. Also, the
+reclamation can AFAIK be postponed for later times when we are slightly more
+idle or to a different thread.
+
+We could use it for cache â in the fast track, we would just read the cache. In
+the slow one, we would have to wait in queue to do the update, in a single
+updater thread (because we don't really want to be updating the same cell twice
+at the same time).
+
+Proposals
+---------
+
+In either case, we would have some kind of L1 cache with pre-rendered answers.
+For these proposals (except the third), we wouldn't care if we split the cache
+into parallel chunks or layers.
+
+Hybrid RCU/Landlord
+~~~~~~~~~~~~~~~~~~~
+
+The landlord approach, just read only accesses to the cache are done directly
+by the peasants. Only if they don't find what they want, they'd append the
+queue to the task of the landlord. The landlord would be doing the RCU updates.
+It could happen that by the time the landlord gets to the task the answer is
+already there, but that would not matter much.
+
+Accessing network would be from landlords.
+
+Coroutines+RCU
+~~~~~~~~~~~~~~
+
+We would do the coroutines, and the reads from shared cache would go without
+locking. When doing write, we would have to lock.
+
+To avoid locking, each worker thread would have its own set of upstream sockets
+and we would dup the sockets from users so we don't have to lock that.
+
+Multiple processes with coroutines and RCU
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This would need the layered cache. The upper caches would be mapped to local
+memory for read-only access. Each cache would be a separate process. The
+process would do the updates â if the answer was not there, the process would
+be asked by some kind of IPC to pull it from upstream cache or network.
diff --git a/doc/design/resolver/02-mixed-recursive-authority-setup b/doc/design/resolver/02-mixed-recursive-authority-setup
new file mode 100644
index 0000000..a1cc5f6
--- /dev/null
+++ b/doc/design/resolver/02-mixed-recursive-authority-setup
@@ -0,0 +1,150 @@
+Mixed recursive & authoritative setup
+=====================================
+
+Ideally we will run the authoritative server independently of the
+recursive resolver.
+
+We need a way to run both an authoritative and a recursive resolver on
+the same machine and listening on the same IP/port. But we need a way to
+run only one of them as well.
+
+This is mostly the same problem as we have with DDNS packets and xfr-out
+requests, but they aren't that performance sensitive as auth & resolver.
+
+There are a number of possible approaches to this:
+
+One fat module
+--------------
+
+With some build system or dynamic linker tricks, we create three modules:
+
+ * Stand-alone auth
+ * Stand-alone resolver
+ * Compound module containing both
+
+The user then chooses either one stand-alone module, or the compound one,
+depending on the requirements.
+
+Advantages
+~~~~~~~~~~
+
+ * It is easier to switch between processing and ask authoritative questions
+ from within the resolver processing.
+
+Disadvantages
+~~~~~~~~~~~~~
+
+ * The code is not separated (one bugs takes down both, admin can't see which
+ one takes how much CPU).
+ * BIND 9 does this and its code is a jungle. Maybe it's not just a
+ coincidence.
+ * Limits flexibility -- for example, we can't then decide to make the resolver
+ threaded (or we would have to make sure the auth processing doesn't break
+ with threads, which will be hard).
+
+There's also the idea of putting the auth into a loadable library and the
+resolver could load and use it somehow. But the advantages and disadvantages
+are probably the same.
+
+Auth first
+----------
+
+We do the same as with xfrout and ddns. When a query comes, it is examined and
+if the `RD` bit is set, it is forwarded to the resolver.
+
+Advantages
+~~~~~~~~~~
+
+ * Separate auth and resolver modules
+ * Minimal changes to auth
+ * No slowdown on the auth side
+
+Disadvantages
+~~~~~~~~~~~~~
+
+ * Counter-intuitive asymmetric design
+ * Possible slowdown on the resolver side
+ * Resolver needs to know both modes (for running stand-alone too)
+
+There's also the possibility of the reverse -- resolver first. It may make
+more sense for performance (the more usual scenario would probably be a
+high-load resolver with just few low-volume authoritative zones). On the other
+hand, auth already has some forwarding tricks.
+
+Auth with cache
+---------------
+
+This is mostly the same as ``Auth first'', however, the cache is in the auth
+server. If it is in the cache, it is answered right away. If not, it is then
+forwarded to the resolver. The resolver then updates the cache too.
+
+Advantages
+~~~~~~~~~~
+
+ * Probably good performance
+
+Disadvantages
+~~~~~~~~~~~~~
+
+ * Cache duplication (several auth modules, it doesn't feel like it would work
+ with shared memory without locking).
+ * Cache is probably very different from authoritative zones, it would
+ complicate auth processing.
+ * The resolver needs own copy of cache (to be able to get partial results),
+ probably a different one than the auth server.
+
+Receptionist
+------------
+
+One module does only the listening. It doesn't process the queries itself, it
+only looks into them and forwards them to the processing modules.
+
+Advantages
+~~~~~~~~~~
+
+ * Clean design with separated modules
+ * Easy to run modules stand-alone
+ * Allows for solving the xfrout & ddns forwarding without auth running
+ * Allows for views (different auths with different configurations)
+ * Allows balancing/clustering across multiple machines
+ * Easy to create new modules for different kinds of DNS handling and share
+ port with them too
+
+Disadvantages
+~~~~~~~~~~~~~
+
+ * Need to set up another module (not a problem if we have inter-module
+ dependencies in b10-init)
+ * Possible performance impact. However, experiments show this is not an issue,
+ and the receptionist can actually increase the throughput with some tuning
+ and the increase in RTT is not big.
+
+Implementation ideas
+~~~~~~~~~~~~~~~~~~~~
+
+ * Let's have a new TCP transport, where we send not only the DNS messages,
+ but also the source and destination ports and addresses (two reasons --
+ ACLs in target module and not keeping state in the receptionist). It would
+ allow for transfer of a batch of messages at once, to save some calls to
+ kernel (like a length of block of messages, it is read at once, then they
+ are all parsed one by one, the whole block of answers is sent back).
+ * A module creates a listening socket (UNIX by default) on startup and
+ contacts all the receptionists. It sends what kind of packets to send
+ to the module and the address of the UNIX socket. All the receptionists
+ connect to the module. This allows for auto-configuring the receptionist.
+ * The queries are sent from the receptionist in batches, the answers are sent
+ back to the receptionist in batches too.
+ * It is possible to fine-tune and use OS-specific tricks (like epoll or
+ sending multiple UDP messages by single call to sendmmsg()).
+
+Proposal
+--------
+
+Implement the receptionist in a way we can still work without it (not throwing
+the current UDPServer and TCPServer in asiodns away).
+
+The way we handle xfrout and DDNS needs some changes, since we can't forward
+sockets for the query. We would implement the receptionist protocol on them,
+which would allow the receptionist to forward messages to them. We would then
+modify auth to be able to forward the queries over the receptionist protocol,
+so ordinary users don't need to start the receptionist.
diff --git a/doc/design/resolver/03-cache-algorithm b/doc/design/resolver/03-cache-algorithm
new file mode 100644
index 0000000..42bfa09
--- /dev/null
+++ b/doc/design/resolver/03-cache-algorithm
@@ -0,0 +1,22 @@
+03-cache-algorithm
+
+Introduction
+------------
+Cache performance may be important for the resolver. It might not be
+critical. We need to research this.
+
+One key question is: given a specific cache hit rate, how much of an
+impact does cache performance have?
+
+For example, if we have 90% cache hit rate, will we still be spending
+most of our time in system calls or in looking things up in our cache?
+
+There are several ways we can consider figuring this out, including
+measuring this in existing resolvers (BIND 9, Unbound) or modeling
+with specific values.
+
+Once we know how critical the cache performance is, we can consider
+which algorithm is best for that. If it is very critical, then a
+custom algorithm designed for DNS caching makes sense. If it is not,
+then we can consider using an STL-based data structure.
+
diff --git a/doc/design/resolver/03-cache-algorithm.txt b/doc/design/resolver/03-cache-algorithm.txt
new file mode 100644
index 0000000..4aacc4d
--- /dev/null
+++ b/doc/design/resolver/03-cache-algorithm.txt
@@ -0,0 +1,256 @@
+03-cache-algorithm
+
+Introduction
+------------
+Cache performance may be important for the resolver. It might not be
+critical. We need to research this.
+
+One key question is: given a specific cache hit rate, how much of an
+impact does cache performance have?
+
+For example, if we have 90% cache hit rate, will we still be spending
+most of our time in system calls or in looking things up in our cache?
+
+There are several ways we can consider figuring this out, including
+measuring this in existing resolvers (BIND 9, Unbound) or modeling
+with specific values.
+
+Once we know how critical the cache performance is, we can consider
+which algorithm is best for that. If it is very critical, then a
+custom algorithm designed for DNS caching makes sense. If it is not,
+then we can consider using an STL-based data structure.
+
+Effectiveness of Cache
+----------------------
+
+First, I'll try to answer the introductory questions.
+
+In some simplified model, we can express the amount of running time
+for answering queries directly from the cache in the total running
+time including that used for recursive resolution due to cache miss as
+follows:
+
+A = r*Q2*/(r*Q2+ Q1*(1-r))
+where
+A: amount of time for answering queries from the cache per unit time
+ (such as sec, 0<=A<=1)
+r: cache hit rate (0<=r<=1)
+Q1: max qps of the server with 100% cache hit
+Q2: max qps of the server with 0% cache hit
+
+Q1 can be measured easily for given data set; measuring Q2 is tricky
+in general (it requires many external queries with unreliable
+results), but we can still have some not-so-unrealistic numbers
+through controlled simulation.
+
+As a data point for these values, see a previous experimental results
+of mine:
+https://lists.isc.org/pipermail/bind10-dev/2012-July/003628.html
+
+Looking at the "ideal" server implementation (no protocol overhead)
+with the set up 90% and 85% cache hit rate with 1 recursion on cache
+miss, and with the possible maximum total throughput, we can deduce
+Q1 and Q2, which are: 170591qps and 60138qps respectively.
+
+This means, with 90% cache hit rate (r = 0.9), the server would spend
+76% of its run time for receiving queries and answering responses
+directly from the cache: 0.9*60138/(0.9*60138 + 0.1*170591) = 0.76.
+
+I also ran more realistic experiments: using BIND 9.9.2 and unbound
+1.4.19 in the "forward only" mode with crafted query data and the
+forwarded server to emulate the situation of 100% and 0% cache hit
+rates. I then measured the max response throughput using a
+queryperf-like tool. In both cases Q2 is about 28% of Q1 (I'm not
+showing specific numbers to avoid unnecessary discussion about
+specific performance of existing servers; it's out of scope of this
+memo). Using Q2 = 0.28*Q1, above equation with 90% cache hit rate
+will be: A = 0.9 * 0.28 / (0.9*0.28 + 0.1) = 0.716. So the server will
+spend about 72% of its running time to answer queries directly from
+the cache.
+
+Of course, these experimental results are too simplified. First, in
+these experiments we assumed only one external query is needed on
+cache miss. In general it can be more; however, it may not actually
+be too optimistic either: in my another research result:
+http://bind10.isc.org/wiki/ResolverPerformanceResearch
+In the more detailed analysis using real query sample and tracing what
+an actual resolver would do, it looked we'd need about 1.44 to 1.63
+external queries per cache miss in average.
+
+Still, of course, the real world cases are not that simple: in reality
+we'd need to deal with timeouts, slower remote servers, unexpected
+intermediate results, etc. DNSSEC validating resolvers will clearly
+need to do more work.
+
+So, in the real world deployment Q2 should be much smaller than Q1.
+Here are some specific cases of the relationship between Q1 and Q2 for
+given A (assuming r = 0.9):
+
+70%: Q2 = 0.26 * Q1
+60%: Q2 = 0.17 * Q1
+50%: Q2 = 0.11 * Q1
+
+So, even if "recursive resolution is 10 times heavier" than the cache
+only case, we can assume the server spends a half of its run time for
+answering queries directly from the cache at the cache hit rate of
+90%. I think this is a reasonably safe assumption.
+
+Now, assuming the number of 50% or more, does this suggest we should
+highly optimize the cache? Opinions may vary on this point, but I
+personally think the answer is yes. I've written an experimental
+cache only implementation that employs the idea of fully-rendered
+cached data. On one test machine (2.20GHz AMD64, using a single
+core), queryperf-like benchmark shows it can handle over 180Kqps,
+while BIND 9.9.2 can just handle 41K qps. The experimental
+implementation skips some necessary features for a production server,
+and cache management itself is always inevitable bottleneck, so the
+production version wouldn't be that fast, but it still suggests it may
+not be very difficult to reach over 100Kqps in production environment
+including recursive resolution overhead.
+
+Cache Types
+-----------
+
+1. Record cache
+
+Conceptually, any recursive resolver (with cache) implementation would
+have cache for RRs (or RRsets in the modern version of protocol) given
+in responses to its external queries. In BIND 9, it's called the
+"cached DB", using an in-memory rbt-like tree. unbound calls it
+"rrset cache", which is implemented as a hash table.
+
+2. Delegation cache
+
+Recursive server implementations would also have cache to determine
+the deepest zone cut for a given query name in the recursion process.
+Neither BIND 9 nor unbound has a separate cache for this purpose;
+basically they try to find an NR RRset from the "record cache" whose
+owner name best matches the given query name.
+
+3. Remote server cache
+
+In addition, a recursive server implementation may maintain a cache
+for information of remote authoritative servers. Both BIND 9 and
+unbound conceptually have this type of cache, although there are some
+non-negligible differences in details. BIND 9's implementation of
+this cache is called ADB. Its a hash table whose key is domain name,
+and each entry stores corresponding IPv6/v4 addresses; another data
+structure for each address stores averaged RTT for the address,
+lameness information, EDNS availability, etc. unbound's
+implementation is called "infrastructure cache". It's a hash table
+keyed with IP addresses whose entries store similar information as
+that in BIND 9's per address ADB entry. In unbound a remote server's
+address must be determined by looking up the record cache (rrset cache
+in unbound terminology); unlike BIND 9's ADB, there's no direct
+shortcut from a server's domain name to IP addresses.
+
+4. Full response cache
+
+unbound has an additional cache layer, called the "message cache".
+It's a hash table whose hash key is query parameter (essentially qname
+and type) and entry is a sequence to record (rrset) cache entries.
+This sequence constructs a complete response to the corresponding
+query, so it would help optimize building a response message skipping
+the record cache for each section (answer/authority/additional) of the
+response message. PowerDNS recursor has (seemingly) the same concept
+called "packet cache" (but I don't know its implementation details
+very much).
+
+BIND 9 doesn't have this type of cache; it always looks into the
+record cache to build a complete response to a given query.
+
+Miscellaneous General Requirements
+----------------------------------
+
+- Minimize contention between threads (if threaded)
+- Cache purge policy: normally only a very small part of cached DNS
+ information will be reused, and those reused are very heavily
+ reused. So LRU-like algorithm should generally work well, but we'll
+ also need to honor DNS TTL.
+
+Random Ideas for BIND 10
+------------------------
+
+Below are specific random ideas for BIND 10. Some are based on
+experimental results with reasonably realistic data; some others are
+mostly a guess.
+
+1. Fully rendered response cache
+
+Some real world query samples show that a very small portion of entire
+queries are very popular and queried very often and many times; the
+rest is rarely reused, if any. Two different data sets show top
+10,000 queries would cover around 80% of total queries, regardless
+of the size of the total queries. This suggests an idea of having a
+small, highly optimized full response cache.
+
+I tried this idea in the jinmei-l1cache branch. It's a hash table
+keyed with a tuple of query name and type whose entry stores fully
+rendered, wire-format response image (answer section only, assuming
+the "minimal-responses" option). It also maintains offsets to each
+RR, so it can easily update TTLs when necessary or rotate RRs if
+optionally requested. If neither TTL adjustment nor RR rotation is
+required, query handling is just to lookup the hash table and copy the
+pre-rendered data. Experimental benchmark showed it ran vary fast;
+more than 4 times faster than BIND 9, and even much faster than other
+implementations that have full response cache (although, as usual, the
+comparison is not entirely fair).
+
+Also, the cache size is quite small; the run time memory footprint of
+this server process was just about 5MB. So, I think it's reasonable
+to have each process/thread have their own copy of this cache to
+completely eliminate contention. Also, if we can keep the cache size
+this small, it would be easier to dump it to a file on shutdown and
+reuse it on restart. This will be quite effective (if the downtime is
+reasonably short) because the cached data are expected to be highly
+popular.
+
+2. Record cache
+
+For the normal record cache, I don't have a particular idea beyond
+something obvious, like a hash table to map from query parameters to
+corresponding RRset (or negative information). But I guess this cache
+should be shared by multiple threads. That will help reconstruct the
+full response cache data on TTL expiration more efficiently. And, if
+shared, the data structure should be chosen so that contention
+overhead can be minimized. In general, I guess something like hash
+tables is more suitable than tree-like structure in that sense.
+
+There's other points to discuss for this cache related to other types
+of cache (see below).
+
+3. Separate delegation cache
+
+One thing I'm guessing is that it may make sense if we have a separate
+cache structure for delegation data. It's conceptually a set of NS
+RRs so we can identify the best (longest) matching one for a given
+query name.
+
+Analysis of some sets of query data showed the vast majority of
+end client's queries are for A and AAAA (not surprisingly). So, even
+if we separate this cache from the record cache, the additional
+overhead (both for memory and fetch) will probably (hopefully) be
+marginal. Separating caches will also help reduce contention between
+threads. It *might* also help improve lookup performance because this
+can be optimized for longest match search.
+
+4. Remote server cache without involving the record cache
+
+Likewise, it may make sense to maintain the remote server cache
+separately from the record cache. I guess these AAAA and A records
+are rarely the queried by end clients, so, like the case of delegation
+cache it's possible that the data sets are mostly disjoint. Also, for
+this purpose the RRsets don't have to have higher trust rank (per
+RFC2181 5.4.1): glue or additional are okay, and, by separating these
+from the record cache, we can avoid accidental promotion of these data
+to trustworthy answers and returning them to clients (BIND 9 had this
+type of bugs before).
+
+Custom vs Existing Library (STL etc)
+------------------------------------
+
+It may have to be discussed, but I guess in many cases we end up
+introducing custom implementation because these caches should be
+highly performance sensitive, directly related our core business, and
+also have to be memory efficient. But in some sub components we may
+be able to benefit from existing generic libraries.
diff --git a/doc/design/resolver/README b/doc/design/resolver/README
new file mode 100644
index 0000000..b6e9285
--- /dev/null
+++ b/doc/design/resolver/README
@@ -0,0 +1,5 @@
+This directory contains research and design documents for the BIND 10
+resolver reimplementation.
+
+Each file contains a specific issue and discussion surrounding that
+issue.
diff --git a/src/bin/Makefile.am b/src/bin/Makefile.am
index bdef830..bf61827 100644
--- a/src/bin/Makefile.am
+++ b/src/bin/Makefile.am
@@ -1,5 +1,5 @@
SUBDIRS = bind10 bindctl cfgmgr ddns loadzone msgq cmdctl auth xfrin \
xfrout usermgr zonemgr stats tests resolver sockcreator dhcp4 dhcp6 d2\
- dbutil sysinfo
+ dbutil sysinfo memmgr
check-recursive: all-recursive
diff --git a/src/bin/auth/auth_srv.cc b/src/bin/auth/auth_srv.cc
index 90efee7..ee0306f 100644
--- a/src/bin/auth/auth_srv.cc
+++ b/src/bin/auth/auth_srv.cc
@@ -388,7 +388,8 @@ private:
};
AuthSrv::AuthSrv(isc::xfr::AbstractXfroutClient& xfrout_client,
- isc::util::io::BaseSocketSessionForwarder& ddns_forwarder)
+ isc::util::io::BaseSocketSessionForwarder& ddns_forwarder) :
+ dnss_(NULL)
{
impl_ = new AuthSrvImpl(xfrout_client, ddns_forwarder);
checkin_ = new ConfigChecker(this);
@@ -521,6 +522,8 @@ AuthSrv::processMessage(const IOMessage& io_message, Message& message,
return;
}
+ stats_attrs.setRequestRD(message.getHeaderFlag(Message::HEADERFLAG_RD));
+
const Opcode& opcode = message.getOpcode();
// Get opcode at this point; for all requests regardless of message body
// sanity check.
diff --git a/src/bin/auth/b10-auth.xml.pre b/src/bin/auth/b10-auth.xml.pre
index db5be3e..2bf20c8 100644
--- a/src/bin/auth/b10-auth.xml.pre
+++ b/src/bin/auth/b10-auth.xml.pre
@@ -20,7 +20,7 @@
<refentry>
<refentryinfo>
- <date>February 5, 2013</date>
+ <date>May 22, 2013</date>
</refentryinfo>
<refmeta>
@@ -248,6 +248,18 @@
but remember that if there's any error related to TSIG, some
of the counted opcode may not be trustworthy.
</para>
+
+ <para>
+ The <quote>qryrecursion</quote> counter is limited to queries
+ (requests of opcode 0) even though the RD bit is not specific
+ to queries. In practice, this bit is generally just ignored for
+ other types of requests, while DNS servers behave differently
+ for queries depending on this bit. It is also known that
+ some authoritative-only servers receive a non negligible
+ number of queries with the RD bit being set, so it would be
+ of particular interest to have a specific counters for such
+ requests.
+ </para>
</note>
</refsect1>
diff --git a/src/bin/auth/datasrc_clients_mgr.h b/src/bin/auth/datasrc_clients_mgr.h
index 7b40e1f..11475e2 100644
--- a/src/bin/auth/datasrc_clients_mgr.h
+++ b/src/bin/auth/datasrc_clients_mgr.h
@@ -623,7 +623,7 @@ DataSrcClientsBuilderBase<MutexType, CondVarType>::getZoneWriter(
datasrc::ConfigurableClientList::ZoneWriterPair writerpair;
{
typename MutexType::Locker locker(*map_mutex_);
- writerpair = client_list.getCachedZoneWriter(origin);
+ writerpair = client_list.getCachedZoneWriter(origin, false);
}
switch (writerpair.first) {
diff --git a/src/bin/auth/statistics.cc.pre b/src/bin/auth/statistics.cc.pre
index 21141b0..14341fe 100644
--- a/src/bin/auth/statistics.cc.pre
+++ b/src/bin/auth/statistics.cc.pre
@@ -138,7 +138,14 @@ Counters::incRequest(const MessageAttributes& msgattrs) {
// if a short message which does not contain DNS header is received, or
// a response message (i.e. QR bit is set) is received.
if (opcode) {
- server_msg_counter_.inc(opcode_to_msgcounter[opcode.get().getCode()]);
+ server_msg_counter_.inc(opcode_to_msgcounter[opcode->getCode()]);
+
+ if (opcode.get() == Opcode::QUERY()) {
+ // Recursion Desired bit
+ if (msgattrs.requestHasRD()) {
+ server_msg_counter_.inc(MSG_QRYRECURSION);
+ }
+ }
}
// TSIG
diff --git a/src/bin/auth/statistics.h b/src/bin/auth/statistics.h
index 52f9bad..2ab987d 100644
--- a/src/bin/auth/statistics.h
+++ b/src/bin/auth/statistics.h
@@ -66,6 +66,8 @@ private:
enum BitAttributes {
REQ_WITH_EDNS_0, // request with EDNS ver.0
REQ_WITH_DNSSEC_OK, // DNSSEC OK (DO) bit is set in request
+ REQ_WITH_RD, // Recursion Desired (RD) bit is set in
+ // request
REQ_TSIG_SIGNED, // request is signed with valid TSIG
REQ_BADSIG, // request is signed but bad signature
RES_IS_TRUNCATED, // response is truncated
@@ -170,6 +172,22 @@ public:
bit_attributes_[REQ_WITH_DNSSEC_OK] = with_dnssec_ok;
}
+ /// \brief Return Recursion Desired (RD) bit of the request.
+ ///
+ /// \return true if Recursion Desired (RD) bit of the request is set
+ /// \throw None
+ bool requestHasRD() const {
+ return (bit_attributes_[REQ_WITH_RD]);
+ }
+
+ /// \brief Set Recursion Desired (RD) bit of the request.
+ ///
+ /// \param with_rd true if Recursion Desired (RD)bit of the request is set
+ /// \throw None
+ void setRequestRD(const bool with_rd) {
+ bit_attributes_[REQ_WITH_RD] = with_rd;
+ }
+
/// \brief Return whether the request is TSIG signed or not.
///
/// \return true if the request is TSIG signed
diff --git a/src/bin/auth/statistics_msg_items.def b/src/bin/auth/statistics_msg_items.def
index d8d3597..05d96c9 100644
--- a/src/bin/auth/statistics_msg_items.def
+++ b/src/bin/auth/statistics_msg_items.def
@@ -31,6 +31,7 @@ qrynoauthans MSG_QRYNOAUTHANS Number of queries received by the b10-auth server
qryreferral MSG_QRYREFERRAL Number of queries received by the b10-auth server resulted in referral answer.
qrynxrrset MSG_QRYNXRRSET Number of queries received by the b10-auth server resulted in NoError and AA bit is set in the response, but the number of answer RR == 0.
authqryrej MSG_QRYREJECT Number of authoritative queries rejected by the b10-auth server.
+qryrecursion MSG_QRYRECURSION Number of queries received by the b10-auth server with "Recursion Desired" (RD) bit was set.
rcode msg_counter_rcode Rcode statistics =
noerror MSG_RCODE_NOERROR Number of requests received by the b10-auth server resulted in RCODE = 0 (NoError).
formerr MSG_RCODE_FORMERR Number of requests received by the b10-auth server resulted in RCODE = 1 (FormErr).
diff --git a/src/bin/auth/tests/statistics_unittest.cc.pre b/src/bin/auth/tests/statistics_unittest.cc.pre
index cf6f29a..654bcd9 100644
--- a/src/bin/auth/tests/statistics_unittest.cc.pre
+++ b/src/bin/auth/tests/statistics_unittest.cc.pre
@@ -361,6 +361,64 @@ TEST_F(CountersTest, incrementTSIG) {
}
}
+TEST_F(CountersTest, incrementRD) {
+ Message response(Message::RENDER);
+ MessageAttributes msgattrs;
+ std::map<std::string, int> expect;
+
+ // Test these patterns:
+ // OpCode Recursion Desired
+ // ---------------------------
+ // 0 (Query) false
+ // 0 (Query) true
+ // 2 (Status) false
+ // 2 (Status) true
+ // Make sure the counter will be incremented only for the requests with
+ // OpCode=Query and Recursion Desired (RD) bit=1.
+ int count_opcode_query = 0;
+ int count_opcode_status = 0;
+ for (int i = 0; i < 4; ++i) {
+ const bool is_recursion_desired = i & 1;
+ const uint8_t opcode_code = i & 0x2;
+ const Opcode opcode(opcode_code);
+ buildSkeletonMessage(msgattrs);
+ msgattrs.setRequestRD(is_recursion_desired);
+ msgattrs.setRequestOpCode(opcode);
+
+ response.setRcode(Rcode::REFUSED());
+ response.addQuestion(Question(Name("example.com"),
+ RRClass::IN(), RRType::AAAA()));
+ response.setHeaderFlag(Message::HEADERFLAG_QR);
+
+ counters.inc(msgattrs, response, true);
+
+ if (opcode == Opcode::QUERY()) {
+ ++count_opcode_query;
+ } else {
+ ++count_opcode_status;
+ }
+
+ expect.clear();
+ expect["opcode.query"] = count_opcode_query;
+ expect["opcode.status"] = count_opcode_status;
+ expect["request.v4"] = i+1;
+ expect["request.udp"] = i+1;
+ expect["request.edns0"] = i+1;
+ expect["request.dnssec_ok"] = i+1;
+ expect["responses"] = i+1;
+ // qryrecursion will (only) be incremented if i == 1: OpCode=Query and
+ // RD bit=1
+ expect["qryrecursion"] = (i == 0) ? 0 : 1;
+ expect["rcode.refused"] = i+1;
+ // these counters are for queries; the value will be equal to the
+ // number of requests with OpCode=Query
+ expect["qrynoauthans"] = count_opcode_query;
+ expect["authqryrej"] = count_opcode_query;
+ checkStatisticsCounters(counters.get()->get("zones")->get("_SERVER_"),
+ expect);
+ }
+}
+
TEST_F(CountersTest, incrementOpcode) {
Message response(Message::RENDER);
MessageAttributes msgattrs;
diff --git a/src/bin/bind10/init.py.in b/src/bin/bind10/init.py.in
index efc0b04..3bb7ea7 100755
--- a/src/bin/bind10/init.py.in
+++ b/src/bin/bind10/init.py.in
@@ -89,7 +89,8 @@ logger = isc.log.Logger("init")
DBG_PROCESS = logger.DBGLVL_TRACE_BASIC
DBG_COMMANDS = logger.DBGLVL_TRACE_DETAIL
-# Messages sent over the unix domain socket to indicate if it is followed by a real socket
+# Messages sent over the unix domain socket to indicate if it is followed by a
+# real socket
CREATOR_SOCKET_OK = b"1\n"
CREATOR_SOCKET_UNAVAILABLE = b"0\n"
@@ -200,7 +201,8 @@ class Init:
verbose=False, nokill=False, setuid=None, setgid=None,
username=None, cmdctl_port=None, wait_time=10):
"""
- Initialize the Init of BIND. This is a singleton (only one can run).
+ Initialize the Init of BIND. This is a singleton (only one can
+ run).
The msgq_socket_file specifies the UNIX domain socket file that the
msgq process listens on. If verbose is True, then b10-init reports
@@ -223,12 +225,13 @@ class Init:
self.component_config = {}
# Some time in future, it may happen that a single component has
# multple processes (like a pipeline-like component). If so happens,
- # name "components" may be inappropriate. But as the code isn't probably
- # completely ready for it, we leave it at components for now. We also
- # want to support multiple instances of a single component. If it turns
- # out that we'll have a single component with multiple same processes
- # or if we start multiple components with the same configuration (we do
- # this now, but it might change) is an open question.
+ # name "components" may be inappropriate. But as the code isn't
+ # probably completely ready for it, we leave it at components for
+ # now. We also want to support multiple instances of a single
+ # component. If it turns out that we'll have a single component with
+ # multiple same processes or if we start multiple components with the
+ # same configuration (we do this now, but it might change) is an open
+ # question.
self.components = {}
# Simply list of components that died and need to wait for a
# restart. Components manage their own restart schedule now
@@ -351,7 +354,8 @@ class Init:
def command_handler(self, command, args):
logger.debug(DBG_COMMANDS, BIND10_RECEIVED_COMMAND, command)
- answer = isc.config.ccsession.create_answer(1, "command not implemented")
+ answer = isc.config.ccsession.create_answer(1,
+ "command not implemented")
if type(command) != str:
answer = isc.config.ccsession.create_answer(1, "bad command")
else:
@@ -440,7 +444,8 @@ class Init:
if pid is None:
logger.debug(DBG_PROCESS, BIND10_STARTED_PROCESS, self.curproc)
else:
- logger.debug(DBG_PROCESS, BIND10_STARTED_PROCESS_PID, self.curproc, pid)
+ logger.debug(DBG_PROCESS, BIND10_STARTED_PROCESS_PID, self.curproc,
+ pid)
def process_running(self, msg, who):
"""
@@ -499,7 +504,8 @@ class Init:
if msgq_proc.process:
msgq_proc.process.kill()
logger.error(BIND10_CONNECTING_TO_CC_FAIL)
- raise CChannelConnectError("Unable to connect to c-channel after 5 seconds")
+ raise CChannelConnectError("Unable to connect to c-channel " +
+ "after 5 seconds")
# try to connect, and if we can't wait a short while
try:
@@ -507,13 +513,43 @@ class Init:
except isc.cc.session.SessionError:
time.sleep(0.1)
- # Subscribe to the message queue. The only messages we expect to receive
- # on this channel are once relating to process startup.
+ # Subscribe to the message queue. The only messages we expect to
+ # receive on this channel are once relating to process startup.
if self.cc_session is not None:
self.cc_session.group_subscribe("Init")
return msgq_proc
+ def wait_msgq(self):
+ """
+ Wait for the message queue to fully start. It does so only after
+ the config manager connects to it. We know it is ready when it
+ starts answering commands.
+
+ We don't add a specific command for it here, an error response is
+ as good as positive one to know it is alive.
+ """
+ # We do 10 times shorter sleep here (since the start should be fast
+ # now), so we have 10 times more attempts.
+ time_remaining = self.wait_time * 10
+ retry = True
+ while time_remaining > 0 and retry:
+ try:
+ self.ccs.rpc_call('AreYouThere?', 'Msgq')
+ # We don't expect this to succeed. If it does, it's programmer
+ # error
+ raise Exception("Non-existing RPC call succeeded")
+ except isc.config.RPCRecipientMissing:
+ retry = True # Not there yet
+ time.sleep(0.1)
+ time_remaining -= 1
+ except isc.config.RPCError:
+ retry = False # It doesn't like the RPC, so it's alive now
+
+ if retry: # Still not started
+ raise ProcessStartError("Msgq didn't complete the second stage " +
+ "of startup")
+
def start_cfgmgr(self):
"""
Starts the configuration manager process
@@ -536,14 +572,16 @@ class Init:
# time to wait can be set on the command line.
time_remaining = self.wait_time
msg, env = self.cc_session.group_recvmsg()
- while time_remaining > 0 and not self.process_running(msg, "ConfigManager"):
+ while time_remaining > 0 and not self.process_running(msg,
+ "ConfigManager"):
logger.debug(DBG_PROCESS, BIND10_WAIT_CFGMGR)
time.sleep(1)
time_remaining = time_remaining - 1
msg, env = self.cc_session.group_recvmsg()
if not self.process_running(msg, "ConfigManager"):
- raise ProcessStartError("Configuration manager process has not started")
+ raise ProcessStartError("Configuration manager process has not " +
+ "started")
return bind_cfgd
@@ -567,7 +605,8 @@ class Init:
# A couple of utility methods for starting processes...
- def start_process(self, name, args, c_channel_env, port=None, address=None):
+ def start_process(self, name, args, c_channel_env, port=None,
+ address=None):
"""
Given a set of command arguments, start the process and output
appropriate log messages. If the start is successful, the process
@@ -612,9 +651,9 @@ class Init:
# The next few methods start up the rest of the BIND-10 processes.
# Although many of these methods are little more than a call to
- # start_simple, they are retained (a) for testing reasons and (b) as a place
- # where modifications can be made if the process start-up sequence changes
- # for a given process.
+ # start_simple, they are retained (a) for testing reasons and (b) as a
+ # place where modifications can be made if the process start-up sequence
+ # changes for a given process.
def start_auth(self):
"""
@@ -666,6 +705,10 @@ class Init:
# inside the configurator.
self.start_ccsession(self.c_channel_env)
+ # Make sure msgq is fully started before proceeding to the rest
+ # of the components.
+ self.wait_msgq()
+
# Extract the parameters associated with Init. This can only be
# done after the CC Session is started. Note that the logging
# configuration may override the "-v" switch set on the command line.
@@ -689,7 +732,8 @@ class Init:
try:
self.cc_session = isc.cc.Session(self.msgq_socket_file)
logger.fatal(BIND10_MSGQ_ALREADY_RUNNING)
- return "b10-msgq already running, or socket file not cleaned , cannot start"
+ return "b10-msgq already running, or socket file not cleaned , " +\
+ "cannot start"
except isc.cc.session.SessionError:
# this is the case we want, where the msgq is not running
pass
@@ -948,8 +992,8 @@ class Init:
def set_creator(self, creator):
"""
- Registeres a socket creator into the b10-init. The socket creator is not
- used directly, but through a cache. The cache is created in this
+ Registeres a socket creator into the b10-init. The socket creator is
+ not used directly, but through a cache. The cache is created in this
method.
If called more than once, it raises a ValueError.
@@ -1121,9 +1165,12 @@ def parse_args(args=sys.argv[1:], Parser=OptionParser):
parser = Parser(version=VERSION)
parser.add_option("-m", "--msgq-socket-file", dest="msgq_socket_file",
type="string", default=None,
- help="UNIX domain socket file the b10-msgq daemon will use")
+ help="UNIX domain socket file the b10-msgq daemon " +
+ "will use")
parser.add_option("-i", "--no-kill", action="store_true", dest="nokill",
- default=False, help="do not send SIGTERM and SIGKILL signals to modules during shutdown")
+ default=False,
+ help="do not send SIGTERM and SIGKILL signals to " +
+ "modules during shutdown")
parser.add_option("-u", "--user", dest="user", type="string", default=None,
help="Change user after startup (must run as root)")
parser.add_option("-v", "--verbose", dest="verbose", action="store_true",
@@ -1147,7 +1194,9 @@ def parse_args(args=sys.argv[1:], Parser=OptionParser):
default=None,
help="file to dump the PID of the BIND 10 process")
parser.add_option("-w", "--wait", dest="wait_time", type="int",
- default=10, help="Time (in seconds) to wait for config manager to start up")
+ default=10,
+ help="Time (in seconds) to wait for config manager to "
+ "start up")
(options, args) = parser.parse_args(args)
diff --git a/src/bin/bind10/run_bind10.sh.in b/src/bin/bind10/run_bind10.sh.in
index a22f300..09c9708 100755
--- a/src/bin/bind10/run_bind10.sh.in
+++ b/src/bin/bind10/run_bind10.sh.in
@@ -20,7 +20,7 @@ export PYTHON_EXEC
BIND10_PATH=@abs_top_builddir@/src/bin/bind10
-PATH=@abs_top_builddir@/src/bin/msgq:@abs_top_builddir@/src/bin/auth:@abs_top_builddir@/src/bin/resolver:@abs_top_builddir@/src/bin/cfgmgr:@abs_top_builddir@/src/bin/cmdctl:@abs_top_builddir@/src/bin/stats:@abs_top_builddir@/src/bin/xfrin:@abs_top_builddir@/src/bin/xfrout:@abs_top_builddir@/src/bin/zonemgr:@abs_top_builddir@/src/bin/ddns:@abs_top_builddir@/src/bin/dhcp6:@abs_top_builddir@/src/bin/sockcreator:$PATH
+PATH=@abs_top_builddir@/src/bin/msgq:@abs_top_builddir@/src/bin/auth:@abs_top_builddir@/src/bin/resolver:@abs_top_builddir@/src/bin/cfgmgr:@abs_top_builddir@/src/bin/cmdctl:@abs_top_builddir@/src/bin/stats:@abs_top_builddir@/src/bin/xfrin:@abs_top_builddir@/src/bin/xfrout:@abs_top_builddir@/src/bin/zonemgr:@abs_top_builddir@/src/bin/ddns:@abs_top_builddir@/src/bin/dhcp6:@abs_top_builddir@/src/bin/sockcreator:@abs_top_builddir@/src/bin/memmgr:$PATH
export PATH
PYTHONPATH=@abs_top_builddir@/src/lib/python/isc/log_messages:@abs_top_builddir@/src/lib/python/isc/cc:@abs_top_builddir@/src/lib/python:@abs_top_builddir@/src/lib/dns/python/.libs:@abs_top_builddir@/src/lib/xfr/.libs:@abs_top_builddir@/src/lib/log/.libs:@abs_top_builddir@/src/lib/util/io/.libs:@abs_top_builddir@/src/lib/python/isc/config:@abs_top_builddir@/src/lib/python/isc/acl/.libs:@abs_top_builddir@/src/lib/python/isc/datasrc/.libs
diff --git a/src/bin/bind10/tests/init_test.py.in b/src/bin/bind10/tests/init_test.py.in
index 8ac6458..913e642 100644
--- a/src/bin/bind10/tests/init_test.py.in
+++ b/src/bin/bind10/tests/init_test.py.in
@@ -16,7 +16,8 @@
# Most of the time, we omit the "init" for brevity. Sometimes,
# we want to be explicit about what we do, like when hijacking a library
# call used by the b10-init.
-from init import Init, ProcessInfo, parse_args, dump_pid, unlink_pid_file, _BASETIME
+from init import Init, ProcessInfo, parse_args, dump_pid, unlink_pid_file, \
+ _BASETIME
import init
# XXX: environment tests are currently disabled, due to the preprocessor
@@ -941,6 +942,7 @@ class TestStartStopProcessesInit(unittest.TestCase):
init.start_ccsession = lambda _: start_ccsession()
# We need to return the original _read_bind10_config
init._read_bind10_config = lambda: Init._read_bind10_config(init)
+ init.wait_msgq = lambda: None
init.start_all_components()
self.check_started(init, True, start_auth, start_resolver)
self.check_environment_unchanged()
@@ -967,6 +969,7 @@ class TestStartStopProcessesInit(unittest.TestCase):
init = MockInit()
self.check_preconditions(init)
+ init.wait_msgq = lambda: None
init.start_all_components()
init.runnable = True
init.config_handler(self.construct_config(False, False))
@@ -1028,6 +1031,7 @@ class TestStartStopProcessesInit(unittest.TestCase):
init = MockInit()
self.check_preconditions(init)
+ init.wait_msgq = lambda: None
init.start_all_components()
init.runnable = True
@@ -1066,6 +1070,7 @@ class TestStartStopProcessesInit(unittest.TestCase):
init = MockInit()
self.check_preconditions(init)
+ init.wait_msgq = lambda: None
init.start_all_components()
init.config_handler(self.construct_config(False, False))
self.check_started_dhcp(init, False, False)
@@ -1075,6 +1080,7 @@ class TestStartStopProcessesInit(unittest.TestCase):
init = MockInit()
self.check_preconditions(init)
# v6 only enabled
+ init.wait_msgq = lambda: None
init.start_all_components()
init.runnable = True
init._Init_started = True
@@ -1347,6 +1353,7 @@ class TestInitComponents(unittest.TestCase):
# Start it
orig = init._component_configurator.startup
init._component_configurator.startup = self.__unary_hook
+ init.wait_msgq = lambda: None
init.start_all_components()
init._component_configurator.startup = orig
self.__check_core(self.__param)
@@ -1499,6 +1506,7 @@ class TestInitComponents(unittest.TestCase):
pass
init.ccs = CC()
init.ccs.get_full_config = lambda: {'components': self.__compconfig}
+ init.wait_msgq = lambda: None
init.start_all_components()
self.__check_extended(self.__param)
@@ -1768,6 +1776,51 @@ class TestInitComponents(unittest.TestCase):
# this is set by ProcessInfo.spawn()
self.assertEqual(42147, pi.pid)
+ def test_wait_msgq(self):
+ """
+ Test we can wait for msgq to provide its own alias.
+
+ It is not available the first time, the second it is.
+ """
+ class RpcSession:
+ def __init__(self):
+ # Not yet called
+ self.called = 0
+
+ def rpc_call(self, command, recipient):
+ self.called += 1
+ if self.called == 1:
+ raise isc.config.RPCRecipientMissing("Not yet")
+ elif self.called == 2:
+ raise isc.config.RPCError(1, "What?")
+ else:
+ raise Exception("Called too many times")
+
+ init = MockInitSimple()
+ init.wait_time = 1
+ init.ccs = RpcSession()
+ init.wait_msgq()
+ self.assertEqual(2, init.ccs.called)
+
+ def test_wait_msgq_fail(self):
+ """
+ Test the wait_msgq fails in case the msgq does not appear
+ after so many attempts.
+ """
+ class RpcSession:
+ def __init__(self):
+ self.called = 0
+
+ def rpc_call(self, command, recipient):
+ self.called += 1
+ raise isc.config.RPCRecipientMissing("Not yet")
+
+ b10init = MockInitSimple()
+ b10init.wait_time = 1
+ b10init.ccs = RpcSession()
+ self.assertRaises(init.ProcessStartError, b10init.wait_msgq)
+ self.assertEqual(10, b10init.ccs.called)
+
def test_start_cfgmgr(self):
'''Test that b10-cfgmgr is started.'''
class DummySession():
diff --git a/src/bin/cfgmgr/plugins/tests/datasrc_test.py b/src/bin/cfgmgr/plugins/tests/datasrc_test.py
index 3c714c6..546e534 100644
--- a/src/bin/cfgmgr/plugins/tests/datasrc_test.py
+++ b/src/bin/cfgmgr/plugins/tests/datasrc_test.py
@@ -96,15 +96,6 @@ class DatasrcTest(unittest.TestCase):
"params": {}
}]})
- def test_dstype_bad(self):
- """
- The configuration is correct by the spec, but it would be rejected
- by the client list. Check we reject it.
- """
- self.reject({"IN": [{
- "type": "No such type"
- }]})
-
def test_invalid_mem_params(self):
"""
The client list skips in-memory sources. So we check it locally that
diff --git a/src/bin/cmdctl/cmdctl.py.in b/src/bin/cmdctl/cmdctl.py.in
index b1ee903..7a9e8b8 100755
--- a/src/bin/cmdctl/cmdctl.py.in
+++ b/src/bin/cmdctl/cmdctl.py.in
@@ -36,7 +36,6 @@ import re
import ssl, socket
import isc
import pprint
-import select
import csv
import random
import time
diff --git a/src/bin/d2/.gitignore b/src/bin/d2/.gitignore
index 7b03931..d147802 100644
--- a/src/bin/d2/.gitignore
+++ b/src/bin/d2/.gitignore
@@ -1,5 +1,5 @@
-/b10-d2
-/b10-d2.8
+/b10-dhcp-ddns
+/b10-dhcp-ddns.8
/d2_messages.cc
/d2_messages.h
/spec_config.h
diff --git a/src/bin/d2/Makefile.am b/src/bin/d2/Makefile.am
index 7cf3019..667c11e 100644
--- a/src/bin/d2/Makefile.am
+++ b/src/bin/d2/Makefile.am
@@ -48,10 +48,15 @@ pkglibexec_PROGRAMS = b10-dhcp-ddns
b10_dhcp_ddns_SOURCES = main.cc
b10_dhcp_ddns_SOURCES += d2_log.cc d2_log.h
-b10_dhcp_ddns_SOURCES += d_process.h
b10_dhcp_ddns_SOURCES += d2_process.cc d2_process.h
b10_dhcp_ddns_SOURCES += d_controller.cc d_controller.h
b10_dhcp_ddns_SOURCES += d2_controller.cc d2_controller.h
+b10_dhcp_ddns_SOURCES += d_cfg_mgr.cc d_cfg_mgr.h
+b10_dhcp_ddns_SOURCES += d2_config.cc d2_config.h
+b10_dhcp_ddns_SOURCES += d2_cfg_mgr.cc d2_cfg_mgr.h
+b10_dhcp_ddns_SOURCES += d2_update_message.cc d2_update_message.h
+b10_dhcp_ddns_SOURCES += d2_zone.cc d2_zone.h
+b10_dhcp_ddns_SOURCES += ncr_msg.cc ncr_msg.h
nodist_b10_dhcp_ddns_SOURCES = d2_messages.h d2_messages.cc
EXTRA_DIST += d2_messages.mes
@@ -61,6 +66,9 @@ b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
+b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/dhcpsrv/libb10-dhcpsrv.la
+b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/dns/libb10-dns++.la
+b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
b10_dhcp_ddnsdir = $(pkgdatadir)
b10_dhcp_ddns_DATA = dhcp-ddns.spec
diff --git a/src/bin/d2/d2_cfg_mgr.cc b/src/bin/d2/d2_cfg_mgr.cc
new file mode 100644
index 0000000..07068d6
--- /dev/null
+++ b/src/bin/d2/d2_cfg_mgr.cc
@@ -0,0 +1,132 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <d2/d2_log.h>
+#include <d2/d2_cfg_mgr.h>
+
+#include <boost/foreach.hpp>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::data;
+using namespace isc::asiolink;
+
+namespace isc {
+namespace d2 {
+
+// *********************** D2CfgContext *************************
+
+D2CfgContext::D2CfgContext()
+ : forward_mgr_(new DdnsDomainListMgr("forward_mgr")),
+ reverse_mgr_(new DdnsDomainListMgr("reverse_mgr")),
+ keys_(new TSIGKeyInfoMap()) {
+}
+
+D2CfgContext::D2CfgContext(const D2CfgContext& rhs) : DCfgContextBase(rhs) {
+ if (rhs.forward_mgr_) {
+ forward_mgr_.reset(new DdnsDomainListMgr(rhs.forward_mgr_->getName()));
+ forward_mgr_->setDomains(rhs.forward_mgr_->getDomains());
+ }
+
+ if (rhs.reverse_mgr_) {
+ reverse_mgr_.reset(new DdnsDomainListMgr(rhs.reverse_mgr_->getName()));
+ reverse_mgr_->setDomains(rhs.reverse_mgr_->getDomains());
+ }
+
+ keys_ = rhs.keys_;
+}
+
+D2CfgContext::~D2CfgContext() {
+}
+
+// *********************** D2CfgMgr *************************
+
+D2CfgMgr::D2CfgMgr() : DCfgMgrBase(DCfgContextBasePtr(new D2CfgContext())) {
+ // TSIG keys need to parse before the Domains, so we can catch Domains
+ // that specify undefined keys. Create the necessary parsing order now.
+ addToParseOrder("interface");
+ addToParseOrder("ip_address");
+ addToParseOrder("port");
+ addToParseOrder("tsig_keys");
+ addToParseOrder("forward_ddns");
+ addToParseOrder("reverse_ddns");
+}
+
+D2CfgMgr::~D2CfgMgr() {
+}
+
+bool
+D2CfgMgr::matchForward(const std::string& fqdn, DdnsDomainPtr& domain) {
+ if (fqdn.empty()) {
+ // This is a programmatic error and should not happen.
+ isc_throw(D2CfgError, "matchForward passed an empty fqdn");
+ }
+
+ // Fetch the forward manager from the D2 context.
+ DdnsDomainListMgrPtr mgr = getD2CfgContext()->getForwardMgr();
+
+ // Call the manager's match method and return the result.
+ return (mgr->matchDomain(fqdn, domain));
+}
+
+bool
+D2CfgMgr::matchReverse(const std::string& fqdn, DdnsDomainPtr& domain) {
+ if (fqdn.empty()) {
+ // This is a programmatic error and should not happen.
+ isc_throw(D2CfgError, "matchReverse passed a null or empty fqdn");
+ }
+
+ // Fetch the reverse manager from the D2 context.
+ DdnsDomainListMgrPtr mgr = getD2CfgContext()->getReverseMgr();
+
+ // Call the manager's match method and return the result.
+ return (mgr->matchDomain(fqdn, domain));
+}
+
+
+isc::dhcp::ParserPtr
+D2CfgMgr::createConfigParser(const std::string& config_id) {
+ // Get D2 specific context.
+ D2CfgContextPtr context = getD2CfgContext();
+
+ // Create parser instance based on element_id.
+ DhcpConfigParser* parser = NULL;
+ if ((config_id == "interface") ||
+ (config_id == "ip_address")) {
+ parser = new StringParser(config_id, context->getStringStorage());
+ } else if (config_id == "port") {
+ parser = new Uint32Parser(config_id, context->getUint32Storage());
+ } else if (config_id == "forward_ddns") {
+ parser = new DdnsDomainListMgrParser("forward_mgr",
+ context->getForwardMgr(),
+ context->getKeys());
+ } else if (config_id == "reverse_ddns") {
+ parser = new DdnsDomainListMgrParser("reverse_mgr",
+ context->getReverseMgr(),
+ context->getKeys());
+ } else if (config_id == "tsig_keys") {
+ parser = new TSIGKeyInfoListParser("tsig_key_list", context->getKeys());
+ } else {
+ isc_throw(NotImplemented,
+ "parser error: D2CfgMgr parameter not supported: "
+ << config_id);
+ }
+
+ return (isc::dhcp::ParserPtr(parser));
+}
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
+
diff --git a/src/bin/d2/d2_cfg_mgr.h b/src/bin/d2/d2_cfg_mgr.h
new file mode 100644
index 0000000..f9089cb
--- /dev/null
+++ b/src/bin/d2/d2_cfg_mgr.h
@@ -0,0 +1,175 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef D2_CFG_MGR_H
+#define D2_CFG_MGR_H
+
+#include <asiolink/io_address.h>
+#include <cc/data.h>
+#include <exceptions/exceptions.h>
+#include <d2/d_cfg_mgr.h>
+#include <d2/d2_config.h>
+
+#include <stdint.h>
+#include <string>
+
+namespace isc {
+namespace d2 {
+
+class D2CfgContext;
+/// @brief Pointer to a configuration context.
+typedef boost::shared_ptr<D2CfgContext> D2CfgContextPtr;
+
+/// @brief DHCP-DDNS Configuration Context
+///
+/// Implements the storage container for configuration context.
+/// It provides a single enclosure for the storage of configuration parameters
+/// and any other DHCP-DDNS specific information that needs to be accessible
+/// during configuration parsing as well as to the application as a whole.
+/// It is derived from the context base class, DCfgContextBase.
+class D2CfgContext : public DCfgContextBase {
+public:
+ /// @brief Constructor
+ D2CfgContext();
+
+ /// @brief Destructor
+ virtual ~D2CfgContext();
+
+ /// @brief Creates a clone of this context object.
+ ///
+ /// @return returns a pointer to the new clone.
+ virtual DCfgContextBasePtr clone() {
+ return (DCfgContextBasePtr(new D2CfgContext(*this)));
+ }
+
+ /// @brief Fetches the forward DNS domain list manager.
+ ///
+ /// @return returns a pointer to the forward manager.
+ DdnsDomainListMgrPtr getForwardMgr() {
+ return (forward_mgr_);
+ }
+
+ /// @brief Fetches the reverse DNS domain list manager.
+ ///
+ /// @return returns a pointer to the reverse manager.
+ DdnsDomainListMgrPtr getReverseMgr() {
+ return (reverse_mgr_);
+ }
+
+ /// @brief Fetches the map of TSIG keys.
+ ///
+ /// @return returns a pointer to the key map.
+ TSIGKeyInfoMapPtr getKeys() {
+ return (keys_);
+ }
+
+protected:
+ /// @brief Copy constructor for use by derivations in clone().
+ D2CfgContext(const D2CfgContext& rhs);
+
+private:
+ /// @brief Private assignment operator to avoid potential for slicing.
+ D2CfgContext& operator=(const D2CfgContext& rhs);
+
+ /// @brief Forward domain list manager.
+ DdnsDomainListMgrPtr forward_mgr_;
+
+ /// @brief Reverse domain list manager.
+ DdnsDomainListMgrPtr reverse_mgr_;
+
+ /// @brief Storage for the map of TSIGKeyInfos
+ TSIGKeyInfoMapPtr keys_;
+};
+
+/// @brief Defines a pointer for DdnsDomain instances.
+typedef boost::shared_ptr<DdnsDomainListMgr> DdnsDomainListMgrPtr;
+
+
+
+/// @brief DHCP-DDNS Configuration Manager
+///
+/// Provides the mechanisms for managing the DHCP-DDNS application's
+/// configuration. This includes services for parsing sets of configuration
+/// values, storing the parsed information in its converted form,
+/// and retrieving the information on demand.
+class D2CfgMgr : public DCfgMgrBase {
+public:
+ /// @brief Constructor
+ D2CfgMgr();
+
+ /// @brief Destructor
+ virtual ~D2CfgMgr();
+
+ /// @brief Convenience method that returns the D2 configuration context.
+ ///
+ /// @return returns a pointer to the configuration context.
+ D2CfgContextPtr getD2CfgContext() {
+ return (boost::dynamic_pointer_cast<D2CfgContext>(getContext()));
+ }
+
+ /// @brief Matches a given FQDN to a forward domain.
+ ///
+ /// This calls the matchDomain method of the forward domain manager to
+ /// match the given FQDN to a forward domain.
+ ///
+ /// @param fqdn is the name for which to look.
+ /// @param domain receives the matching domain. Note that it will be reset
+ /// upon entry and only set if a match is subsequently found.
+ ///
+ /// @return returns true if a match is found, false otherwise.
+ /// @throw throws D2CfgError if given an invalid fqdn.
+ bool matchForward(const std::string& fqdn, DdnsDomainPtr &domain);
+
+ /// @brief Matches a given FQDN to a reverse domain.
+ ///
+ /// This calls the matchDomain method of the reverse domain manager to
+ /// match the given FQDN to a forward domain.
+ ///
+ /// @param fqdn is the name for which to look.
+ /// @param domain receives the matching domain. Note that it will be reset
+ /// upon entry and only set if a match is subsequently found.
+ ///
+ /// @return returns true if a match is found, false otherwise.
+ /// @throw throws D2CfgError if given an invalid fqdn.
+ bool matchReverse(const std::string& fqdn, DdnsDomainPtr &domain);
+
+protected:
+ /// @brief Given an element_id returns an instance of the appropriate
+ /// parser.
+ ///
+ /// It is responsible for top-level or outermost DHCP-DDNS configuration
+ /// elements (see dhcp-ddns.spec):
+ /// 1. interface
+ /// 2. ip_address
+ /// 3. port
+ /// 4. forward_ddns
+ /// 5. reverse_ddns
+ ///
+ /// @param element_id is the string name of the element as it will appear
+ /// in the configuration set.
+ ///
+ /// @return returns a ParserPtr to the parser instance.
+ /// @throw throws DCfgMgrBaseError if an error occurs.
+ virtual isc::dhcp::ParserPtr
+ createConfigParser(const std::string& element_id);
+};
+
+/// @brief Defines a shared pointer to D2CfgMgr.
+typedef boost::shared_ptr<D2CfgMgr> D2CfgMgrPtr;
+
+
+}; // end of isc::d2 namespace
+}; // end of isc namespace
+
+#endif // D2_CFG_MGR_H
diff --git a/src/bin/d2/d2_config.cc b/src/bin/d2/d2_config.cc
new file mode 100644
index 0000000..fe5ad0b
--- /dev/null
+++ b/src/bin/d2/d2_config.cc
@@ -0,0 +1,605 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <d2/d2_log.h>
+#include <d2/d2_cfg_mgr.h>
+#include <dhcpsrv/dhcp_parsers.h>
+#include <exceptions/exceptions.h>
+#include <asiolink/io_error.h>
+
+#include <boost/foreach.hpp>
+#include <boost/lexical_cast.hpp>
+
+namespace isc {
+namespace d2 {
+
+// *********************** TSIGKeyInfo *************************
+
+TSIGKeyInfo::TSIGKeyInfo(const std::string& name, const std::string& algorithm,
+ const std::string& secret)
+ :name_(name), algorithm_(algorithm), secret_(secret) {
+}
+
+TSIGKeyInfo::~TSIGKeyInfo() {
+}
+
+
+// *********************** DnsServerInfo *************************
+
+const char* DnsServerInfo::EMPTY_IP_STR = "0.0.0.0";
+
+DnsServerInfo::DnsServerInfo(const std::string& hostname,
+ isc::asiolink::IOAddress ip_address, uint32_t port,
+ bool enabled)
+ :hostname_(hostname), ip_address_(ip_address), port_(port),
+ enabled_(enabled) {
+}
+
+DnsServerInfo::~DnsServerInfo() {
+}
+
+// *********************** DdnsDomain *************************
+
+DdnsDomain::DdnsDomain(const std::string& name, const std::string& key_name,
+ DnsServerInfoStoragePtr servers)
+ : name_(name), key_name_(key_name), servers_(servers) {
+}
+
+DdnsDomain::~DdnsDomain() {
+}
+
+// *********************** DdnsDomainLstMgr *************************
+
+const char* DdnsDomainListMgr::wildcard_domain_name_ = "*";
+
+DdnsDomainListMgr::DdnsDomainListMgr(const std::string& name) : name_(name),
+ domains_(new DdnsDomainMap()) {
+}
+
+
+DdnsDomainListMgr::~DdnsDomainListMgr () {
+}
+
+void
+DdnsDomainListMgr::setDomains(DdnsDomainMapPtr domains) {
+ if (!domains) {
+ isc_throw(D2CfgError,
+ "DdnsDomainListMgr::setDomains: Domain list may not be null");
+ }
+
+ domains_ = domains;
+
+ // Look for the wild card domain. If present, set the member variable
+ // to remember it. This saves us from having to look for it every time
+ // we attempt a match.
+ DdnsDomainMap::iterator gotit = domains_->find(wildcard_domain_name_);
+ if (gotit != domains_->end()) {
+ wildcard_domain_ = gotit->second;
+ }
+}
+
+bool
+DdnsDomainListMgr::matchDomain(const std::string& fqdn, DdnsDomainPtr& domain) {
+ // Clear the return parameter.
+ domain.reset();
+
+ // First check the case of one domain to rule them all.
+ if ((size() == 1) && (wildcard_domain_)) {
+ domain = wildcard_domain_;
+ return (true);
+ }
+
+ // Start with the longest version of the fqdn and search the list.
+ // Continue looking for shorter versions of fqdn so long as no match is
+ // found.
+ // @todo This can surely be optimized, time permitting.
+ std::string match_name = fqdn;
+ std::size_t start_pos = 0;
+ while (start_pos != std::string::npos) {
+ match_name = match_name.substr(start_pos, std::string::npos);
+ DdnsDomainMap::iterator gotit = domains_->find(match_name);
+ if (gotit != domains_->end()) {
+ domain = gotit->second;
+ return (true);
+ }
+
+ start_pos = match_name.find_first_of(".");
+ if (start_pos != std::string::npos) {
+ ++start_pos;
+ }
+ }
+
+ // There's no match. If they specified a wild card domain use it
+ // otherwise there's no domain for this entry.
+ if (wildcard_domain_) {
+ domain = wildcard_domain_;
+ return (true);
+ }
+
+ LOG_WARN(dctl_logger, DHCP_DDNS_NO_MATCH).arg(fqdn);
+ return (false);
+}
+
+// *************************** PARSERS ***********************************
+
+// *********************** TSIGKeyInfoParser *************************
+
+TSIGKeyInfoParser::TSIGKeyInfoParser(const std::string& entry_name,
+ TSIGKeyInfoMapPtr keys)
+ : entry_name_(entry_name), keys_(keys), local_scalars_() {
+ if (!keys_) {
+ isc_throw(D2CfgError, "TSIGKeyInfoParser ctor:"
+ " key storage cannot be null");
+ }
+}
+
+TSIGKeyInfoParser::~TSIGKeyInfoParser() {
+}
+
+void
+TSIGKeyInfoParser::build(isc::data::ConstElementPtr key_config) {
+ isc::dhcp::ConfigPair config_pair;
+ // For each element in the key configuration:
+ // 1. Create a parser for the element.
+ // 2. Invoke the parser's build method passing in the element's
+ // configuration.
+ // 3. Invoke the parser's commit method to store the element's parsed
+ // data to the parser's local storage.
+ BOOST_FOREACH (config_pair, key_config->mapValue()) {
+ isc::dhcp::ParserPtr parser(createConfigParser(config_pair.first));
+ parser->build(config_pair.second);
+ parser->commit();
+ }
+}
+
+isc::dhcp::ParserPtr
+TSIGKeyInfoParser::createConfigParser(const std::string& config_id) {
+ DhcpConfigParser* parser = NULL;
+ // Based on the configuration id of the element, create the appropriate
+ // parser. Scalars are set to use the parser's local scalar storage.
+ if ((config_id == "name") ||
+ (config_id == "algorithm") ||
+ (config_id == "secret")) {
+ parser = new isc::dhcp::StringParser(config_id,
+ local_scalars_.getStringStorage());
+ } else {
+ isc_throw(NotImplemented,
+ "parser error: TSIGKeyInfo parameter not supported: "
+ << config_id);
+ }
+
+ // Return the new parser instance.
+ return (isc::dhcp::ParserPtr(parser));
+}
+
+void
+TSIGKeyInfoParser::commit() {
+ std::string name;
+ std::string algorithm;
+ std::string secret;
+
+ // Fetch the key configuration's parsed scalar values from parser's
+ // local storage.
+ local_scalars_.getParam("name", name);
+ local_scalars_.getParam("algorithm", algorithm);
+ local_scalars_.getParam("secret", secret);
+
+ // @todo Validation here is very superficial. This will expand as TSIG
+ // Key use is more fully implemented.
+
+ // Name cannot be blank.
+ if (name.empty()) {
+ isc_throw(D2CfgError, "TSIG Key Info must specify name");
+ }
+
+ // Algorithme cannot be blank.
+ if (algorithm.empty()) {
+ isc_throw(D2CfgError, "TSIG Key Info must specify algorithm");
+ }
+
+ // Secret cannot be blank.
+ if (secret.empty()) {
+ isc_throw(D2CfgError, "TSIG Key Info must specify secret");
+ }
+
+ // Currently, the premise is that key storage is always empty prior to
+ // parsing so we are always adding keys never replacing them. Duplicates
+ // are not allowed and should be flagged as a configuration error.
+ if (keys_->find(name) != keys_->end()) {
+ isc_throw(D2CfgError, "Duplicate TSIG key specified:" << name);
+ }
+
+ TSIGKeyInfoPtr key_info(new TSIGKeyInfo(name, algorithm, secret));
+
+ // Add the new TSIGKeyInfo to the key storage.
+ (*keys_)[name]=key_info;
+}
+
+// *********************** TSIGKeyInfoListParser *************************
+
+TSIGKeyInfoListParser::TSIGKeyInfoListParser(const std::string& list_name,
+ TSIGKeyInfoMapPtr keys)
+ :list_name_(list_name), keys_(keys), parsers_() {
+ if (!keys_) {
+ isc_throw(D2CfgError, "TSIGKeyInfoListParser ctor:"
+ " key storage cannot be null");
+ }
+}
+
+TSIGKeyInfoListParser::~TSIGKeyInfoListParser(){
+}
+
+void
+TSIGKeyInfoListParser::
+build(isc::data::ConstElementPtr key_list){
+ int i = 0;
+ isc::data::ConstElementPtr key_config;
+ // For each key element in the key list:
+ // 1. Create a parser for the key element.
+ // 2. Invoke the parser's build method passing in the key's
+ // configuration.
+ // 3. Add the parser to a local collection of parsers.
+ BOOST_FOREACH(key_config, key_list->listValue()) {
+ // Create a name for the parser based on its position in the list.
+ std::string entry_name = boost::lexical_cast<std::string>(i++);
+ isc::dhcp::ParserPtr parser(new TSIGKeyInfoParser(entry_name,
+ keys_));
+ parser->build(key_config);
+ parsers_.push_back(parser);
+ }
+}
+
+void
+TSIGKeyInfoListParser::commit() {
+ // Invoke commit on each server parser. This will cause each one to
+ // create it's server instance and commit it to storage.
+ BOOST_FOREACH(isc::dhcp::ParserPtr parser, parsers_) {
+ parser->commit();
+ }
+}
+
+// *********************** DnsServerInfoParser *************************
+
+DnsServerInfoParser::DnsServerInfoParser(const std::string& entry_name,
+ DnsServerInfoStoragePtr servers)
+ : entry_name_(entry_name), servers_(servers), local_scalars_() {
+ if (!servers_) {
+ isc_throw(D2CfgError, "DnsServerInfoParser ctor:"
+ " server storage cannot be null");
+ }
+}
+
+DnsServerInfoParser::~DnsServerInfoParser() {
+}
+
+void
+DnsServerInfoParser::build(isc::data::ConstElementPtr server_config) {
+ isc::dhcp::ConfigPair config_pair;
+ // For each element in the server configuration:
+ // 1. Create a parser for the element.
+ // 2. Invoke the parser's build method passing in the element's
+ // configuration.
+ // 3. Invoke the parser's commit method to store the element's parsed
+ // data to the parser's local storage.
+ BOOST_FOREACH (config_pair, server_config->mapValue()) {
+ isc::dhcp::ParserPtr parser(createConfigParser(config_pair.first));
+ parser->build(config_pair.second);
+ parser->commit();
+ }
+
+}
+
+isc::dhcp::ParserPtr
+DnsServerInfoParser::createConfigParser(const std::string& config_id) {
+ DhcpConfigParser* parser = NULL;
+ // Based on the configuration id of the element, create the appropriate
+ // parser. Scalars are set to use the parser's local scalar storage.
+ if ((config_id == "hostname") ||
+ (config_id == "ip_address")) {
+ parser = new isc::dhcp::StringParser(config_id,
+ local_scalars_.getStringStorage());
+ } else if (config_id == "port") {
+ parser = new isc::dhcp::Uint32Parser(config_id,
+ local_scalars_.getUint32Storage());
+ } else {
+ isc_throw(NotImplemented,
+ "parser error: DnsServerInfo parameter not supported: "
+ << config_id);
+ }
+
+ // Return the new parser instance.
+ return (isc::dhcp::ParserPtr(parser));
+}
+
+void
+DnsServerInfoParser::commit() {
+ std::string hostname;
+ std::string ip_address;
+ uint32_t port = DnsServerInfo::STANDARD_DNS_PORT;
+
+ // Fetch the server configuration's parsed scalar values from parser's
+ // local storage.
+ local_scalars_.getParam("hostname", hostname, DCfgContextBase::OPTIONAL);
+ local_scalars_.getParam("ip_address", ip_address,
+ DCfgContextBase::OPTIONAL);
+ local_scalars_.getParam("port", port, DCfgContextBase::OPTIONAL);
+
+ // The configuration must specify one or the other.
+ if (hostname.empty() == ip_address.empty()) {
+ isc_throw(D2CfgError, "Dns Server must specify one or the other"
+ " of hostname and IP address");
+ }
+
+ DnsServerInfoPtr serverInfo;
+ if (!hostname.empty()) {
+ // When hostname is specified, create a valid, blank IOAddress and
+ // then create the DnsServerInfo.
+ isc::asiolink::IOAddress io_addr(DnsServerInfo::EMPTY_IP_STR);
+ serverInfo.reset(new DnsServerInfo(hostname, io_addr, port));
+ } else {
+ try {
+ // Create an IOAddress from the IP address string given and then
+ // create the DnsServerInfo.
+ isc::asiolink::IOAddress io_addr(ip_address);
+ serverInfo.reset(new DnsServerInfo(hostname, io_addr, port));
+ } catch (const isc::asiolink::IOError& ex) {
+ isc_throw(D2CfgError, "Invalid IP address:" << ip_address);
+ }
+ }
+
+ // Add the new DnsServerInfo to the server storage.
+ servers_->push_back(serverInfo);
+}
+
+// *********************** DnsServerInfoListParser *************************
+
+DnsServerInfoListParser::DnsServerInfoListParser(const std::string& list_name,
+ DnsServerInfoStoragePtr servers)
+ :list_name_(list_name), servers_(servers), parsers_() {
+ if (!servers_) {
+ isc_throw(D2CfgError, "DdnsServerInfoListParser ctor:"
+ " server storage cannot be null");
+ }
+}
+
+DnsServerInfoListParser::~DnsServerInfoListParser(){
+}
+
+void
+DnsServerInfoListParser::
+build(isc::data::ConstElementPtr server_list){
+ int i = 0;
+ isc::data::ConstElementPtr server_config;
+ // For each server element in the server list:
+ // 1. Create a parser for the server element.
+ // 2. Invoke the parser's build method passing in the server's
+ // configuration.
+ // 3. Add the parser to a local collection of parsers.
+ BOOST_FOREACH(server_config, server_list->listValue()) {
+ // Create a name for the parser based on its position in the list.
+ std::string entry_name = boost::lexical_cast<std::string>(i++);
+ isc::dhcp::ParserPtr parser(new DnsServerInfoParser(entry_name,
+ servers_));
+ parser->build(server_config);
+ parsers_.push_back(parser);
+ }
+}
+
+void
+DnsServerInfoListParser::commit() {
+ // Domains must have at least one server.
+ if (parsers_.size() == 0) {
+ isc_throw (D2CfgError, "Server List must contain at least one server");
+ }
+
+ // Invoke commit on each server parser. This will cause each one to
+ // create it's server instance and commit it to storage.
+ BOOST_FOREACH(isc::dhcp::ParserPtr parser, parsers_) {
+ parser->commit();
+ }
+}
+
+// *********************** DdnsDomainParser *************************
+
+DdnsDomainParser::DdnsDomainParser(const std::string& entry_name,
+ DdnsDomainMapPtr domains,
+ TSIGKeyInfoMapPtr keys)
+ : entry_name_(entry_name), domains_(domains), keys_(keys),
+ local_servers_(new DnsServerInfoStorage()), local_scalars_() {
+ if (!domains_) {
+ isc_throw(D2CfgError,
+ "DdnsDomainParser ctor, domain storage cannot be null");
+ }
+}
+
+
+DdnsDomainParser::~DdnsDomainParser() {
+}
+
+void
+DdnsDomainParser::build(isc::data::ConstElementPtr domain_config) {
+ // For each element in the domain configuration:
+ // 1. Create a parser for the element.
+ // 2. Invoke the parser's build method passing in the element's
+ // configuration.
+ // 3. Invoke the parser's commit method to store the element's parsed
+ // data to the parser's local storage.
+ isc::dhcp::ConfigPair config_pair;
+ BOOST_FOREACH(config_pair, domain_config->mapValue()) {
+ isc::dhcp::ParserPtr parser(createConfigParser(config_pair.first));
+ parser->build(config_pair.second);
+ parser->commit();
+ }
+}
+
+isc::dhcp::ParserPtr
+DdnsDomainParser::createConfigParser(const std::string& config_id) {
+ DhcpConfigParser* parser = NULL;
+ // Based on the configuration id of the element, create the appropriate
+ // parser. Scalars are set to use the parser's local scalar storage.
+ if ((config_id == "name") ||
+ (config_id == "key_name")) {
+ parser = new isc::dhcp::StringParser(config_id,
+ local_scalars_.getStringStorage());
+ } else if (config_id == "dns_servers") {
+ // Server list parser is given in our local server storage. It will pass
+ // this down to its server parsers and is where they will write their
+ // server instances upon commit.
+ parser = new DnsServerInfoListParser(config_id, local_servers_);
+ } else {
+ isc_throw(NotImplemented,
+ "parser error: DdnsDomain parameter not supported: "
+ << config_id);
+ }
+
+ // Return the new domain parser instance.
+ return (isc::dhcp::ParserPtr(parser));
+}
+
+void
+DdnsDomainParser::commit() {
+ std::string name;
+ std::string key_name;
+
+ // Domain name is not optional. The get will throw if its not there.
+ local_scalars_.getParam("name", name);
+
+ // Blank domain names are not allowed.
+ if (name.empty()) {
+ isc_throw(D2CfgError, "Domain name cannot be blank");
+ }
+
+ // Currently, the premise is that domain storage is always empty
+ // prior to parsing so always adding domains never replacing them.
+ // Duplicates are not allowed and should be flagged as a configuration
+ // error.
+ if (domains_->find(name) != domains_->end()) {
+ isc_throw(D2CfgError, "Duplicate domain specified:" << name);
+ }
+
+ // Key name is optional. If it is not blank, then validate it against
+ // the defined list of keys.
+ local_scalars_.getParam("key_name", key_name, DCfgContextBase::OPTIONAL);
+ if (!key_name.empty()) {
+ if ((!keys_) || (keys_->find(key_name) == keys_->end())) {
+ isc_throw(D2CfgError, "DdnsDomain :" << name <<
+ " specifies and undefined key:" << key_name);
+ }
+ }
+
+ // Instantiate the new domain and add it to domain storage.
+ DdnsDomainPtr domain(new DdnsDomain(name, key_name, local_servers_));
+
+ // Add the new domain to the domain storage.
+ (*domains_)[name]=domain;
+}
+
+// *********************** DdnsDomainListParser *************************
+
+DdnsDomainListParser::DdnsDomainListParser(const std::string& list_name,
+ DdnsDomainMapPtr domains,
+ TSIGKeyInfoMapPtr keys)
+ :list_name_(list_name), domains_(domains), keys_(keys), parsers_() {
+ if (!domains_) {
+ isc_throw(D2CfgError, "DdnsDomainListParser ctor:"
+ " domain storage cannot be null");
+ }
+}
+
+DdnsDomainListParser::~DdnsDomainListParser(){
+}
+
+void
+DdnsDomainListParser::
+build(isc::data::ConstElementPtr domain_list){
+ // For each domain element in the domain list:
+ // 1. Create a parser for the domain element.
+ // 2. Invoke the parser's build method passing in the domain's
+ // configuration.
+ // 3. Add the parser to the local collection of parsers.
+ int i = 0;
+ isc::data::ConstElementPtr domain_config;
+ BOOST_FOREACH(domain_config, domain_list->listValue()) {
+ std::string entry_name = boost::lexical_cast<std::string>(i++);
+ isc::dhcp::ParserPtr parser(new DdnsDomainParser(entry_name,
+ domains_, keys_));
+ parser->build(domain_config);
+ parsers_.push_back(parser);
+ }
+}
+
+void
+DdnsDomainListParser::commit() {
+ // Invoke commit on each server parser. This will cause each one to
+ // create it's server instance and commit it to storage.
+ BOOST_FOREACH(isc::dhcp::ParserPtr parser, parsers_) {
+ parser->commit();
+ }
+}
+
+
+// *********************** DdnsDomainListMgrParser *************************
+
+DdnsDomainListMgrParser::DdnsDomainListMgrParser(const std::string& entry_name,
+ DdnsDomainListMgrPtr mgr, TSIGKeyInfoMapPtr keys)
+ : entry_name_(entry_name), mgr_(mgr), keys_(keys),
+ local_domains_(new DdnsDomainMap()), local_scalars_() {
+}
+
+
+DdnsDomainListMgrParser::~DdnsDomainListMgrParser() {
+}
+
+void
+DdnsDomainListMgrParser::build(isc::data::ConstElementPtr domain_config) {
+ // For each element in the domain manager configuration:
+ // 1. Create a parser for the element.
+ // 2. Invoke the parser's build method passing in the element's
+ // configuration.
+ // 3. Invoke the parser's commit method to store the element's parsed
+ // data to the parser's local storage.
+ isc::dhcp::ConfigPair config_pair;
+ BOOST_FOREACH(config_pair, domain_config->mapValue()) {
+ isc::dhcp::ParserPtr parser(createConfigParser(config_pair.first));
+ parser->build(config_pair.second);
+ parser->commit();
+ }
+}
+
+isc::dhcp::ParserPtr
+DdnsDomainListMgrParser::createConfigParser(const std::string& config_id) {
+ DhcpConfigParser* parser = NULL;
+ if (config_id == "ddns_domains") {
+ // Domain list parser is given our local domain storage. It will pass
+ // this down to its domain parsers and is where they will write their
+ // domain instances upon commit.
+ parser = new DdnsDomainListParser(config_id, local_domains_, keys_);
+ } else {
+ isc_throw(NotImplemented, "parser error: "
+ "DdnsDomainListMgr parameter not supported: " << config_id);
+ }
+
+ // Return the new domain parser instance.
+ return (isc::dhcp::ParserPtr(parser));
+}
+
+void
+DdnsDomainListMgrParser::commit() {
+ // Add the new domain to the domain storage.
+ mgr_->setDomains(local_domains_);
+}
+
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
diff --git a/src/bin/d2/d2_config.h b/src/bin/d2/d2_config.h
new file mode 100644
index 0000000..7350291
--- /dev/null
+++ b/src/bin/d2/d2_config.h
@@ -0,0 +1,941 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef D2_CONFIG_H
+#define D2_CONFIG_H
+
+#include <asiolink/io_address.h>
+#include <cc/data.h>
+#include <d2/d_cfg_mgr.h>
+#include <dhcpsrv/dhcp_parsers.h>
+#include <exceptions/exceptions.h>
+
+#include <boost/foreach.hpp>
+
+#include <stdint.h>
+#include <string>
+
+namespace isc {
+namespace d2 {
+
+/// @file d2_config.h
+/// @brief A collection of classes for housing and parsing the application
+/// configuration necessary for the DHCP-DDNS application (aka D2).
+///
+/// This file contains the class declarations for the class hierarchy created
+/// from the D2 configuration and the parser classes used to create it.
+/// The application configuration consists of a set of scalar parameters,
+/// a list of TSIG keys, and two managed lists of domains: one list for
+/// forward domains and one list for reverse domains.
+///
+/// The key list consists of one or more TSIG keys, each entry described by
+/// a name, the algorithm method name, and its secret key component.
+///
+/// @todo NOTE that TSIG configuration parsing is functional, the use of
+/// TSIG Keys during the actual DNS update transactions is not. This will be
+/// implemented in a future release.
+///
+/// Each managed domain list consists of a list one or more domains and is
+/// represented by the class DdnsDomainListMgr.
+///
+/// Each domain consists of a set of scalars parameters and a list of DNS
+/// servers which support that domain. Among its scalars, is key_name, which
+/// is the name of the TSIG Key to use for with this domain. This value should
+/// map to one of the TSIG Keys in the key list. Domains are represented by
+/// the class, DdnsDomain.
+///
+/// Each server consists of a set of scalars used to describe the server such
+/// that the application can carry out DNS update exchanges with it. Servers
+/// are represented by the class, DnsServerInfo.
+///
+/// The configuration specification for use with BIND10 is detailed in the file
+/// dhcp-ddns.spec.
+///
+/// The parsing class hierarchy reflects this same scheme. Working top down:
+///
+/// A DdnsDomainListMgrParser parses a managed domain list entry. It handles
+/// any scalars which belong to the manager as well as creating and invoking a
+/// DdnsDomainListParser to parse its list of domain entries.
+///
+/// A DdnsDomainLiatParser creates and invokes DdnsDomainListParser for each
+/// domain entry in its list.
+///
+/// A DdnsDomainParser handles the scalars which belong to the domain as well as
+/// creating and invoking a DnsSeverInfoListParser to parse its list of server
+/// entries.
+///
+/// A DnsServerInfoListParser creates and invokes a DnsServerInfoParser for
+/// each server entry in its list.
+///
+/// A DdnsServerInfoParser handles the scalars which belong to the server.
+/// The following is sample configuration in JSON form with extra spacing
+/// for clarity:
+///
+/// @code
+/// {
+/// "interface" : "eth1" ,
+/// "ip_address" : "192.168.1.33" ,
+/// "port" : 88 ,
+/// "tsig_keys":
+//// [
+/// {
+/// "name": "d2_key.tmark.org" ,
+/// "algorithm": "md5" ,
+/// "secret": "0123456989"
+/// }
+/// ],
+/// "forward_ddns" :
+/// {
+/// "ddns_domains":
+/// [
+/// {
+/// "name": "tmark.org." ,
+/// "key_name": "d2_key.tmark.org" ,
+/// "dns_servers" :
+/// [
+/// { "hostname": "fserver.tmark.org" },
+/// { "hostname": "f2server.tmark.org" }
+/// ]
+/// },
+/// {
+/// "name": "pub.tmark.org." ,
+/// "key_name": "d2_key.tmark.org" ,
+/// "dns_servers" :
+/// [
+/// { "hostname": "f3server.tmark.org" }
+/// ]
+/// }
+/// ]
+/// },
+/// "reverse_ddns" :
+/// {
+/// "ddns_domains":
+/// [
+/// {
+/// "name": " 0.168.192.in.addr.arpa." ,
+/// "key_name": "d2_key.tmark.org" ,
+/// "dns_servers" :
+/// [
+/// { "ip_address": "127.0.0.101" , "port": 100 }
+/// ]
+/// }
+/// ]
+/// }
+/// }
+/// @endcode
+
+/// @brief Exception thrown when the error during configuration handling
+/// occurs.
+class D2CfgError : public isc::Exception {
+public:
+ D2CfgError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Represents a TSIG Key.
+///
+/// Currently, this is simple storage class containing the basic attributes of
+/// a TSIG Key. It is intended primarily as a reference for working with
+/// actual keys and may eventually be replaced by isc::dns::TSIGKey. TSIG Key
+/// functionality at this stage is strictly limited to configuration parsing.
+/// @todo full functionality for using TSIG during DNS updates will be added
+/// in a future release.
+class TSIGKeyInfo {
+public:
+
+ /// @brief Constructor
+ ///
+ /// @param name the unique label used to identify this key
+ /// @param algorithm the name of the encryption alogirthm this key uses.
+ /// (@todo This will be a fixed list of choices)
+ /// @param secret the secret component of this key
+ TSIGKeyInfo(const std::string& name, const std::string& algorithm,
+ const std::string& secret);
+
+ /// @brief Destructor
+ virtual ~TSIGKeyInfo();
+
+ /// @brief Getter which returns the key's name.
+ ///
+ /// @return returns the name as as std::string.
+ const std::string getName() const {
+ return (name_);
+ }
+
+ /// @brief Getter which returns the key's algorithm.
+ ///
+ /// @return returns the algorithm as as std::string.
+ const std::string getAlgorithm() const {
+ return (algorithm_);
+ }
+
+ /// @brief Getter which returns the key's secret.
+ ///
+ /// @return returns the secret as as std::string.
+ const std::string getSecret() const {
+ return (secret_);
+ }
+
+private:
+ /// @brief The name of the key.
+ ///
+ /// This value is the unique identifeir thay domains use to
+ /// to specify which TSIG key they need.
+ std::string name_;
+
+ /// @brief The algorithm that should be used for this key.
+ std::string algorithm_;
+
+ /// @brief The secret value component of this key.
+ std::string secret_;
+};
+
+/// @brief Defines a pointer for TSIGKeyInfo instances.
+typedef boost::shared_ptr<TSIGKeyInfo> TSIGKeyInfoPtr;
+
+/// @brief Defines a map of TSIGKeyInfos, keyed by the name.
+typedef std::map<std::string, TSIGKeyInfoPtr> TSIGKeyInfoMap;
+
+/// @brief Defines a iterator pairing of name and TSIGKeyInfo
+typedef std::pair<std::string, TSIGKeyInfoPtr> TSIGKeyInfoMapPair;
+
+/// @brief Defines a pointer to map of TSIGkeyInfos
+typedef boost::shared_ptr<TSIGKeyInfoMap> TSIGKeyInfoMapPtr;
+
+
+/// @brief Represents a specific DNS Server.
+/// It provides information about the server's network identity and typically
+/// belongs to a list of servers supporting DNS for a given domain. It will
+/// be used to establish communications with the server to carry out DNS
+/// updates.
+class DnsServerInfo {
+public:
+
+ /// @brief defines DNS standard port value
+ static const uint32_t STANDARD_DNS_PORT = 53;
+
+ /// @brief defines an "empty" string version of an ip address.
+ static const char* EMPTY_IP_STR;
+
+
+ /// @brief Constructor
+ ///
+ /// @param hostname is the resolvable name of the server. If not blank,
+ /// then the server address should be resolved at runtime.
+ /// @param ip_address is the static IP address of the server. If hostname
+ /// is blank, then this address should be used to connect to the server.
+ /// @param port is the port number on which the server listens.
+ /// primarily meant for testing purposes. Normally, DNS traffic is on
+ /// is port 53. (NOTE the constructing code is responsible for setting
+ /// the default.)
+ /// @param enabled is a flag that indicates whether this server is
+ /// enabled for use. It defaults to true.
+ DnsServerInfo(const std::string& hostname,
+ isc::asiolink::IOAddress ip_address,
+ uint32_t port = STANDARD_DNS_PORT,
+ bool enabled=true);
+
+ /// @brief Destructor
+ virtual ~DnsServerInfo();
+
+ /// @brief Getter which returns the server's hostname.
+ ///
+ /// @return returns the hostname as as std::string.
+ const std::string getHostname() const {
+ return (hostname_);
+ }
+
+ /// @brief Getter which returns the server's port number.
+ ///
+ /// @return returns the port number as a unsigned integer.
+ uint32_t getPort() const {
+ return (port_);
+ }
+
+ /// @brief Getter which returns the server's ip_address.
+ ///
+ /// @return returns the address as an IOAddress reference.
+ const isc::asiolink::IOAddress& getIpAddress() const {
+ return (ip_address_);
+ }
+
+ /// @brief Convenience method which returns whether or not the
+ /// server is enabled.
+ ///
+ /// @return returns true if the server is enabled, false otherwise.
+ bool isEnabled() const {
+ return (enabled_);
+ }
+
+ /// @brief Sets the server's enabled flag to true.
+ void enable() {
+ enabled_ = true;
+ }
+
+ /// @brief Sets the server's enabled flag to false.
+ void disable() {
+ enabled_ = false;
+ }
+
+
+private:
+ /// @brief The resolvable name of the server. If not blank, then the
+ /// server's IP address should be dynamically resolved at runtime.
+ std::string hostname_;
+
+ /// @brief The static IP address of the server. When hostname is blank,
+ /// then this address should be used to connect to the server.
+ isc::asiolink::IOAddress ip_address_;
+
+ /// @brief The port number on which the server listens for DNS traffic.
+ uint32_t port_;
+
+ /// @param enabled is a flag that indicates whether this server is
+ /// enabled for use. It defaults to true.
+ bool enabled_;
+};
+
+/// @brief Defines a pointer for DnsServerInfo instances.
+typedef boost::shared_ptr<DnsServerInfo> DnsServerInfoPtr;
+
+/// @brief Defines a storage container for DnsServerInfo pointers.
+typedef std::vector<DnsServerInfoPtr> DnsServerInfoStorage;
+
+/// @brief Defines a pointer to DnsServerInfo storage containers.
+typedef boost::shared_ptr<DnsServerInfoStorage> DnsServerInfoStoragePtr;
+
+
+/// @brief Represents a DNS domain that is may be updated dynamically.
+/// This class specifies a DNS domain and the list of DNS servers that support
+/// it. It's primary use is to map a domain to the DNS server(s) responsible
+/// for it.
+/// @todo Currently the name entry for a domain is just an std::string. It
+/// may be worthwhile to change this to a dns::Name for purposes of better
+/// validation and matching capabilities.
+class DdnsDomain {
+public:
+ /// @brief Constructor
+ ///
+ /// @param name is the domain name of the domain.
+ /// @param key_name is the TSIG key name for use with this domain.
+ /// @param servers is the list of server(s) supporting this domain.
+ DdnsDomain(const std::string& name, const std::string& key_name,
+ DnsServerInfoStoragePtr servers);
+
+ /// @brief Destructor
+ virtual ~DdnsDomain();
+
+ /// @brief Getter which returns the domain's name.
+ ///
+ /// @return returns the name in an std::string.
+ const std::string getName() const {
+ return (name_);
+ }
+
+ /// @brief Getter which returns the domain's TSIG key name.
+ ///
+ /// @return returns the key name in an std::string.
+ const std::string getKeyName() const {
+ return (key_name_);
+ }
+
+ /// @brief Getter which returns the domain's list of servers.
+ ///
+ /// @return returns the pointer to the server storage.
+ const DnsServerInfoStoragePtr& getServers() {
+ return (servers_);
+ }
+
+private:
+ /// @brief The domain name of the domain.
+ std::string name_;
+
+ /// @brief The name of the TSIG key for use with this domain.
+ std::string key_name_;
+
+ /// @brief The list of server(s) supporting this domain.
+ DnsServerInfoStoragePtr servers_;
+};
+
+/// @brief Defines a pointer for DdnsDomain instances.
+typedef boost::shared_ptr<DdnsDomain> DdnsDomainPtr;
+
+/// @brief Defines a map of DdnsDomains, keyed by the domain name.
+typedef std::map<std::string, DdnsDomainPtr> DdnsDomainMap;
+
+/// @brief Defines a iterator pairing domain name and DdnsDomain
+typedef std::pair<std::string, DdnsDomainPtr> DdnsDomainMapPair;
+
+/// @brief Defines a pointer to DdnsDomain storage containers.
+typedef boost::shared_ptr<DdnsDomainMap> DdnsDomainMapPtr;
+
+/// @brief Provides storage for and management of a list of DNS domains.
+/// In addition to housing the domain list storage, it provides domain matching
+/// services. These services are used to match a FQDN to a domain. Currently
+/// it supports a single matching service, which will return the matching
+/// domain or a wild card domain if one is specified. The wild card domain is
+/// specified as a domain whose name is "*".
+/// As matching capabilities evolve this class is expected to expand.
+class DdnsDomainListMgr {
+public:
+ /// @brief defines the domain name for denoting the wildcard domain.
+ static const char* wildcard_domain_name_;
+
+ /// @brief Constructor
+ ///
+ /// @param name is an arbitrary label assigned to this manager.
+ DdnsDomainListMgr(const std::string& name);
+
+ /// @brief Destructor
+ virtual ~DdnsDomainListMgr ();
+
+ /// @brief Matches a given name to a domain based on a longest match
+ /// scheme.
+ ///
+ /// Given a FQDN, search the list of domains, successively removing a
+ /// sub-domain from the FQDN until a match is found. If no match is found
+ /// and the wild card domain is present in the list, then return it as the
+ /// match. If the wild card domain is the only domain in the list, then
+ /// it will be returned immediately for any FQDN.
+ ///
+ /// @param fqdn is the name for which to look.
+ /// @param domain receives the matching domain. Note that it will be reset
+ /// upon entry and only set if a match is subsequently found.
+ ///
+ /// @return returns true if a match is found, false otherwise.
+ /// @todo This is a very basic match method, which expects valid FQDNs
+ /// both as input and for the DdnsDomain::getName(). Currently both are
+ /// simple strings and there is no normalization (i.e. added trailing dots
+ /// if missing).
+ virtual bool matchDomain(const std::string& fqdn, DdnsDomainPtr& domain);
+
+ /// @brief Fetches the manager's name.
+ ///
+ /// @return returns a std::string containing the name of the manager.
+ const std::string getName() const {
+ return (name_);
+ }
+
+ /// @brief Returns the number of domains in the domain list.
+ ///
+ /// @brief returns an unsigned int containing the domain count.
+ uint32_t size() const {
+ return (domains_->size());
+ }
+
+ /// @brief Fetches the wild card domain.
+ ///
+ /// @return returns a pointer reference to the domain. The pointer will
+ /// empty if the wild card domain is not present.
+ const DdnsDomainPtr& getWildcardDomain() {
+ return (wildcard_domain_);
+ }
+
+ /// @brief Fetches the domain list.
+ ///
+ /// @return returns a pointer reference to the list of domains.
+ const DdnsDomainMapPtr &getDomains() {
+ return (domains_);
+ }
+
+ /// @brief Sets the manger's domain list to the given list of domains.
+ /// This method will scan the inbound list for the wild card domain and
+ /// set the internal wild card domain pointer accordingly.
+ void setDomains(DdnsDomainMapPtr domains);
+
+private:
+ /// @brief An arbitrary label assigned to this manager.
+ std::string name_;
+
+ /// @brief Map of the domains, keyed by name.
+ DdnsDomainMapPtr domains_;
+
+ /// @brief Pointer to the wild card domain.
+ DdnsDomainPtr wildcard_domain_;
+};
+
+/// @brief Defines a pointer for DdnsDomain instances.
+typedef boost::shared_ptr<DdnsDomainListMgr> DdnsDomainListMgrPtr;
+
+/// @brief Storage container for scalar configuration parameters.
+///
+/// This class is useful for implementing parsers for more complex configuration
+/// elements (e.g. those of item type "map"). It provides a convenient way to
+/// add storage to the parser for an arbitrary number and variety of scalar
+/// configuration items (e.g. ints, bools, strings...) without explicitly adding
+/// storage for each individual type needed by the parser.
+///
+/// This class implements a concrete version of the base class by supplying a
+/// "clone" method.
+class DScalarContext : public DCfgContextBase {
+public:
+
+ /// @brief Constructor
+ DScalarContext() {
+ };
+
+ /// @brief Destructor
+ virtual ~DScalarContext() {
+ }
+
+ /// @brief Creates a clone of a DStubContext.
+ ///
+ /// @return returns a pointer to the new clone.
+ virtual DCfgContextBasePtr clone() {
+ return (DCfgContextBasePtr(new DScalarContext(*this)));
+ }
+
+protected:
+ /// @brief Copy constructor
+ DScalarContext(const DScalarContext& rhs) : DCfgContextBase(rhs) {
+ }
+
+private:
+ /// @brief Private assignment operator, not implemented.
+ DScalarContext& operator=(const DScalarContext& rhs);
+};
+
+/// @brief Defines a pointer for DScalarContext instances.
+typedef boost::shared_ptr<DScalarContext> DScalarContextPtr;
+
+/// @brief Parser for TSIGKeyInfo
+///
+/// This class parses the configuration element "tsig_key" defined in
+/// src/bin/d2/dhcp-ddns.spec and creates an instance of a TSIGKeyInfo.
+class TSIGKeyInfoParser : public isc::dhcp::DhcpConfigParser {
+public:
+ /// @brief Constructor
+ ///
+ /// @param entry_name is an arbitrary label assigned to this configuration
+ /// definition. Since servers are specified in a list this value is likely
+ /// be something akin to "key:0", set during parsing.
+ /// @param keys is a pointer to the storage area to which the parser
+ /// should commit the newly created TSIGKeyInfo instance.
+ TSIGKeyInfoParser(const std::string& entry_name, TSIGKeyInfoMapPtr keys);
+
+ /// @brief Destructor
+ virtual ~TSIGKeyInfoParser();
+
+ /// @brief Performs the actual parsing of the given "tsig_key" element.
+ ///
+ /// The results of the parsing are retained internally for use during
+ /// commit.
+ ///
+ /// @param key_config is the "tsig_key" configuration to parse
+ virtual void build(isc::data::ConstElementPtr key_config);
+
+ /// @brief Creates a parser for the given "tsig_key" member element id.
+ ///
+ /// The key elements currently supported are(see dhcp-ddns.spec):
+ /// 1. name
+ /// 2. algorithm
+ /// 3. secret
+ ///
+ /// @param config_id is the "item_name" for a specific member element of
+ /// the "tsig_key" specification.
+ ///
+ /// @return returns a pointer to newly created parser.
+ virtual isc::dhcp::ParserPtr createConfigParser(const std::string&
+ config_id);
+
+ /// @brief Instantiates a DnsServerInfo from internal data values
+ /// saves it to the storage area pointed to by servers_.
+ virtual void commit();
+
+private:
+ /// @brief Arbitrary label assigned to this parser instance.
+ /// Since servers are specified in a list this value is likely be something
+ /// akin to "key:0", set during parsing. Primarily here for diagnostics.
+ std::string entry_name_;
+
+ /// @brief Pointer to the storage area to which the parser should commit
+ /// the newly created TSIGKeyInfo instance. This is given to us as a
+ /// constructor argument by an upper level.
+ TSIGKeyInfoMapPtr keys_;
+
+ /// @brief Local storage area for scalar parameter values. Use to hold
+ /// data until time to commit.
+ DScalarContext local_scalars_;
+};
+
+/// @brief Parser for a list of TSIGKeyInfos
+///
+/// This class parses a list of "tsig_key" configuration elements.
+/// (see src/bin/d2/dhcp-ddns.spec). The TSIGKeyInfo instances are added
+/// to the given storage upon commit.
+class TSIGKeyInfoListParser : public isc::dhcp::DhcpConfigParser {
+public:
+
+ /// @brief Constructor
+ ///
+ /// @param list_name is an arbitrary label assigned to this parser instance.
+ /// @param keys is a pointer to the storage area to which the parser
+ /// should commit the newly created TSIGKeyInfo instance.
+ TSIGKeyInfoListParser(const std::string& list_name, TSIGKeyInfoMapPtr keys);
+
+ /// @brief Destructor
+ virtual ~TSIGKeyInfoListParser();
+
+ /// @brief Performs the parsing of the given list "tsig_key" elements.
+ ///
+ /// It iterates over each key entry in the list:
+ /// 1. Instantiate a TSIGKeyInfoParser for the entry
+ /// 2. Pass the element configuration to the parser's build method
+ /// 3. Add the parser instance to local storage
+ ///
+ /// The net effect is to parse all of the key entries in the list
+ /// prepping them for commit.
+ ///
+ /// @param key_list_config is the list of "tsig_key" elements to parse.
+ virtual void build(isc::data::ConstElementPtr key_list_config);
+
+ /// @brief Iterates over the internal list of TSIGKeyInfoParsers,
+ /// invoking commit on each. This causes each parser to instantiate a
+ /// TSIGKeyInfo from its internal data values and add that that key
+ /// instance to the storage area, keys_.
+ virtual void commit();
+
+private:
+ /// @brief Arbitrary label assigned to this parser instance.
+ std::string list_name_;
+
+ /// @brief Pointer to the storage area to which the parser should commit
+ /// the list of newly created TSIGKeyInfo instances. This is given to us
+ /// as a constructor argument by an upper level.
+ TSIGKeyInfoMapPtr keys_;
+
+ /// @brief Local storage of TSIGKeyInfoParser instances
+ isc::dhcp::ParserCollection parsers_;
+};
+
+/// @brief Parser for DnsServerInfo
+///
+/// This class parses the configuration element "dns_server" defined in
+/// src/bin/d2/dhcp-ddns.spec and creates an instance of a DnsServerInfo.
+class DnsServerInfoParser : public isc::dhcp::DhcpConfigParser {
+public:
+ /// @brief Constructor
+ ///
+ /// @param entry_name is an arbitrary label assigned to this configuration
+ /// definition. Since servers are specified in a list this value is likely
+ /// be something akin to "server:0", set during parsing.
+ /// @param servers is a pointer to the storage area to which the parser
+ /// should commit the newly created DnsServerInfo instance.
+ DnsServerInfoParser(const std::string& entry_name,
+ DnsServerInfoStoragePtr servers);
+
+ /// @brief Destructor
+ virtual ~DnsServerInfoParser();
+
+ /// @brief Performs the actual parsing of the given "dns_server" element.
+ /// The results of the parsing are retained internally for use during
+ /// commit.
+ ///
+ /// @param server_config is the "dns_server" configuration to parse
+ virtual void build(isc::data::ConstElementPtr server_config);
+
+ /// @brief Creates a parser for the given "dns_server" member element id.
+ ///
+ /// The server elements currently supported are(see dhcp-ddns.spec):
+ /// 1. hostname
+ /// 2. ip_address
+ /// 3. port
+ ///
+ /// @param config_id is the "item_name" for a specific member element of
+ /// the "dns_server" specification.
+ ///
+ /// @return returns a pointer to newly created parser.
+ virtual isc::dhcp::ParserPtr createConfigParser(const std::string&
+ config_id);
+
+ /// @brief Instantiates a DnsServerInfo from internal data values
+ /// saves it to the storage area pointed to by servers_.
+ virtual void commit();
+
+private:
+ /// @brief Arbitrary label assigned to this parser instance.
+ /// Since servers are specified in a list this value is likely be something
+ /// akin to "server:0", set during parsing. Primarily here for diagnostics.
+ std::string entry_name_;
+
+ /// @brief Pointer to the storage area to which the parser should commit
+ /// the newly created DnsServerInfo instance. This is given to us as a
+ /// constructor argument by an upper level.
+ DnsServerInfoStoragePtr servers_;
+
+ /// @brief Local storage area for scalar parameter values. Use to hold
+ /// data until time to commit.
+ DScalarContext local_scalars_;
+};
+
+/// @brief Parser for a list of DnsServerInfos
+///
+/// This class parses a list of "dns_server" configuration elements.
+/// (see src/bin/d2/dhcp-ddns.spec). The DnsServerInfo instances are added
+/// to the given storage upon commit.
+class DnsServerInfoListParser : public isc::dhcp::DhcpConfigParser {
+public:
+
+ /// @brief Constructor
+ ///
+ /// @param list_name is an arbitrary label assigned to this parser instance.
+ /// @param servers is a pointer to the storage area to which the parser
+ /// should commit the newly created DnsServerInfo instance.
+ DnsServerInfoListParser(const std::string& list_name,
+ DnsServerInfoStoragePtr servers);
+
+ /// @brief Destructor
+ virtual ~DnsServerInfoListParser();
+
+ /// @brief Performs the actual parsing of the given list "dns_server"
+ /// elements.
+ /// It iterates over each server entry in the list:
+ /// 1. Instantiate a DnsServerInfoParser for the entry
+ /// 2. Pass the element configuration to the parser's build method
+ /// 3. Add the parser instance to local storage
+ ///
+ /// The net effect is to parse all of the server entries in the list
+ /// prepping them for commit.
+ ///
+ /// @param server_list_config is the list of "dns_server" elements to parse.
+ virtual void build(isc::data::ConstElementPtr server_list_config);
+
+ /// @brief Iterates over the internal list of DnsServerInfoParsers,
+ /// invoking commit on each. This causes each parser to instantiate a
+ /// DnsServerInfo from its internal data values and add that that server
+ /// instance to the storage area, servers_.
+ virtual void commit();
+
+private:
+ /// @brief Arbitrary label assigned to this parser instance.
+ std::string list_name_;
+
+ /// @brief Pointer to the storage area to which the parser should commit
+ /// the list of newly created DnsServerInfo instances. This is given to us
+ /// as a constructor argument by an upper level.
+ DnsServerInfoStoragePtr servers_;
+
+ /// @brief Local storage of DnsServerInfoParser instances
+ isc::dhcp::ParserCollection parsers_;
+};
+
+/// @brief Parser for DdnsDomain
+///
+/// This class parses the configuration element "ddns_domain" defined in
+/// src/bin/d2/dhcp-ddns.spec and creates an instance of a DdnsDomain.
+class DdnsDomainParser : public isc::dhcp::DhcpConfigParser {
+public:
+ /// @brief Constructor
+ ///
+ /// @param entry_name is an arbitrary label assigned to this configuration
+ /// definition. Since domains are specified in a list this value is likely
+ /// be something akin to "forward_ddns:0", set during parsing.
+ /// @param domains is a pointer to the storage area to which the parser
+ /// @param keys is a pointer to a map of the defined TSIG keys.
+ /// should commit the newly created DdnsDomain instance.
+ DdnsDomainParser(const std::string& entry_name, DdnsDomainMapPtr domains,
+ TSIGKeyInfoMapPtr keys);
+
+ /// @brief Destructor
+ virtual ~DdnsDomainParser();
+
+ /// @brief Performs the actual parsing of the given "ddns_domain" element.
+ /// The results of the parsing are retained internally for use during
+ /// commit.
+ ///
+ /// @param domain_config is the "ddns_domain" configuration to parse
+ virtual void build(isc::data::ConstElementPtr domain_config);
+
+ /// @brief Creates a parser for the given "ddns_domain" member element id.
+ ///
+ /// The domain elements currently supported are(see dhcp-ddns.spec):
+ /// 1. name
+ /// 2. key_name
+ /// 3. dns_servers
+ ///
+ /// @param config_id is the "item_name" for a specific member element of
+ /// the "ddns_domain" specification.
+ ///
+ /// @return returns a pointer to newly created parser.
+ virtual isc::dhcp::ParserPtr createConfigParser(const std::string&
+ config_id);
+
+ /// @brief Instantiates a DdnsDomain from internal data values
+ /// saves it to the storage area pointed to by domains_.
+ virtual void commit();
+
+private:
+
+ /// @brief Arbitrary label assigned to this parser instance.
+ std::string entry_name_;
+
+ /// @brief Pointer to the storage area to which the parser should commit
+ /// the newly created DdnsDomain instance. This is given to us as a
+ /// constructor argument by an upper level.
+ DdnsDomainMapPtr domains_;
+
+ /// @brief Pointer to the map of defined TSIG keys.
+ /// This map is passed into us and contains all of the TSIG keys defined
+ /// for this configuration. It is used to validate the key name entry of
+ /// DdnsDomains that specify one.
+ TSIGKeyInfoMapPtr keys_;
+
+ /// @brief Local storage for DnsServerInfo instances. This is passed into
+ /// DnsServerInfoListParser(s), which in turn passes it into each
+ /// DnsServerInfoParser. When the DnsServerInfoParsers "commit" they add
+ /// their server instance to this storage.
+ DnsServerInfoStoragePtr local_servers_;
+
+ /// @brief Local storage area for scalar parameter values. Use to hold
+ /// data until time to commit.
+ DScalarContext local_scalars_;
+};
+
+/// @brief Parser for a list of DdnsDomains
+///
+/// This class parses a list of "ddns_domain" configuration elements.
+/// (see src/bin/d2/dhcp-ddns.spec). The DdnsDomain instances are added
+/// to the given storage upon commit.
+class DdnsDomainListParser : public isc::dhcp::DhcpConfigParser {
+public:
+
+ /// @brief Constructor
+ ///
+ /// @param list_name is an arbitrary label assigned to this parser instance.
+ /// @param domains is a pointer to the storage area to which the parser
+ /// @param keys is a pointer to a map of the defined TSIG keys.
+ /// should commit the newly created DdnsDomain instance.
+ DdnsDomainListParser(const std::string& list_name,
+ DdnsDomainMapPtr domains, TSIGKeyInfoMapPtr keys);
+
+ /// @brief Destructor
+ virtual ~DdnsDomainListParser();
+
+ /// @brief Performs the actual parsing of the given list "ddns_domain"
+ /// elements.
+ /// It iterates over each server entry in the list:
+ /// 1. Instantiate a DdnsDomainParser for the entry
+ /// 2. Pass the element configuration to the parser's build method
+ /// 3. Add the parser instance to local storage
+ ///
+ /// The net effect is to parse all of the domain entries in the list
+ /// prepping them for commit.
+ ///
+ /// @param domain_list_config is the list of "ddns_domain" elements to
+ /// parse.
+ virtual void build(isc::data::ConstElementPtr domain_list_config);
+
+ /// @brief Iterates over the internal list of DdnsDomainParsers, invoking
+ /// commit on each. This causes each parser to instantiate a DdnsDomain
+ /// from its internal data values and add that domain instance to the
+ /// storage area, domains_.
+ virtual void commit();
+
+private:
+ /// @brief Arbitrary label assigned to this parser instance.
+ std::string list_name_;
+
+ /// @brief Pointer to the storage area to which the parser should commit
+ /// the list of newly created DdnsDomain instances. This is given to us
+ /// as a constructor argument by an upper level.
+ DdnsDomainMapPtr domains_;
+
+ /// @brief Pointer to the map of defined TSIG keys.
+ /// This map is passed into us and contains all of the TSIG keys defined
+ /// for this configuration. It is used to validate the key name entry of
+ /// DdnsDomains that specify one.
+ TSIGKeyInfoMapPtr keys_;
+
+ /// @brief Local storage of DdnsDomainParser instances
+ isc::dhcp::ParserCollection parsers_;
+};
+
+/// @brief Parser for DdnsDomainListMgr
+///
+/// This class parses the configuration elements "forward_ddns" and
+/// "reverse_ddns" as defined in src/bin/d2/dhcp-ddns.spec. It populates the
+/// given DdnsDomainListMgr with parsed information upon commit. Note that
+/// unlike other parsers, this parser does NOT instantiate the final object
+/// during the commit phase, it populates it. It must pre-exist.
+class DdnsDomainListMgrParser : public isc::dhcp::DhcpConfigParser {
+public:
+ /// @brief Constructor
+ ///
+ /// @param entry_name is an arbitrary label assigned to this configuration
+ /// definition.
+ /// @param mgr is a pointer to the DdnsDomainListMgr to populate.
+ /// @param keys is a pointer to a map of the defined TSIG keys.
+ /// @throw throws D2CfgError if mgr pointer is empty.
+ DdnsDomainListMgrParser(const std::string& entry_name,
+ DdnsDomainListMgrPtr mgr, TSIGKeyInfoMapPtr keys);
+
+ /// @brief Destructor
+ virtual ~DdnsDomainListMgrParser();
+
+ /// @brief Performs the actual parsing of the given manager element.
+ /// The results of the parsing are retained internally for use during
+ /// commit.
+ ///
+ /// @param mgr_config is the manager configuration to parse
+ virtual void build(isc::data::ConstElementPtr mgr_config);
+
+ /// @brief Creates a parser for the given manager member element id.
+ ///
+ /// The manager elements currently supported are (see dhcp-ddns.spec):
+ /// 1. ddns_domains
+ ///
+ /// @param config_id is the "item_name" for a specific member element of
+ /// the manager specification.
+ ///
+ /// @return returns a pointer to newly created parser.
+ virtual isc::dhcp::ParserPtr createConfigParser(const std::string&
+ config_id);
+
+ /// @brief Populates the DdnsDomainListMgr from internal data values
+ /// set during parsing.
+ virtual void commit();
+
+private:
+ /// @brief Arbitrary label assigned to this parser instance.
+ std::string entry_name_;
+
+ /// @brief Pointer to manager instance to which the parser should commit
+ /// the parsed data. This is given to us as a constructor argument by an
+ /// upper level.
+ DdnsDomainListMgrPtr mgr_;
+
+ /// @brief Pointer to the map of defined TSIG keys.
+ /// This map is passed into us and contains all of the TSIG keys defined
+ /// for this configuration. It is used to validate the key name entry of
+ /// DdnsDomains that specify one.
+ TSIGKeyInfoMapPtr keys_;
+
+ /// @brief Local storage for DdnsDomain instances. This is passed into a
+ /// DdnsDomainListParser(s), which in turn passes it into each
+ /// DdnsDomainParser. When the DdnsDomainParsers "commit" they add their
+ /// domain instance to this storage.
+ DdnsDomainMapPtr local_domains_;
+
+ /// @brief Local storage area for scalar parameter values. Use to hold
+ /// data until time to commit.
+ /// @todo Currently, the manager has no scalars but this is likely to
+ /// change as matching capabilities expand.
+ DScalarContext local_scalars_;
+};
+
+
+}; // end of isc::d2 namespace
+}; // end of isc namespace
+
+#endif // D2_CONFIG_H
diff --git a/src/bin/d2/d2_controller.h b/src/bin/d2/d2_controller.h
index 5ee15b1..0290f87 100644
--- a/src/bin/d2/d2_controller.h
+++ b/src/bin/d2/d2_controller.h
@@ -24,7 +24,7 @@ namespace d2 {
/// This class is the DHCP-DDNS specific derivation of DControllerBase. It
/// creates and manages an instance of the DHCP-DDNS application process,
/// D2Process.
-/// @TODO Currently, this class provides only the minimum required specialized
+/// @todo Currently, this class provides only the minimum required specialized
/// behavior to run the DHCP-DDNS service. It may very well expand as the
/// service implementation evolves. Some thought was given to making
/// DControllerBase a templated class but the labor savings versus the
diff --git a/src/bin/d2/d2_messages.mes b/src/bin/d2/d2_messages.mes
index 27778dc..f442fe0 100644
--- a/src/bin/d2/d2_messages.mes
+++ b/src/bin/d2/d2_messages.mes
@@ -26,11 +26,22 @@ to establish a session with the BIND10 control channel.
A debug message listing the command (and possible arguments) received
from the BIND10 control system by the controller.
+% DCTL_CONFIG_COMPLETE server has completed configuration: %1
+This is an informational message announcing the successful processing of a
+new configuration. It is output during server startup, and when an updated
+configuration is committed by the administrator. Additional information
+may be provided.
+
% DCTL_CONFIG_LOAD_FAIL %1 configuration failed to load: %2
This critical error message indicates that the initial application
configuration has failed. The service will start, but will not
process requests until the configuration has been corrected.
+% DCTL_CONFIG_START parsing new configuration: %1
+A debug message indicating that the application process has received an
+updated configuration and has passed it to its configuration manager
+for parsing.
+
% DCTL_CONFIG_STUB %1 configuration stub handler called
This debug message is issued when the dummy handler for configuration
events is called. This only happens during initial startup.
@@ -55,7 +66,24 @@ application and will exit.
% DCTL_NOT_RUNNING %1 application instance is not running
A warning message is issued when an attempt is made to shut down the
-the application when it is not running.
+application when it is not running.
+
+% DCTL_ORDER_ERROR configuration contains more elements than the parsing order
+An error message which indicates that configuration being parsed includes
+element ids not specified the configuration manager's parse order list. This
+is a programmatic error.
+
+% DCTL_ORDER_NO_ELEMENT element: %1 is in the parsing order but is missing from the configuration
+An error message output during a configuration update. The program is
+expecting an item but has not found it in the new configuration. This may
+mean that the BIND 10 configuration database is corrupt.
+
+% DCTL_PARSER_FAIL configuration parsing failed for configuration element: %1, reason: %2
+On receipt of message containing details to a change of its configuration,
+the server failed to create a parser to decode the contents of the named
+configuration element, or the creation succeeded but the parsing actions
+and committal of changes failed. The reason for the failure is given in
+the message.
% DCTL_PROCESS_FAILED %1 application execution failed: %2
The controller has encountered a fatal error while running the
@@ -96,6 +124,11 @@ has been invoked.
This is a debug message issued when the Dhcp-Ddns application encounters an
unrecoverable error from within the event loop.
+% DHCP_DDNS_NO_MATCH No DNS servers match FQDN: %1
+This is warning message issued when there are no domains in the configuration
+which match the cited fully qualified domain name (FQDN). The DNS Update
+request for the FQDN cannot be processed.
+
% DHCP_DDNS_PROCESS_INIT application init invoked
This is a debug message issued when the Dhcp-Ddns application enters
its init method.
diff --git a/src/bin/d2/d2_process.cc b/src/bin/d2/d2_process.cc
index 130bcc1..44575a3 100644
--- a/src/bin/d2/d2_process.cc
+++ b/src/bin/d2/d2_process.cc
@@ -14,6 +14,7 @@
#include <config/ccsession.h>
#include <d2/d2_log.h>
+#include <d2/d2_cfg_mgr.h>
#include <d2/d2_process.h>
using namespace asio;
@@ -22,7 +23,7 @@ namespace isc {
namespace d2 {
D2Process::D2Process(const char* name, IOServicePtr io_service)
- : DProcessBase(name, io_service) {
+ : DProcessBase(name, io_service, DCfgMgrBasePtr(new D2CfgMgr())) {
};
void
@@ -60,19 +61,19 @@ D2Process::shutdown() {
isc::data::ConstElementPtr
D2Process::configure(isc::data::ConstElementPtr config_set) {
- // @TODO This is the initial implementation which simply accepts
- // any content in config_set as valid. This is sufficient to
- // allow participation as a BIND10 module, while D2 configuration support
- // is being developed.
+ // @todo This is the initial implementation passes the configuration onto
+ // the D2CfgMgr. There may be additional steps taken added to handle
+ // configuration changes but for now, assume that D2CfgMgr is handling it
+ // all.
LOG_DEBUG(dctl_logger, DBGLVL_TRACE_BASIC,
DHCP_DDNS_CONFIGURE).arg(config_set->str());
- return (isc::config::createAnswer(0, "Configuration accepted."));
+ return (getCfgMgr()->parseConfig(config_set));
}
isc::data::ConstElementPtr
D2Process::command(const std::string& command, isc::data::ConstElementPtr args){
- // @TODO This is the initial implementation. If and when D2 is extended
+ // @todo This is the initial implementation. If and when D2 is extended
// to support its own commands, this implementation must change. Otherwise
// it should reject all commands as it does now.
LOG_DEBUG(dctl_logger, DBGLVL_TRACE_BASIC,
diff --git a/src/bin/d2/d2_process.h b/src/bin/d2/d2_process.h
index 4ddf8be..0055564 100644
--- a/src/bin/d2/d2_process.h
+++ b/src/bin/d2/d2_process.h
@@ -41,7 +41,7 @@ public:
D2Process(const char* name, IOServicePtr io_service);
/// @brief Will be used after instantiation to perform initialization
- /// unique to D2. @TODO This will likely include interactions with
+ /// unique to D2. @todo This will likely include interactions with
/// QueueMgr and UpdateMgr, to prepare for request receipt and processing.
/// Current implementation successfully does nothing.
/// @throw throws a DProcessBaseError if the initialization fails.
@@ -58,7 +58,7 @@ public:
/// @brief Implements the process's shutdown processing. When invoked, it
/// should ensure that the process gracefully exits the run method.
/// Current implementation simply sets the shutdown flag monitored by the
- /// run method. @TODO this may need to expand as the implementation evolves.
+ /// run method. @todo this may need to expand as the implementation evolves.
/// @throw throws a DProcessBaseError if an error is encountered.
virtual void shutdown();
diff --git a/src/bin/d2/d2_update_message.cc b/src/bin/d2/d2_update_message.cc
new file mode 100644
index 0000000..71fb9f3
--- /dev/null
+++ b/src/bin/d2/d2_update_message.cc
@@ -0,0 +1,221 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <d2/d2_update_message.h>
+#include <dns/messagerenderer.h>
+#include <dns/name.h>
+#include <dns/opcode.h>
+#include <dns/question.h>
+
+namespace isc {
+namespace d2 {
+
+using namespace isc::dns;
+
+D2UpdateMessage::D2UpdateMessage(const Direction direction)
+ : message_(direction == INBOUND ?
+ dns::Message::PARSE : dns::Message::RENDER) {
+ // If this object is to create an outgoing message, we have to
+ // set the proper Opcode field and QR flag here.
+ if (direction == OUTBOUND) {
+ message_.setOpcode(Opcode(Opcode::UPDATE_CODE));
+ message_.setHeaderFlag(dns::Message::HEADERFLAG_QR, false);
+
+ }
+}
+
+D2UpdateMessage::QRFlag
+D2UpdateMessage::getQRFlag() const {
+ return (message_.getHeaderFlag(dns::Message::HEADERFLAG_QR) ?
+ RESPONSE : REQUEST);
+}
+
+uint16_t
+D2UpdateMessage::getId() const {
+ return (message_.getQid());
+}
+
+void
+D2UpdateMessage::setId(const uint16_t id) {
+ message_.setQid(id);
+}
+
+
+const dns::Rcode&
+D2UpdateMessage::getRcode() const {
+ return (message_.getRcode());
+}
+
+void
+D2UpdateMessage::setRcode(const dns::Rcode& rcode) {
+ message_.setRcode(rcode);
+}
+
+unsigned int
+D2UpdateMessage::getRRCount(const UpdateMsgSection section) const {
+ return (message_.getRRCount(ddnsToDnsSection(section)));
+}
+
+const dns::RRsetIterator
+D2UpdateMessage::beginSection(const UpdateMsgSection section) const {
+ return (message_.beginSection(ddnsToDnsSection(section)));
+}
+
+const dns::RRsetIterator
+D2UpdateMessage::endSection(const UpdateMsgSection section) const {
+ return (message_.endSection(ddnsToDnsSection(section)));
+}
+
+void
+D2UpdateMessage::setZone(const Name& zone, const RRClass& rrclass) {
+ // The Zone data is kept in the underlying Question class. If there
+ // is a record stored there already, we need to remove it, because
+ // we may have at most one Zone record in the DNS Update message.
+ if (message_.getRRCount(dns::Message::SECTION_QUESTION) > 0) {
+ message_.clearSection(dns::Message::SECTION_QUESTION);
+ }
+ // Add the new record...
+ Question question(zone, rrclass, RRType::SOA());
+ message_.addQuestion(question);
+ // ... and update the local class member holding the D2Zone object.
+ zone_.reset(new D2Zone(question.getName(), question.getClass()));
+}
+
+D2ZonePtr
+D2UpdateMessage::getZone() const {
+ return (zone_);
+}
+
+void
+D2UpdateMessage::addRRset(const UpdateMsgSection section,
+ const dns::RRsetPtr& rrset) {
+ if (section == SECTION_ZONE) {
+ isc_throw(isc::BadValue, "unable to add RRset to the Zone section"
+ " of the DNS Update message, use setZone instead");
+ }
+ message_.addRRset(ddnsToDnsSection(section), rrset);
+}
+
+void
+D2UpdateMessage::toWire(AbstractMessageRenderer& renderer) {
+ // We are preparing the wire format of the message, meaning
+ // that this message will be sent as a request to the DNS.
+ // Therefore, we expect that this message is a REQUEST.
+ if (getQRFlag() != REQUEST) {
+ isc_throw(InvalidQRFlag, "QR flag must be cleared for the outgoing"
+ " DNS Update message");
+ }
+ // According to RFC2136, the ZONE section may contain exactly one
+ // record.
+ if (getRRCount(SECTION_ZONE) != 1) {
+ isc_throw(InvalidZoneSection, "Zone section of the DNS Update message"
+ " must comprise exactly one record (RFC2136, section 2.3)");
+ }
+ message_.toWire(renderer);
+}
+
+void
+D2UpdateMessage::fromWire(isc::util::InputBuffer& buffer) {
+ // First, use the underlying dns::Message implementation to get the
+ // contents of the DNS response message. Note that it may or may
+ // not be the message that we are interested in, but needs to be
+ // parsed so as we can check its ID, Opcode etc.
+ message_.fromWire(buffer);
+ // This class exposes the getZone() function. This function will return
+ // pointer to the D2Zone object if non-empty Zone section exists in the
+ // received message. It will return NULL pointer if it doesn't exist.
+ // The pointer is held in the D2UpdateMessage class member. We need to
+ // update this pointer every time we parse the message.
+ if (getRRCount(D2UpdateMessage::SECTION_ZONE) > 0) {
+ // There is a Zone section in the received message. Replace
+ // Zone pointer with the new value.
+ QuestionPtr question = *message_.beginQuestion();
+ // If the Zone counter is greater than 0 (which we have checked)
+ // there must be a valid Question pointer stored in the message_
+ // object. If there isn't, it is a programming error.
+ assert(question);
+ zone_.reset(new D2Zone(question->getName(), question->getClass()));
+
+ } else {
+ // Zone section doesn't hold any pointers, so set the pointer to NULL.
+ zone_.reset();
+
+ }
+ // Check that the content of the received message is sane.
+ // One of the basic checks to do is to verify that we have
+ // received the DNS update message. If not, it can be dropped
+ // or an error message can be printed. Other than that, we
+ // will check that there is at most one Zone record and QR flag
+ // is set.
+ validateResponse();
+}
+
+dns::Message::Section
+D2UpdateMessage::ddnsToDnsSection(const UpdateMsgSection section) {
+ /// The following switch maps the enumerator values from the
+ /// DNS Update message to the corresponding enumerator values
+ /// representing fields of the DNS message.
+ switch(section) {
+ case SECTION_ZONE :
+ return (dns::Message::SECTION_QUESTION);
+
+ case SECTION_PREREQUISITE:
+ return (dns::Message::SECTION_ANSWER);
+
+ case SECTION_UPDATE:
+ return (dns::Message::SECTION_AUTHORITY);
+
+ case SECTION_ADDITIONAL:
+ return (dns::Message::SECTION_ADDITIONAL);
+
+ default:
+ ;
+ }
+ isc_throw(dns::InvalidMessageSection,
+ "unknown message section " << section);
+}
+
+void
+D2UpdateMessage::validateResponse() const {
+ // Verify that we are dealing with the DNS Update message. According to
+ // RFC 2136, section 3.8 server will copy the Opcode from the query.
+ // If we are dealing with a different type of message, we may simply
+ // stop further processing, because it is likely that the message was
+ // directed to someone else.
+ if (message_.getOpcode() != Opcode::UPDATE()) {
+ isc_throw(NotUpdateMessage, "received message is not a DDNS update,"
+ << " received message code is "
+ << message_.getOpcode().getCode());
+ }
+ // Received message should have QR flag set, which indicates that it is
+ // a RESPONSE.
+ if (getQRFlag() == REQUEST) {
+ isc_throw(InvalidQRFlag, "received message should have QR flag set,"
+ " to indicate that it is a RESPONSE message; the QR"
+ << " flag in received message is unset");
+ }
+ // DNS server may copy a Zone record from the query message. Since query
+ // must comprise exactly one Zone record (RFC 2136, section 2.3), the
+ // response message may contain 1 record at most. It may also contain no
+ // records if a server chooses not to copy Zone section.
+ if (getRRCount(SECTION_ZONE) > 1) {
+ isc_throw(InvalidZoneSection, "received message contains "
+ << getRRCount(SECTION_ZONE) << " Zone records,"
+ << " it should contain at most 1 record");
+ }
+}
+
+} // namespace d2
+} // namespace isc
+
diff --git a/src/bin/d2/d2_update_message.h b/src/bin/d2/d2_update_message.h
new file mode 100644
index 0000000..98e98a8
--- /dev/null
+++ b/src/bin/d2/d2_update_message.h
@@ -0,0 +1,337 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef D2_UPDATE_MESSAGE_H
+#define D2_UPDATE_MESSAGE_H
+
+#include <d2/d2_zone.h>
+#include <dns/message.h>
+#include <dns/name.h>
+#include <dns/rcode.h>
+#include <dns/rrclass.h>
+#include <dns/rrset.h>
+
+#include <map>
+
+namespace isc {
+namespace d2 {
+
+/// @brief Exception indicating that Zone section contains invalid content.
+///
+/// This exception is thrown when ZONE section of the DNS Update message
+/// is invalid. According to RFC2136, section 2.3, the zone section is
+/// allowed to contain exactly one record. When Request message contains
+/// more records or is empty, this exception is thrown.
+class InvalidZoneSection : public Exception {
+public:
+ InvalidZoneSection(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// @brief Exception indicating that QR flag has invalid value.
+///
+/// This exception is thrown when QR flag has invalid value for
+/// the operation performed on the particular message. For instance,
+/// the QR flag must be set to indicate that the given message is
+/// a RESPONSE when @c D2UpdateMessage::fromWire is performed.
+/// The QR flag must be cleared when @c D2UpdateMessage::toWire
+/// is executed.
+class InvalidQRFlag : public Exception {
+public:
+ InvalidQRFlag(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// @brief Exception indicating that the parsed message is not DNS Update.
+///
+/// This exception is thrown when decoding the DNS message which is not
+/// a DNS Update.
+class NotUpdateMessage : public Exception {
+public:
+ NotUpdateMessage(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+
+/// @brief The @c D2UpdateMessage encapsulates a DNS Update message.
+///
+/// This class represents the DNS Update message. Functions exposed by this
+/// class allow to specify the data sections carried by the message and create
+/// an on-wire format of this message. This class is also used to decode
+/// messages received from the DNS server in the on-wire format.
+///
+/// <b>Design choice:</b> A dedicated class has been created to encapsulate
+/// DNS Update message because existing @c isc::dns::Message is designed to
+/// support regular DNS messages (described in RFC 1035) only. Although DNS
+/// Update has the same format, particular sections serve different purposes.
+/// In order to avoid rewrite of significant portions of @c isc::dns::Message
+/// class, this class is implemented in-terms-of @c isc::dns::Message class
+/// to reuse its functionality where possible.
+class D2UpdateMessage {
+public:
+
+ /// @brief Indicates if the @c D2UpdateMessage object encapsulates Inbound
+ /// or Outbound message.
+ enum Direction {
+ INBOUND,
+ OUTBOUND
+ };
+
+ /// @brief Indicates whether DNS Update message is a REQUEST or RESPONSE.
+ enum QRFlag {
+ REQUEST,
+ RESPONSE
+ };
+
+ /// @brief Identifies sections in the DNS Update Message.
+ ///
+ /// Each message comprises message Header and may contain the following
+ /// sections:
+ /// - ZONE
+ /// - PREREQUISITE
+ /// - UPDATE
+ /// - ADDITIONAL
+ ///
+ /// The enum elements are used by functions such as @c getRRCount (to get
+ /// the number of records in a corresponding section) and @c beginSection
+ /// and @c endSection (to access data in the corresponding section).
+ enum UpdateMsgSection {
+ SECTION_ZONE,
+ SECTION_PREREQUISITE,
+ SECTION_UPDATE,
+ SECTION_ADDITIONAL
+ };
+
+public:
+ /// @brief Constructor used to create an instance of the DNS Update Message
+ /// (either outgoing or incoming).
+ ///
+ /// This constructor is used to create an instance of either incoming or
+ /// outgoing DNS Update message. The boolean argument indicates wheteher it
+ /// is incoming (true) or outgoing (false) message. For incoming messages
+ /// the @c D2UpdateMessage::fromWire function is used to parse on-wire data.
+ /// For outgoing messages, modifier functions should be used to set the
+ /// message contents and @c D2UpdateMessage::toWire function to create
+ /// on-wire data.
+ ///
+ /// @param direction indicates if this is an inbound or outbound message.
+ D2UpdateMessage(const Direction direction = OUTBOUND);
+
+ ///
+ /// @name Copy constructor and assignment operator
+ ///
+ /// Copy constructor and assignment operator are private because we assume
+ /// there will be no need to copy messages on the client side.
+ //@{
+private:
+ D2UpdateMessage(const D2UpdateMessage& source);
+ D2UpdateMessage& operator=(const D2UpdateMessage& source);
+ //@}
+
+public:
+
+ /// @brief Returns enum value indicating if the message is a
+ /// REQUEST or RESPONSE
+ ///
+ /// The returned value is REQUEST if the message is created as an outgoing
+ /// message. In such case the QR flag bit in the message header is cleared.
+ /// The returned value is RESPONSE if the message is created as an incoming
+ /// message and the QR flag bit was set in the received message header.
+ ///
+ /// @return An enum value indicating whether the message is a
+ /// REQUEST or RESPONSE.
+ QRFlag getQRFlag() const;
+
+ /// @brief Returns message ID.
+ ///
+ /// @return message ID.
+ uint16_t getId() const;
+
+ /// @brief Sets message ID.
+ ///
+ /// @param id 16-bit value of the message id.
+ void setId(const uint16_t id);
+
+ /// @brief Returns an object representing message RCode.
+ ///
+ /// @return An object representing message RCode.
+ const dns::Rcode& getRcode() const;
+
+ /// @brief Sets message RCode.
+ ///
+ /// @param rcode An object representing message RCode.
+ void setRcode(const dns::Rcode& rcode);
+
+ /// @brief Returns number of RRsets in the specified message section.
+ ///
+ /// @param section An @c UpdateMsgSection enum specifying a message section
+ /// for which the number of RRsets is to be returned.
+ ///
+ /// @return A number of RRsets in the specified message section.
+ unsigned int getRRCount(const UpdateMsgSection section) const;
+
+ /// @name Functions returning iterators to RRsets in message sections.
+ ///
+ //@{
+ /// @brief Return iterators pointing to the beginning of the list of RRsets,
+ /// which belong to the specified section.
+ ///
+ /// @param section An @c UpdateMsgSection enum specifying a message section
+ /// for which the iterator should be returned.
+ ///
+ /// @return An iterator pointing to the beginning of the list of the
+ /// RRsets, which belong to the specified section.
+ const dns::RRsetIterator beginSection(const UpdateMsgSection section) const;
+
+ /// @brief Return iterators pointing to the end of the list of RRsets,
+ /// which belong to the specified section.
+ ///
+ /// @param section An @c UpdateMsgSection enum specifying a message section
+ /// for which the iterator should be returned.
+ ///
+ /// @return An iterator pointing to the end of the list of the
+ /// RRsets, which belong to the specified section.
+ const dns::RRsetIterator endSection(const UpdateMsgSection section) const;
+ //@}
+
+ /// @brief Sets the Zone record.
+ ///
+ /// This function creates the @c D2Zone object, representing a Zone record
+ /// for the outgoing message. If the Zone record is already set, it is
+ /// replaced by the new record being set by this function. The RRType for
+ /// the record is always SOA.
+ ///
+ /// @param zone A name of the zone being updated.
+ /// @param rrclass A class of the zone record.
+ void setZone(const dns::Name& zone, const dns::RRClass& rrclass);
+
+ /// @brief Returns a pointer to the object representing Zone record.
+ ///
+ /// @return A pointer to the object representing Zone record.
+ D2ZonePtr getZone() const;
+
+ /// @brief Adds an RRset to the specified section.
+ ///
+ /// This function may throw exception if the specified section is
+ /// out of bounds or Zone section update is attempted. For Zone
+ /// section @c D2UpdateMessage::setZone function should be used instead.
+ /// Also, this function expects that @c rrset argument is non-NULL.
+ ///
+ /// @param section A message section where the RRset should be added.
+ /// @param rrset A reference to a RRset which should be added.
+ void addRRset(const UpdateMsgSection section, const dns::RRsetPtr& rrset);
+
+ /// @name Functions to handle message encoding and decoding.
+ ///
+ //@{
+ /// @brief Encode outgoing message into wire format.
+ ///
+ /// This function encodes the DNS Update into the wire format. The format of
+ /// such a message is described in the RFC2136, section 2. Some of the
+ /// sections which belong to encoded message may be empty. If a particular
+ /// message section is empty (does not comprise any RRs), the corresponding
+ /// counter in the message header is set to 0. These counters are: PRCOUNT,
+ /// UPCOUNT, ADCOUNT for the Prerequisites, Update RRs and Additional Data
+ /// RRs respectively. The ZOCOUNT must be equal to 1 because RFC2136
+ /// requires that the message comprises exactly one Zone record.
+ ///
+ /// This function does not guarantee exception safety. However, exceptions
+ /// should be rare because @c D2UpdateMessage class API prevents invalid
+ /// use of the class. The typical case, when this function may throw an
+ /// exception is when this it is called on the object representing
+ /// incoming (instead of outgoing) message. In such case, the QR field
+ /// will be set to RESPONSE, which is invalid setting when calling this
+ /// function.
+ ///
+ /// @param renderer A renderer object used to generate the message wire
+ /// format.
+ void toWire(dns::AbstractMessageRenderer& renderer);
+
+ /// @brief Decode incoming message from the wire format.
+ ///
+ /// This function decodes the DNS Update message stored in the buffer
+ /// specified by the function argument. In the first turn, this function
+ /// parses message header and extracts the section counters: ZOCOUNT,
+ /// PRCOUNT, UPCOUNT and ADCOUNT. Using these counters, function identifies
+ /// message sections, which follow message header. These sections can be
+ /// later accessed using: @c D2UpdateMessage::getZone,
+ /// @c D2UpdateMessage::beginSection and @c D2UpdateMessage::endSection
+ /// functions.
+ ///
+ /// This function is NOT exception safe. It signals message decoding errors
+ /// through exceptions. Message decoding error may occur if the received
+ /// message does not conform to the general DNS Message format, specified in
+ /// RFC 1035. Errors which are specific to DNS Update messages include:
+ /// - Invalid Opcode - not an UPDATE.
+ /// - Invalid QR flag - the QR bit should be set to indicate that the
+ /// message is the server response.
+ /// - The number of records in the Zone section is greater than 1.
+ ///
+ /// @param buffer input buffer, holding DNS Update message to be parsed.
+ void fromWire(isc::util::InputBuffer& buffer);
+ //@}
+
+private:
+ /// Maps the values of the @c UpdateMessageSection field to the
+ /// corresponding values in the @c isc::dns::Message class. This
+ /// mapping is required here because this class uses @c isc::dns::Message
+ /// class to do the actual processing of the DNS Update message.
+ ///
+ /// @param section An enum indicating the section for which the
+ /// corresponding enum value from @c isc::dns::Message will be returned.
+ ///
+ /// @return The enum value indicating the section in the DNS message
+ /// represented by the @c isc::dns::Message class.
+ static
+ dns::Message::Section ddnsToDnsSection(const UpdateMsgSection section);
+
+ /// @brief Checks received response message for correctness.
+ ///
+ /// This function verifies that the received response from a server is
+ /// correct. Currently this function checks the following:
+ /// - Opcode is 'DNS Update',
+ /// - QR flag is RESPONSE (flag bit is set),
+ /// - Zone section comprises at most one record.
+ ///
+ /// The function will throw exception if any of the conditions above are
+ /// not met.
+ ///
+ /// @throw isc::d2::NotUpdateMessage if invalid Opcode.
+ /// @throw isc::d2::InvalidQRFlag if QR flag is not set to RESPONSE
+ /// @throw isc::d2::InvalidZone section, if Zone section comprises more
+ /// than one record.
+ void validateResponse() const;
+
+ /// @brief An object representing DNS Message which is used by the
+ /// implementation of @c D2UpdateMessage to perform low level.
+ ///
+ /// Declaration of this object pollutes the header with the details
+ /// of @c D2UpdateMessage implementation. It might be cleaner to use
+ /// Pimpl idiom to hide this object in an D2UpdateMessageImpl. However,
+ /// it would bring additional complications to the implementation
+ /// while the benefit would low - this header is not a part of any
+ /// common library. Therefore, if implementation is changed, modification of
+ /// private members of this class in the header has low impact.
+ dns::Message message_;
+
+ /// @brief Holds a pointer to the object, representing Zone in the DNS
+ /// Update.
+ D2ZonePtr zone_;
+
+};
+
+} // namespace d2
+} // namespace isc
+
+#endif // D2_UPDATE_MESSAGE_H
diff --git a/src/bin/d2/d2_zone.cc b/src/bin/d2/d2_zone.cc
new file mode 100644
index 0000000..96aa2bb
--- /dev/null
+++ b/src/bin/d2/d2_zone.cc
@@ -0,0 +1,36 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <d2/d2_zone.h>
+
+namespace isc {
+namespace d2 {
+
+D2Zone::D2Zone(const dns::Name& name, const dns::RRClass& rrclass)
+ : name_(name), rrclass_(rrclass) {
+}
+
+std::string D2Zone::toText() const {
+ return (name_.toText() + " " + rrclass_.toText() + " SOA\n");
+}
+
+std::ostream&
+operator<<(std::ostream& os, const D2Zone& zone) {
+ os << zone.toText();
+ return (os);
+}
+
+} // namespace d2
+} // namespace isc
+
diff --git a/src/bin/d2/d2_zone.h b/src/bin/d2/d2_zone.h
new file mode 100644
index 0000000..60d43c8
--- /dev/null
+++ b/src/bin/d2/d2_zone.h
@@ -0,0 +1,117 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef D2_ZONE_H
+#define D2_ZONE_H
+
+#include <dns/name.h>
+#include <dns/rrclass.h>
+
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace d2 {
+
+/// @brief The @c D2Zone encapsulates the Zone section in DNS Update message.
+///
+/// This class is used by the @c D2UpdateMessage to encapsulate the Zone section
+/// of the DNS Update message. Class members hold corresponding values of
+/// section's fields: NAME, CLASS. This class does not hold the RTYPE field
+/// value because RTYPE is always equal to SOA for DNS Update message (see
+/// RFC 2136, section 2.3).
+///
+/// Note, that this @c D2Zone class neither exposes functions to decode messages
+/// from wire format nor to encode to wire format. This is not needed, because
+/// @c isc::d2::D2UpdateMessage class uses @c D2Zone only to return the parsed
+/// Zone information to the caller. Internally, D2UpdateMessage parses and
+/// stores Zone section using @c isc::dns::Question class, and the @c toWire
+/// and @c fromWire functions of the @c isc::dns::Question class are used.
+class D2Zone {
+public:
+ /// @brief Constructor from Name and RRClass.
+ ///
+ /// @param name The name of the Zone.
+ /// @param rrclass The RR class of the Zone.
+ D2Zone(const dns::Name& name, const dns::RRClass& rrclass);
+
+ ///
+ /// @name Getters
+ ///
+ //@{
+ /// @brief Returns the Zone name.
+ ///
+ /// @return A reference to the Zone name.
+ const dns::Name& getName() const { return (name_); }
+
+ /// @brief Returns the Zone class.
+ ///
+ /// @return A reference to the Zone class.
+ const dns::RRClass& getClass() const { return (rrclass_); }
+ //@}
+
+ /// @brief Returns text representation of the Zone.
+ ///
+ /// This function concatenates the name of the Zone, Class and Type.
+ /// The type is always SOA.
+ ///
+ /// @return A text representation of the Zone.
+ std::string toText() const;
+
+ ///
+ /// @name Comparison Operators
+ ///
+ //@{
+ /// @brief Equality operator to compare @c D2Zone objects in query and
+ /// response messages.
+ ///
+ /// @param rhs Zone to compare against.
+ ///
+ /// @return true if name and class are equal, false otherwise.
+ bool operator==(const D2Zone& rhs) const {
+ return ((rrclass_ == rhs.rrclass_) && (name_ == rhs.name_));
+ }
+
+ /// @brief Inequality operator to compare @c D2Zone objects in query and
+ /// response messages.
+ ///
+ /// @param rhs Zone to compare against.
+ ///
+ /// @return true if any of name or class are unequal, false otherwise.
+ bool operator!=(const D2Zone& rhs) const {
+ return (!operator==(rhs));
+ }
+ //@}
+
+private:
+ dns::Name name_; ///< Holds the Zone name.
+ dns::RRClass rrclass_; ///< Holds the Zone class.
+};
+
+typedef boost::shared_ptr<D2Zone> D2ZonePtr;
+
+/// @brief Insert the @c D2Zone as a string into stream.
+///
+/// @param os A @c std::ostream object on which the insertion operation is
+/// performed.
+/// @param zone A reference to the @c D2Zone object output by the
+/// operation.
+///
+/// @return A reference to the same @c std::ostream object referenced by
+/// parameter @c os after the insertion operation.
+std::ostream& operator<<(std::ostream& os, const D2Zone& zone);
+
+} // namespace d2
+} // namespace isc
+
+#endif // D2_ZONE_H
diff --git a/src/bin/d2/d_cfg_mgr.cc b/src/bin/d2/d_cfg_mgr.cc
new file mode 100644
index 0000000..a8394a9
--- /dev/null
+++ b/src/bin/d2/d_cfg_mgr.cc
@@ -0,0 +1,240 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config/ccsession.h>
+#include <d2/d2_log.h>
+#include <dhcp/libdhcp++.h>
+#include <d2/d_cfg_mgr.h>
+#include <dhcpsrv/dhcp_parsers.h>
+#include <util/encode/hex.h>
+#include <util/strutil.h>
+
+#include <boost/foreach.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/algorithm/string.hpp>
+
+#include <limits>
+#include <iostream>
+#include <vector>
+#include <map>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::data;
+using namespace isc::asiolink;
+
+namespace isc {
+namespace d2 {
+
+// *********************** DCfgContextBase *************************
+
+DCfgContextBase::DCfgContextBase():
+ boolean_values_(new BooleanStorage()),
+ uint32_values_(new Uint32Storage()),
+ string_values_(new StringStorage()) {
+ }
+
+DCfgContextBase::DCfgContextBase(const DCfgContextBase& rhs):
+ boolean_values_(new BooleanStorage(*(rhs.boolean_values_))),
+ uint32_values_(new Uint32Storage(*(rhs.uint32_values_))),
+ string_values_(new StringStorage(*(rhs.string_values_))) {
+}
+
+void
+DCfgContextBase::getParam(const std::string& name, bool& value, bool optional) {
+ try {
+ value = boolean_values_->getParam(name);
+ } catch (DhcpConfigError& ex) {
+ // If the parameter is not optional, re-throw the exception.
+ if (!optional) {
+ throw;
+ }
+ }
+}
+
+
+void
+DCfgContextBase::getParam(const std::string& name, uint32_t& value,
+ bool optional) {
+ try {
+ value = uint32_values_->getParam(name);
+ } catch (DhcpConfigError& ex) {
+ // If the parameter is not optional, re-throw the exception.
+ if (!optional) {
+ throw;
+ }
+ }
+}
+
+void
+DCfgContextBase::getParam(const std::string& name, std::string& value,
+ bool optional) {
+ try {
+ value = string_values_->getParam(name);
+ } catch (DhcpConfigError& ex) {
+ // If the parameter is not optional, re-throw the exception.
+ if (!optional) {
+ throw;
+ }
+ }
+}
+
+DCfgContextBase::~DCfgContextBase() {
+}
+
+// *********************** DCfgMgrBase *************************
+
+DCfgMgrBase::DCfgMgrBase(DCfgContextBasePtr context)
+ : parse_order_(), context_(context) {
+ if (!context_) {
+ isc_throw(DCfgMgrBaseError, "DCfgMgrBase ctor: context cannot be NULL");
+ }
+}
+
+DCfgMgrBase::~DCfgMgrBase() {
+}
+
+isc::data::ConstElementPtr
+DCfgMgrBase::parseConfig(isc::data::ConstElementPtr config_set) {
+ LOG_DEBUG(dctl_logger, DBGLVL_COMMAND,
+ DCTL_CONFIG_START).arg(config_set->str());
+
+ if (!config_set) {
+ return (isc::config::createAnswer(1,
+ std::string("Can't parse NULL config")));
+ }
+
+ // The parsers implement data inheritance by directly accessing
+ // configuration context. For this reason the data parsers must store
+ // the parsed data into context immediately. This may cause data
+ // inconsistency if the parsing operation fails after the context has been
+ // modified. We need to preserve the original context here
+ // so as we can rollback changes when an error occurs.
+ DCfgContextBasePtr original_context = context_->clone();
+
+ // Answer will hold the result returned to the caller.
+ ConstElementPtr answer;
+
+ // Holds the name of the element being parsed.
+ std::string element_id;
+
+ try {
+ // Grab a map of element_ids and their data values from the new
+ // configuration set.
+ const std::map<std::string, ConstElementPtr>& values_map =
+ config_set->mapValue();
+
+ // Use a pre-ordered list of element ids to parse the elements in a
+ // specific order if the list (parser_order_) is not empty; otherwise
+ // elements are parsed in the order the value_map presents them.
+
+ if (parse_order_.size() > 0) {
+ // For each element_id in the parse order list, look for it in the
+ // value map. If the element exists in the map, pass it and it's
+ // associated data in for parsing.
+ // If there is no matching entry in the value map an error is
+ // thrown. Note, that elements tagged as "optional" from the user
+ // perspective must still have default or empty entries in the
+ // configuration set to be parsed.
+ int parsed_count = 0;
+ std::map<std::string, ConstElementPtr>::const_iterator it;
+ BOOST_FOREACH(element_id, parse_order_) {
+ it = values_map.find(element_id);
+ if (it != values_map.end()) {
+ ++parsed_count;
+ buildAndCommit(element_id, it->second);
+ }
+ else {
+ LOG_ERROR(dctl_logger, DCTL_ORDER_NO_ELEMENT)
+ .arg(element_id);
+ isc_throw(DCfgMgrBaseError, "Element:" << element_id <<
+ " is listed in the parse order but is not "
+ " present in the configuration");
+ }
+ }
+
+ // NOTE: When using ordered parsing, the parse order list MUST
+ // include every possible element id that the value_map may contain.
+ // Entries in the map that are not in the parse order, would not be
+ // parsed. For now we will flag this as a programmatic error. One
+ // could attempt to adjust for this, by identifying such entries
+ // and parsing them either first or last but which would be correct?
+ // Better to hold the engineer accountable. So, if we parsed none
+ // or we parsed fewer than are in the map; then either the parse i
+ // order is incomplete OR the map has unsupported values.
+ if (!parsed_count ||
+ (parsed_count && ((parsed_count + 1) < values_map.size()))) {
+ LOG_ERROR(dctl_logger, DCTL_ORDER_ERROR);
+ isc_throw(DCfgMgrBaseError,
+ "Configuration contains elements not in parse order");
+ }
+ } else {
+ // Order doesn't matter so iterate over the value map directly.
+ // Pass each element and it's associated data in to be parsed.
+ ConfigPair config_pair;
+ BOOST_FOREACH(config_pair, values_map) {
+ element_id = config_pair.first;
+ buildAndCommit(element_id, config_pair.second);
+ }
+ }
+
+ // Everything was fine. Configuration set processed successfully.
+ LOG_INFO(dctl_logger, DCTL_CONFIG_COMPLETE).arg("");
+ answer = isc::config::createAnswer(0, "Configuration committed.");
+
+ } catch (const isc::Exception& ex) {
+ LOG_ERROR(dctl_logger, DCTL_PARSER_FAIL).arg(element_id).arg(ex.what());
+ answer = isc::config::createAnswer(1,
+ string("Configuration parsing failed: ") + ex.what());
+
+ // An error occurred, so make sure that we restore original context.
+ context_ = original_context;
+ return (answer);
+ }
+
+ return (answer);
+}
+
+void DCfgMgrBase::buildAndCommit(std::string& element_id,
+ isc::data::ConstElementPtr value) {
+ // Call derivation's implementation to create the appropriate parser
+ // based on the element id.
+ ParserPtr parser = createConfigParser(element_id);
+ if (!parser) {
+ isc_throw(DCfgMgrBaseError, "Could not create parser");
+ }
+
+ try {
+ // Invoke the parser's build method passing in the value. This will
+ // "convert" the Element form of value into the actual data item(s)
+ // and store them in parser's local storage.
+ parser->build(value);
+
+ // Invoke the parser's commit method. This "writes" the the data
+ // item(s) stored locally by the parser into the context. (Note that
+ // parsers are free to do more than update the context, but that is an
+ // nothing something we are concerned with here.)
+ parser->commit();
+ } catch (const isc::Exception& ex) {
+ isc_throw(DCfgMgrBaseError,
+ "Could not build and commit: " << ex.what());
+ } catch (...) {
+ isc_throw(DCfgMgrBaseError, "Non-ISC exception occurred");
+ }
+}
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
+
diff --git a/src/bin/d2/d_cfg_mgr.h b/src/bin/d2/d_cfg_mgr.h
new file mode 100644
index 0000000..a0bd9bb
--- /dev/null
+++ b/src/bin/d2/d_cfg_mgr.h
@@ -0,0 +1,331 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef D_CFG_MGR_H
+#define D_CFG_MGR_H
+
+#include <cc/data.h>
+#include <exceptions/exceptions.h>
+#include <dhcpsrv/dhcp_parsers.h>
+
+#include <stdint.h>
+#include <string>
+
+namespace isc {
+namespace d2 {
+
+/// @brief Exception thrown if the configuration manager encounters an error.
+class DCfgMgrBaseError : public isc::Exception {
+public:
+ DCfgMgrBaseError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+class DCfgContextBase;
+/// @brief Pointer to a configuration context.
+typedef boost::shared_ptr<DCfgContextBase> DCfgContextBasePtr;
+
+/// @brief Abstract class that implements a container for configuration context.
+/// It provides a single enclosure for the storage of configuration parameters
+/// and any other context specific information that needs to be accessible
+/// during configuration parsing as well as to the application as a whole.
+/// The base class supports storage for a small set of simple data types.
+/// Derivations simply add additional storage as needed. Note that this class
+/// declares the pure virtual clone() method, its copy constructor is protected,
+/// and its copy operator is inaccessible. Derivations must supply an
+/// implementation of clone that calls the base class copy constructor.
+/// This allows the management class to perform context backup and restoration
+/// without derivation specific knowledge using logic like
+/// the following:
+///
+/// // Make a backup copy
+/// DCfgContextBasePtr backup_copy(context_->clone());
+/// :
+/// // Restore from backup
+/// context_ = backup_copy;
+///
+class DCfgContextBase {
+public:
+ /// @brief Indicator that a configuration parameter is optional.
+ static const bool OPTIONAL = true;
+ static const bool REQUIRED = false;
+
+ /// @brief Constructor
+ DCfgContextBase();
+
+ /// @brief Destructor
+ virtual ~DCfgContextBase();
+
+ /// @brief Fetches the value for a given boolean configuration parameter
+ /// from the context.
+ ///
+ /// @param name is the name of the parameter to retrieve.
+ /// @param value is an output parameter in which to return the retrieved
+ /// value.
+ /// @param optional if true, the parameter is optional and the method
+ /// will not throw if the parameter is not found in the context. The
+ /// contents of the output parameter, value, will not be altered.
+ /// It defaults to false if not specified.
+ /// @throw throws DhcpConfigError if the context does not contain the
+ /// parameter and optional is false.
+ void getParam(const std::string& name, bool& value, bool optional=false);
+
+ /// @brief Fetches the value for a given uint32_t configuration parameter
+ /// from the context.
+ ///
+ /// @param name is the name of the parameter to retrieve.
+ /// @param value is an output parameter in which to return the retrieved
+ /// value.
+ /// @param optional if true, the parameter is optional and the method
+ /// will not throw if the parameter is not found in the context. The
+ /// contents of the output parameter, value, will not be altered.
+ /// @throw throws DhcpConfigError if the context does not contain the
+ /// parameter and optional is false.
+ void getParam(const std::string& name, uint32_t& value,
+ bool optional=false);
+
+ /// @brief Fetches the value for a given string configuration parameter
+ /// from the context.
+ ///
+ /// @param name is the name of the parameter to retrieve.
+ /// @param value is an output parameter in which to return the retrieved
+ /// value.
+ /// @param optional if true, the parameter is optional and the method
+ /// will not throw if the parameter is not found in the context. The
+ /// contents of the output parameter, value, will not be altered.
+ /// @throw throws DhcpConfigError if the context does not contain the
+ /// parameter and optional is false.
+ void getParam(const std::string& name, std::string& value,
+ bool optional=false);
+
+ /// @brief Fetches the Boolean Storage. Typically used for passing
+ /// into parsers.
+ ///
+ /// @return returns a pointer to the Boolean Storage.
+ isc::dhcp::BooleanStoragePtr getBooleanStorage() {
+ return (boolean_values_);
+ }
+
+ /// @brief Fetches the uint32 Storage. Typically used for passing
+ /// into parsers.
+ ///
+ /// @return returns a pointer to the uint32 Storage.
+ isc::dhcp::Uint32StoragePtr getUint32Storage() {
+ return (uint32_values_);
+ }
+
+ /// @brief Fetches the string Storage. Typically used for passing
+ /// into parsers.
+ ///
+ /// @return returns a pointer to the string Storage.
+ isc::dhcp::StringStoragePtr getStringStorage() {
+ return (string_values_);
+ }
+
+ /// @brief Creates a clone of this context object.
+ ///
+ /// As mentioned in the the class brief, derivation must supply an
+ /// implementation that initializes the base class storage as well as its
+ /// own. Typically the derivation's clone method would return the result
+ /// of passing "*this" into its own copy constructor:
+ ///
+ /// @code
+ /// class DStubContext : public DCfgContextBase {
+ /// public:
+ /// :
+ /// // Clone calls its own copy constructor
+ /// virtual DCfgContextBasePtr clone() {
+ /// return (DCfgContextBasePtr(new DStubContext(*this)));
+ /// }
+ ///
+ /// // Note that the copy constructor calls the base class copy ctor
+ /// // then initializes its additional storage.
+ /// DStubContext(const DStubContext& rhs) : DCfgContextBase(rhs),
+ /// extra_values_(new Uint32Storage(*(rhs.extra_values_))) {
+ /// }
+ /// :
+ /// // Here's the derivation's additional storage.
+ /// isc::dhcp::Uint32StoragePtr extra_values_;
+ /// :
+ /// @endcode
+ ///
+ /// @return returns a pointer to the new clone.
+ virtual DCfgContextBasePtr clone() = 0;
+
+protected:
+ /// @brief Copy constructor for use by derivations in clone().
+ DCfgContextBase(const DCfgContextBase& rhs);
+
+private:
+ /// @brief Private assignment operator to avoid potential for slicing.
+ DCfgContextBase& operator=(const DCfgContextBase& rhs);
+
+ /// @brief Storage for boolean parameters.
+ isc::dhcp::BooleanStoragePtr boolean_values_;
+
+ /// @brief Storage for uint32 parameters.
+ isc::dhcp::Uint32StoragePtr uint32_values_;
+
+ /// @brief Storage for string parameters.
+ isc::dhcp::StringStoragePtr string_values_;
+};
+
+/// @brief Defines an unsorted, list of string Element IDs.
+typedef std::vector<std::string> ElementIdList;
+
+/// @brief Configuration Manager
+///
+/// DCfgMgrBase is an abstract class that provides the mechanisms for managing
+/// an application's configuration. This includes services for parsing sets of
+/// configuration values, storing the parsed information in its converted form,
+/// and retrieving the information on demand. It is intended to be the worker
+/// class which is handed a set of configuration values to process by upper
+/// application management layers.
+///
+/// The class presents a public method for receiving new configurations,
+/// parseConfig. This method coordinates the parsing effort as follows:
+///
+/// @code
+/// make backup copy of configuration context
+/// for each top level element in new configuration
+/// get derivation-specific parser for element
+/// run parser
+/// update context with parsed results
+/// break on error
+///
+/// if an error occurred
+/// restore configuration context from backup
+/// @endcode
+///
+/// After making a backup of the current context, it iterates over the top-level
+/// elements in the new configuration. The order in which the elements are
+/// processed is either:
+///
+/// 1. Natural order presented by the configuration set
+/// 2. Specific order determined by a list of element ids
+///
+/// This allows a derivation to specify the order in which its elements are
+/// parsed if there are dependencies between elements.
+///
+/// To parse a given element, its id is passed into createConfigParser,
+/// which returns an instance of the appropriate parser. This method is
+/// abstract so the derivation's implementation determines the type of parser
+/// created. This isolates the knowledge of specific element ids and which
+/// application specific parsers to derivation.
+///
+/// Once the parser has been created, it is used to parse the data value
+/// associated with the element id and update the context with the parsed
+/// results.
+///
+/// In the event that an error occurs, parsing is halted and the
+/// configuration context is restored from backup.
+class DCfgMgrBase {
+public:
+ /// @brief Constructor
+ ///
+ /// @param context is a pointer to the configuration context the manager
+ /// will use for storing parsed results.
+ ///
+ /// @throw throws DCfgMgrBaseError if context is null
+ DCfgMgrBase(DCfgContextBasePtr context);
+
+ /// @brief Destructor
+ virtual ~DCfgMgrBase();
+
+ /// @brief Acts as the receiver of new configurations and coordinates
+ /// the parsing as described in the class brief.
+ ///
+ /// @param config_set is a set of configuration elements to parsed.
+ ///
+ /// @return an Element that contains the results of configuration composed
+ /// of an integer status value (0 means successful, non-zero means failure),
+ /// and a string explanation of the outcome.
+ isc::data::ConstElementPtr parseConfig(isc::data::ConstElementPtr
+ config_set);
+
+ /// @brief Adds a given element id to the end of the parse order list.
+ ///
+ /// The order in which elements are retrieved from this is the order in
+ /// which they are added to the list. Derivations should use this method
+ /// to populate the parse order as part of their constructor.
+ ///
+ /// @param element_id is the string name of the element as it will appear
+ /// in the configuration set.
+ void addToParseOrder(const std::string& element_id){
+ parse_order_.push_back(element_id);
+ }
+
+ /// @brief Fetches the parse order list.
+ ///
+ /// @return returns a const reference to the list.
+ const ElementIdList& getParseOrder() const {
+ return (parse_order_);
+ }
+
+ /// @brief Fetches the configuration context.
+ ///
+ /// @return returns a pointer reference to the configuration context.
+ DCfgContextBasePtr& getContext() {
+ return (context_);
+ }
+
+protected:
+ /// @brief Create a parser instance based on an element id.
+ ///
+ /// Given an element_id returns an instance of the appropriate parser.
+ /// This method is abstract, isolating any direct knowledge of element_ids
+ /// and parsers to within the application-specific derivation.
+ ///
+ /// @param element_id is the string name of the element as it will appear
+ /// in the configuration set.
+ ///
+ /// @return returns a ParserPtr to the parser instance.
+ /// @throw throws DCfgMgrBaseError if an error occurs.
+ virtual isc::dhcp::ParserPtr
+ createConfigParser(const std::string& element_id) = 0;
+
+private:
+
+ /// @brief Parse a configuration element.
+ ///
+ /// Given an element_id and data value, instantiate the appropriate
+ /// parser, parse the data value, and commit the results.
+ ///
+ /// @param element_id is the string name of the element as it will appear
+ /// in the configuration set.
+ /// @param value is the data value to be parsed and associated with
+ /// element_id.
+ ///
+ /// @throw throws DCfgMgrBaseError if an error occurs.
+ void buildAndCommit(std::string& element_id,
+ isc::data::ConstElementPtr value);
+
+ /// @brief A list of element ids which specifies the element parsing order.
+ ///
+ /// If the list is empty, the natural order in the configuration set
+ /// it used.
+ ElementIdList parse_order_;
+
+ /// @brief Pointer to the configuration context instance.
+ DCfgContextBasePtr context_;
+};
+
+/// @brief Defines a shared pointer to DCfgMgrBase.
+typedef boost::shared_ptr<DCfgMgrBase> DCfgMgrBasePtr;
+
+
+}; // end of isc::d2 namespace
+}; // end of isc namespace
+
+#endif // D_CFG_MGR_H
diff --git a/src/bin/d2/d_controller.cc b/src/bin/d2/d_controller.cc
index b32fc45..921a07c 100644
--- a/src/bin/d2/d_controller.cc
+++ b/src/bin/d2/d_controller.cc
@@ -305,7 +305,7 @@ isc::data::ConstElementPtr
DControllerBase::updateConfig(isc::data::ConstElementPtr new_config) {
isc::data::ConstElementPtr full_config;
if (stand_alone_) {
- // @TODO Until there is a configuration manager to provide retrieval
+ // @todo Until there is a configuration manager to provide retrieval
// we'll just assume the incoming config is the full configuration set.
// It may also make more sense to isolate the controller from the
// configuration manager entirely. We could do something like
diff --git a/src/bin/d2/d_controller.h b/src/bin/d2/d_controller.h
index bf7a607..bdb02d5 100644
--- a/src/bin/d2/d_controller.h
+++ b/src/bin/d2/d_controller.h
@@ -171,7 +171,7 @@ public:
/// various configuration values. Installing the dummy handler
/// that guarantees to return success causes initial configuration
/// to be stored for the session being created and that it can
- /// be later accessed with \ref isc::ConfigData::getFullConfig.
+ /// be later accessed with \ref isc::config::ConfigData::getFullConfig.
///
/// @param new_config new configuration.
///
@@ -210,7 +210,7 @@ public:
/// implementation will merge the configuration update into the existing
/// configuration and then invoke the application process' configure method.
///
- /// @TODO This implementation is will evolve as the D2 configuration
+ /// @todo This implementation is will evolve as the D2 configuration
/// management task is implemented (trac #2957).
///
/// @param new_config is the new configuration
@@ -261,7 +261,7 @@ protected:
///
/// @param option is the option "character" from the command line, without
/// any prefixing hyphen(s)
- /// @optarg optarg is the argument value (if one) associated with the option
+ /// @param optarg is the argument value (if one) associated with the option
///
/// @return must return true if the option was valid, false is it is
/// invalid. (Note the default implementation always returns false.)
@@ -395,7 +395,7 @@ protected:
/// @brief Setter for setting the name of the controller's BIND10 spec file.
///
- /// @param value is the file name string.
+ /// @param spec_file_name the file name string.
void setSpecFileName(const std::string& spec_file_name) {
spec_file_name_ = spec_file_name;
}
diff --git a/src/bin/d2/d_process.h b/src/bin/d2/d_process.h
index 11b0a09..d1a7a55 100644
--- a/src/bin/d2/d_process.h
+++ b/src/bin/d2/d_process.h
@@ -17,6 +17,8 @@
#include <asiolink/asiolink.h>
#include <cc/data.h>
+#include <d2/d_cfg_mgr.h>
+
#include <boost/shared_ptr.hpp>
#include <exceptions/exceptions.h>
@@ -58,13 +60,21 @@ public:
/// in log statements, but otherwise arbitrary.
/// @param io_service is the io_service used by the caller for
/// asynchronous event handling.
+ /// @param cfg_mgr the configuration manager instance that handles
+ /// configuration parsing.
///
/// @throw DProcessBaseError is io_service is NULL.
- DProcessBase(const char* app_name, IOServicePtr io_service)
- : app_name_(app_name), io_service_(io_service), shut_down_flag_(false) {
+ DProcessBase(const char* app_name, IOServicePtr io_service,
+ DCfgMgrBasePtr cfg_mgr)
+ : app_name_(app_name), io_service_(io_service), shut_down_flag_(false),
+ cfg_mgr_(cfg_mgr) {
if (!io_service_) {
isc_throw (DProcessBaseError, "IO Service cannot be null");
}
+
+ if (!cfg_mgr_) {
+ isc_throw (DProcessBaseError, "CfgMgr cannot be null");
+ }
};
/// @brief May be used after instantiation to perform initialization unique
@@ -159,6 +169,13 @@ public:
io_service_->stop();
}
+ /// @brief Fetches the process's configuration manager.
+ ///
+ /// @return returns a reference to the configuration manager.
+ DCfgMgrBasePtr& getCfgMgr() {
+ return (cfg_mgr_);
+ }
+
private:
/// @brief Text label for the process. Generally used in log statements,
/// but otherwise can be arbitrary.
@@ -169,6 +186,9 @@ private:
/// @brief Boolean flag set when shutdown has been requested.
bool shut_down_flag_;
+
+ /// @brief Pointer to the configuration manager.
+ DCfgMgrBasePtr cfg_mgr_;
};
/// @brief Defines a shared pointer to DProcessBase.
diff --git a/src/bin/d2/dhcp-ddns.spec b/src/bin/d2/dhcp-ddns.spec
index 1098ada..e29bc88 100644
--- a/src/bin/d2/dhcp-ddns.spec
+++ b/src/bin/d2/dhcp-ddns.spec
@@ -1,21 +1,205 @@
+{
+"module_spec":
{
- "module_spec": {
"module_name": "DhcpDdns",
"module_description": "DHPC-DDNS Service",
"config_data": [
- ],
+ {
+ "item_name": "interface",
+ "item_type": "string",
+ "item_optional": true,
+ "item_default": "eth0"
+ },
+
+ {
+ "item_name": "ip_address",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "127.0.0.1"
+ },
+
+ {
+ "item_name": "port",
+ "item_type": "integer",
+ "item_optional": true,
+ "item_default": 51771
+ },
+ {
+ "item_name": "tsig_keys",
+ "item_type": "list",
+ "item_optional": true,
+ "item_default": [],
+ "list_item_spec":
+ {
+ "item_name": "tsig_key",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {"algorithm" : "hmac_md5"},
+ "map_item_spec": [
+ {
+ "item_name": "name",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ },
+ {
+ "item_name": "algorithm",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ },
+ {
+ "item_name": "secret",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ }]
+ }
+ },
+ {
+ "item_name": "forward_ddns",
+ "item_type": "map",
+ "item_optional": true,
+ "item_default": {},
+ "map_item_spec": [
+ {
+ "item_name": "ddns_domains",
+ "item_type": "list",
+ "item_optional": false,
+ "item_default": [],
+ "list_item_spec":
+ {
+ "item_name": "ddns_domain",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "map_item_spec": [
+ {
+ "item_name": "name",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ },
+
+ {
+ "item_name": "key_name",
+ "item_type": "string",
+ "item_optional": true,
+ "item_default": ""
+ },
+
+ {
+ "item_name": "dns_servers",
+ "item_type": "list",
+ "item_optional": false,
+ "item_default": [],
+ "list_item_spec":
+ {
+ "item_name": "dns_server",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "map_item_spec": [
+ {
+ "item_name": "hostname",
+ "item_type": "string",
+ "item_optional": true,
+ "item_default": ""
+ },
+ {
+ "item_name": "ip_address",
+ "item_type": "string",
+ "item_optional": true,
+ "item_default": ""
+ },
+ {
+ "item_name": "port",
+ "item_type": "integer",
+ "item_optional": true,
+ "item_default": 53
+ }]
+ }
+ }]
+ }
+ }]
+ },
+
+ {
+ "item_name": "reverse_ddns",
+ "item_type": "map",
+ "item_optional": true,
+ "item_default": {},
+ "map_item_spec": [
+ {
+ "item_name": "ddns_domains",
+ "item_type": "list",
+ "item_optional": false,
+ "item_default": [],
+ "list_item_spec":
+ {
+ "item_name": "ddns_domain",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "map_item_spec": [
+ {
+ "item_name": "name",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ },
+
+ {
+ "item_name": "key_name",
+ "item_type": "string",
+ "item_optional": true,
+ "item_default": ""
+ },
+
+ {
+ "item_name": "dns_servers",
+ "item_type": "list",
+ "item_optional": false,
+ "item_default": [],
+ "list_item_spec":
+ {
+ "item_name": "dns_server",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "map_item_spec": [
+ {
+ "item_name": "hostname",
+ "item_type": "string",
+ "item_optional": true,
+ "item_default": ""
+ },
+ {
+ "item_name": "ip_address",
+ "item_type": "string",
+ "item_optional": true,
+ "item_default": ""
+ },
+ {
+ "item_name": "port",
+ "item_type": "integer",
+ "item_optional": true,
+ "item_default": 53
+ }]
+ }
+ }]
+ }
+ }]
+ }],
+
"commands": [
- {
- "command_name": "shutdown",
- "command_description": "Shut down the DHCP-DDNS service",
- "command_args": [
- {
- "item_name": "pid",
- "item_type": "integer",
- "item_optional": true
- }
- ]
- }
+ {
+ "command_name": "shutdown",
+ "command_description": "Shuts down DHCPv6 server.",
+ "command_args": [
+ ]
+ }
]
}
}
+
diff --git a/src/bin/d2/ncr_msg.cc b/src/bin/d2/ncr_msg.cc
new file mode 100644
index 0000000..c1254cb
--- /dev/null
+++ b/src/bin/d2/ncr_msg.cc
@@ -0,0 +1,485 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <d2/ncr_msg.h>
+#include <asiolink/io_address.h>
+#include <asiolink/io_error.h>
+
+#include <sstream>
+
+namespace isc {
+namespace d2 {
+
+using namespace boost::posix_time;
+
+/********************************* D2Dhcid ************************************/
+
+D2Dhcid::D2Dhcid() {
+}
+
+D2Dhcid::D2Dhcid(const std::string& data) {
+ fromStr(data);
+}
+
+void
+D2Dhcid::fromStr(const std::string& data) {
+ bytes_.clear();
+ try {
+ isc::util::encode::decodeHex(data, bytes_);
+ } catch (const isc::Exception& ex) {
+ isc_throw(NcrMessageError, "Invalid data in Dhcid:" << ex.what());
+ }
+}
+
+std::string
+D2Dhcid::toStr() const {
+ return (isc::util::encode::encodeHex(bytes_));
+
+
+}
+
+
+/**************************** NameChangeRequest ******************************/
+
+NameChangeRequest::NameChangeRequest()
+ : change_type_(CHG_ADD), forward_change_(false),
+ reverse_change_(false), fqdn_(""), ip_address_(""),
+ dhcid_(), lease_expires_on_(), lease_length_(0), status_(ST_NEW) {
+}
+
+NameChangeRequest::NameChangeRequest(const NameChangeType change_type,
+ const bool forward_change, const bool reverse_change,
+ const std::string& fqdn, const std::string& ip_address,
+ const D2Dhcid& dhcid,
+ const boost::posix_time::ptime& lease_expires_on,
+ const uint32_t lease_length)
+ : change_type_(change_type), forward_change_(forward_change),
+ reverse_change_(reverse_change), fqdn_(fqdn), ip_address_(ip_address),
+ dhcid_(dhcid), lease_expires_on_(new ptime(lease_expires_on)),
+ lease_length_(lease_length), status_(ST_NEW) {
+
+ // Validate the contents. This will throw a NcrMessageError if anything
+ // is invalid.
+ validateContent();
+}
+
+NameChangeRequestPtr
+NameChangeRequest::fromFormat(const NameChangeFormat format,
+ isc::util::InputBuffer& buffer) {
+ // Based on the format requested, pull the marshalled request from
+ // InputBuffer and pass it into the appropriate format-specific factory.
+ NameChangeRequestPtr ncr;
+ switch (format) {
+ case FMT_JSON: {
+ try {
+ // Get the length of the JSON text.
+ size_t len = buffer.readUint16();
+
+ // Read the text from the buffer into a vector.
+ std::vector<uint8_t> vec;
+ buffer.readVector(vec, len);
+
+ // Turn the vector into a string.
+ std::string string_data(vec.begin(), vec.end());
+
+ // Pass the string of JSON text into JSON factory to create the
+ // NameChangeRequest instance. Note the factory may throw
+ // NcrMessageError.
+ ncr = NameChangeRequest::fromJSON(string_data);
+ } catch (isc::util::InvalidBufferPosition& ex) {
+ // Read error accessing data in InputBuffer.
+ isc_throw(NcrMessageError, "fromFormat: buffer read error: "
+ << ex.what());
+ }
+
+ break;
+ }
+ default:
+ // Programmatic error, shouldn't happen.
+ isc_throw(NcrMessageError, "fromFormat - invalid format");
+ break;
+ }
+
+ return (ncr);
+}
+
+void
+NameChangeRequest::toFormat(const NameChangeFormat format,
+ isc::util::OutputBuffer& buffer) const {
+ // Based on the format requested, invoke the appropriate format handler
+ // which will marshal this request's contents into the OutputBuffer.
+ switch (format) {
+ case FMT_JSON: {
+ // Invoke toJSON to create a JSON text of this request's contents.
+ std::string json = toJSON();
+ uint16_t length = json.size();
+
+ // Write the length of the JSON text to the OutputBuffer first, then
+ // write the JSON text itself.
+ buffer.writeUint16(length);
+ buffer.writeData(json.c_str(), length);
+ break;
+ }
+ default:
+ // Programmatic error, shouldn't happen.
+ isc_throw(NcrMessageError, "toFormat - invalid format");
+ break;
+ }
+}
+
+NameChangeRequestPtr
+NameChangeRequest::fromJSON(const std::string& json) {
+ // This method leverages the existing JSON parsing provided by isc::data
+ // library. Should this prove to be a performance issue, it may be that
+ // lighter weight solution would be appropriate.
+
+ // Turn the string of JSON text into an Element set.
+ isc::data::ElementPtr elements;
+ try {
+ elements = isc::data::Element::fromJSON(json);
+ } catch (isc::data::JSONError& ex) {
+ isc_throw(NcrMessageError,
+ "Malformed NameChangeRequest JSON: " << ex.what());
+ }
+
+ // Get a map of the Elements, keyed by element name.
+ ElementMap element_map = elements->mapValue();
+ isc::data::ConstElementPtr element;
+
+ // Use default constructor to create a "blank" NameChangeRequest.
+ NameChangeRequestPtr ncr(new NameChangeRequest());
+
+ // For each member of NameChangeRequest, find its element in the map and
+ // call the appropriate Element-based setter. These setters may throw
+ // NcrMessageError if the given Element is the wrong type or its data
+ // content is lexically invalid. If the element is NOT found in the
+ // map, getElement will throw NcrMessageError indicating the missing
+ // member. Currently there are no optional values.
+ element = ncr->getElement("change_type", element_map);
+ ncr->setChangeType(element);
+
+ element = ncr->getElement("forward_change", element_map);
+ ncr->setForwardChange(element);
+
+ element = ncr->getElement("reverse_change", element_map);
+ ncr->setReverseChange(element);
+
+ element = ncr->getElement("fqdn", element_map);
+ ncr->setFqdn(element);
+
+ element = ncr->getElement("ip_address", element_map);
+ ncr->setIpAddress(element);
+
+ element = ncr->getElement("dhcid", element_map);
+ ncr->setDhcid(element);
+
+ element = ncr->getElement("lease_expires_on", element_map);
+ ncr->setLeaseExpiresOn(element);
+
+ element = ncr->getElement("lease_length", element_map);
+ ncr->setLeaseLength(element);
+
+ // All members were in the Element set and were correct lexically. Now
+ // validate the overall content semantically. This will throw an
+ // NcrMessageError if anything is amiss.
+ ncr->validateContent();
+
+ // Everything is valid, return the new instance.
+ return (ncr);
+}
+
+std::string
+NameChangeRequest::toJSON() const {
+ // Create a JSON string of this request's contents. Note that this method
+ // does NOT use the isc::data library as generating the output is straight
+ // forward.
+ std::ostringstream stream;
+
+ stream << "{\"change_type\":" << getChangeType() << ","
+ << "\"forward_change\":"
+ << (isForwardChange() ? "true" : "false") << ","
+ << "\"reverse_change\":"
+ << (isReverseChange() ? "true" : "false") << ","
+ << "\"fqdn\":\"" << getFqdn() << "\","
+ << "\"ip_address\":\"" << getIpAddress() << "\","
+ << "\"dhcid\":\"" << getDhcid().toStr() << "\","
+ << "\"lease_expires_on\":\"" << getLeaseExpiresOnStr() << "\","
+ << "\"lease_length\":" << getLeaseLength() << "}";
+
+ return (stream.str());
+}
+
+
+void
+NameChangeRequest::validateContent() {
+ //@todo This is an initial implementation which provides a minimal amount
+ // of validation. FQDN, DHCID, and IP Address members are all currently
+ // strings, these may be replaced with richer classes.
+ if (fqdn_ == "") {
+ isc_throw(NcrMessageError, "FQDN cannot be blank");
+ }
+
+ // Validate IP Address.
+ try {
+ isc::asiolink::IOAddress io_addr(ip_address_);
+ } catch (const isc::asiolink::IOError& ex) {
+ isc_throw(NcrMessageError,
+ "Invalid ip address string for ip_address: " << ip_address_);
+ }
+
+ // Validate the DHCID.
+ if (dhcid_.getBytes().size() == 0) {
+ isc_throw(NcrMessageError, "DHCID cannot be blank");
+ }
+
+ // Validate lease expiration.
+ if (lease_expires_on_->is_not_a_date_time()) {
+ isc_throw(NcrMessageError, "Invalid value for lease_expires_on");
+ }
+
+ // Ensure the request specifies at least one direction to update.
+ if (!forward_change_ && !reverse_change_) {
+ isc_throw(NcrMessageError,
+ "Invalid Request, forward and reverse flags are both false");
+ }
+}
+
+isc::data::ConstElementPtr
+NameChangeRequest::getElement(const std::string& name,
+ const ElementMap& element_map) const {
+ // Look for "name" in the element map.
+ ElementMap::const_iterator it = element_map.find(name);
+ if (it == element_map.end()) {
+ // Didn't find the element, so throw.
+ isc_throw(NcrMessageError,
+ "NameChangeRequest value missing for: " << name );
+ }
+
+ // Found the element, return it.
+ return (it->second);
+}
+
+void
+NameChangeRequest::setChangeType(const NameChangeType value) {
+ change_type_ = value;
+}
+
+
+void
+NameChangeRequest::setChangeType(isc::data::ConstElementPtr element) {
+ long raw_value = -1;
+ try {
+ // Get the element's integer value.
+ raw_value = element->intValue();
+ } catch (isc::data::TypeError& ex) {
+ // We expect a integer Element type, don't have one.
+ isc_throw(NcrMessageError,
+ "Wrong data type for change_type: " << ex.what());
+ }
+
+ if ((raw_value != CHG_ADD) && (raw_value != CHG_REMOVE)) {
+ // Value is not a valid change type.
+ isc_throw(NcrMessageError,
+ "Invalid data value for change_type: " << raw_value);
+ }
+
+ // Good to go, make the assignment.
+ setChangeType(static_cast<NameChangeType>(raw_value));
+}
+
+void
+NameChangeRequest::setForwardChange(const bool value) {
+ forward_change_ = value;
+}
+
+void
+NameChangeRequest::setForwardChange(isc::data::ConstElementPtr element) {
+ bool value;
+ try {
+ // Get the element's boolean value.
+ value = element->boolValue();
+ } catch (isc::data::TypeError& ex) {
+ // We expect a boolean Element type, don't have one.
+ isc_throw(NcrMessageError,
+ "Wrong data type for forward_change :" << ex.what());
+ }
+
+ // Good to go, make the assignment.
+ setForwardChange(value);
+}
+
+void
+NameChangeRequest::setReverseChange(const bool value) {
+ reverse_change_ = value;
+}
+
+void
+NameChangeRequest::setReverseChange(isc::data::ConstElementPtr element) {
+ bool value;
+ try {
+ // Get the element's boolean value.
+ value = element->boolValue();
+ } catch (isc::data::TypeError& ex) {
+ // We expect a boolean Element type, don't have one.
+ isc_throw(NcrMessageError,
+ "Wrong data type for reverse_change :" << ex.what());
+ }
+
+ // Good to go, make the assignment.
+ setReverseChange(value);
+}
+
+
+void
+NameChangeRequest::setFqdn(isc::data::ConstElementPtr element) {
+ setFqdn(element->stringValue());
+}
+
+void
+NameChangeRequest::setFqdn(const std::string& value) {
+ fqdn_ = value;
+}
+
+void
+NameChangeRequest::setIpAddress(const std::string& value) {
+ ip_address_ = value;
+}
+
+
+void
+NameChangeRequest::setIpAddress(isc::data::ConstElementPtr element) {
+ setIpAddress(element->stringValue());
+}
+
+
+void
+NameChangeRequest::setDhcid(const std::string& value) {
+ dhcid_.fromStr(value);
+}
+
+void
+NameChangeRequest::setDhcid(isc::data::ConstElementPtr element) {
+ setDhcid(element->stringValue());
+}
+
+std::string
+NameChangeRequest::getLeaseExpiresOnStr() const {
+ if (!lease_expires_on_) {
+ // This is a programmatic error, should not happen.
+ isc_throw(NcrMessageError,
+ "lease_expires_on_ is null, cannot convert to string");
+ }
+
+ // Return the ISO date-time string for the value of lease_expires_on_.
+ return (to_iso_string(*lease_expires_on_));
+}
+
+void
+NameChangeRequest::setLeaseExpiresOn(const std::string& value) {
+ try {
+ // Create a new ptime instance from the ISO date-time string in value
+ // add assign it to lease_expires_on_.
+ ptime* tptr = new ptime(from_iso_string(value));
+ lease_expires_on_.reset(tptr);
+ } catch(...) {
+ // We were given an invalid string, so throw.
+ isc_throw(NcrMessageError,
+ "Invalid ISO date-time string: [" << value << "]");
+ }
+
+}
+
+void
+NameChangeRequest::setLeaseExpiresOn(const boost::posix_time::ptime& value) {
+ if (lease_expires_on_->is_not_a_date_time()) {
+ isc_throw(NcrMessageError, "Invalid value for lease_expires_on");
+ }
+
+ // Go to go, make the assignment.
+ lease_expires_on_.reset(new ptime(value));
+}
+
+void NameChangeRequest::setLeaseExpiresOn(isc::data::ConstElementPtr element) {
+ // Pull out the string value and pass it into the string setter.
+ setLeaseExpiresOn(element->stringValue());
+}
+
+void
+NameChangeRequest::setLeaseLength(const uint32_t value) {
+ lease_length_ = value;
+}
+
+void
+NameChangeRequest::setLeaseLength(isc::data::ConstElementPtr element) {
+ long value = -1;
+ try {
+ // Get the element's integer value.
+ value = element->intValue();
+ } catch (isc::data::TypeError& ex) {
+ // We expect a integer Element type, don't have one.
+ isc_throw(NcrMessageError,
+ "Wrong data type for lease_length: " << ex.what());
+ }
+
+ // Make sure we the range is correct and value is positive.
+ if (value > std::numeric_limits<uint32_t>::max()) {
+ isc_throw(NcrMessageError, "lease_length value " << value <<
+ "is too large for unsigned 32-bit integer.");
+ }
+ if (value < 0) {
+ isc_throw(NcrMessageError, "lease_length value " << value <<
+ "is negative. It must greater than or equal to zero ");
+ }
+
+ // Good to go, make the assignment.
+ setLeaseLength(static_cast<uint32_t>(value));
+}
+
+void
+NameChangeRequest::setStatus(const NameChangeStatus value) {
+ status_ = value;
+}
+
+std::string
+NameChangeRequest::toText() const {
+ std::ostringstream stream;
+
+ stream << "Type: " << static_cast<int>(change_type_) << " (";
+ switch (change_type_) {
+ case CHG_ADD:
+ stream << "CHG_ADD)\n";
+ break;
+ case CHG_REMOVE:
+ stream << "CHG_REMOVE)\n";
+ break;
+ default:
+ // Shouldn't be possible.
+ stream << "Invalid Value\n";
+ }
+
+ stream << "Forward Change: " << (forward_change_ ? "yes" : "no")
+ << std::endl
+ << "Reverse Change: " << (reverse_change_ ? "yes" : "no")
+ << std::endl
+ << "FQDN: [" << fqdn_ << "]" << std::endl
+ << "IP Address: [" << ip_address_ << "]" << std::endl
+ << "DHCID: [" << dhcid_.toStr() << "]" << std::endl
+ << "Lease Expires On: " << getLeaseExpiresOnStr() << std::endl
+ << "Lease Length: " << lease_length_ << std::endl;
+
+ return (stream.str());
+}
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
diff --git a/src/bin/d2/ncr_msg.h b/src/bin/d2/ncr_msg.h
new file mode 100644
index 0000000..5e7846b
--- /dev/null
+++ b/src/bin/d2/ncr_msg.h
@@ -0,0 +1,503 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef NCR_MSG_H
+#define NCR_MSG_H
+
+/// @file ncr_msg.h
+/// @brief This file provides the classes needed to embody, compose, and
+/// decompose DNS update requests that are sent by DHCP-DDNS clients to
+/// DHCP-DDNS. These requests are referred to as NameChangeRequests.
+
+#include <cc/data.h>
+#include <exceptions/exceptions.h>
+#include <util/buffer.h>
+#include <util/encode/hex.h>
+
+#include <boost/date_time/posix_time/posix_time.hpp>
+
+#include <time.h>
+#include <string>
+
+namespace isc {
+namespace d2 {
+
+/// @brief Exception thrown when NameChangeRequest marshalling error occurs.
+class NcrMessageError : public isc::Exception {
+public:
+ NcrMessageError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Defines the types of DNS updates that can be requested.
+enum NameChangeType {
+ CHG_ADD,
+ CHG_REMOVE
+};
+
+/// @brief Defines the runtime processing status values for requests.
+enum NameChangeStatus {
+ ST_NEW,
+ ST_PENDING,
+ ST_COMPLETED,
+ ST_FAILED,
+};
+
+/// @brief Defines the list of data wire formats supported.
+enum NameChangeFormat {
+ FMT_JSON
+};
+
+/// @brief Container class for handling the DHCID value within a
+/// NameChangeRequest. It provides conversion to and from string for JSON
+/// formatting, but stores the data internally as unsigned bytes.
+class D2Dhcid {
+public:
+ /// @brief Default constructor
+ D2Dhcid();
+
+ /// @brief Constructor - Creates a new instance, populated by converting
+ /// a given string of digits into an array of unsigned bytes.
+ ///
+ /// @param data is a string of hexadecimal digits. The format is simply
+ /// a contiguous stream of digits, with no delimiters. For example a string
+ /// containing "14A3" converts to a byte array containing: 0x14, 0xA3.
+ ///
+ /// @throw throws a NcrMessageError if the input data contains non-digits
+ /// or there is an odd number of digits.
+ D2Dhcid(const std::string& data);
+
+ /// @brief Returns the DHCID value as a string of hexadecimal digits.
+ ///
+ /// @return returns a string containing a contiguous stream of digits.
+ std::string toStr() const;
+
+ /// @brief Sets the DHCID value based on the given string.
+ ///
+ /// @param data is a string of hexadecimal digits. The format is simply
+ /// a contiguous stream of digits, with no delimiters. For example a string
+ /// containing "14A3" converts to a byte array containing: 0x14, 0xA3.
+ ///
+ /// @throw throws a NcrMessageError if the input data contains non-digits
+ /// or there is an odd number of digits.
+ void fromStr(const std::string& data);
+
+ /// @brief Returns a reference to the DHCID byte vector.
+ ///
+ /// @return returns a reference to the vector.
+ const std::vector<uint8_t>& getBytes() {
+ return (bytes_);
+ }
+
+private:
+ /// @brief Storage for the DHCID value in unsigned bytes.
+ std::vector<uint8_t> bytes_;
+};
+
+/// @brief Defines a pointer to a ptime.
+/// NameChangeRequest member(s) that are timestamps are ptime instances.
+/// Boost ptime was chosen because it supports converting to and from ISO
+/// strings in GMT. The Unix style time.h classes convert to GMT but
+/// conversion back assumes local time. This is problematic if the "wire"
+/// format is string (i.e. JSON) and the request were to cross time zones.
+/// Additionally, time_t values should never be used directly so shipping them
+/// as string integers across platforms could theoretically be a problem.
+typedef boost::shared_ptr<boost::posix_time::ptime> TimePtr;
+
+class NameChangeRequest;
+/// @brief Defines a pointer to a NameChangeRequest.
+typedef boost::shared_ptr<NameChangeRequest> NameChangeRequestPtr;
+
+/// @brief Defines a map of Elements, keyed by their string name.
+typedef std::map<std::string, isc::data::ConstElementPtr> ElementMap;
+
+/// @brief Represents a DHCP-DDNS client request.
+/// This class is used by DHCP-DDNS clients (e.g. DHCP4, DHCP6) to
+/// request DNS updates. Each message contains a single DNS change (either an
+/// add/update or a remove) for a single FQDN. It provides marshalling services
+/// for moving instances to and from the wire. Currently, the only format
+/// supported is JSON, however the class provides an interface such that other
+/// formats can be readily supported.
+class NameChangeRequest {
+public:
+ /// @brief Default Constructor.
+ NameChangeRequest();
+
+ /// @brief Constructor. Full constructor, which provides parameters for
+ /// all of the class members, except status.
+ ///
+ /// @param change_type the type of change (Add or Update)
+ /// @param forward_change indicates if this change should be sent to forward
+ /// DNS servers.
+ /// @param reverse_change indicates if this change should be sent to reverse
+ /// DNS servers.
+ /// @param fqdn the domain name whose pointer record(s) should be
+ /// updated.
+ /// @param ip_address the ip address leased to the given FQDN.
+ /// @param dhcid the lease client's unique DHCID.
+ /// @param lease_expires_on a timestamp containing the date/time the lease
+ /// expires.
+ /// @param lease_length the amount of time in seconds for which the
+ /// lease is valid (TTL).
+ NameChangeRequest(const NameChangeType change_type,
+ const bool forward_change, const bool reverse_change,
+ const std::string& fqdn, const std::string& ip_address,
+ const D2Dhcid& dhcid,
+ const boost::posix_time::ptime& lease_expires_on,
+ const uint32_t lease_length);
+
+ /// @brief Static method for creating a NameChangeRequest from a
+ /// buffer containing a marshalled request in a given format.
+ ///
+ /// When the format is:
+ ///
+ /// JSON: The buffer is expected to contain a two byte unsigned integer
+ /// which specified the length of the JSON text; followed by the JSON
+ /// text itself. This method attempts to extract "length" characters
+ /// from the buffer. This data is used to create a character string that
+ /// is than treated as JSON which is then parsed into the data needed
+ /// to create a request instance.
+ ///
+ /// (NOTE currently only JSON is supported.)
+ ///
+ /// @param format indicates the data format to use
+ /// @param buffer is the input buffer containing the marshalled request
+ ///
+ /// @return returns a pointer to the new NameChangeRequest
+ ///
+ /// @throw throws NcrMessageError if an error occurs creating new
+ /// request.
+ static NameChangeRequestPtr fromFormat(const NameChangeFormat format,
+ isc::util::InputBuffer& buffer);
+
+ /// @brief Instance method for marshalling the contents of the request
+ /// into the given buffer in the given format.
+ ///
+ /// When the format is:
+ ///
+ /// JSON: Upon completion, the buffer will contain a two byte unsigned
+ /// integer which specifies the length of the JSON text; followed by the
+ /// JSON text itself. The JSON text contains the names and values for all
+ /// the request data needed to reassemble the request on the receiving
+ /// end. The JSON text in the buffer is NOT null-terminated.
+ ///
+ /// (NOTE currently only JSON is supported.)
+ ///
+ /// @param format indicates the data format to use
+ /// @param buffer is the output buffer to which the request should be
+ /// marshalled.
+ void toFormat(const NameChangeFormat format,
+ isc::util::OutputBuffer& buffer) const;
+
+ /// @brief Static method for creating a NameChangeRequest from a
+ /// string containing a JSON rendition of a request.
+ ///
+ /// @param json is a string containing the JSON text
+ ///
+ /// @return returns a pointer to the new NameChangeRequest
+ ///
+ /// @throw throws NcrMessageError if an error occurs creating new request.
+ static NameChangeRequestPtr fromJSON(const std::string& json);
+
+ /// @brief Instance method for marshalling the contents of the request
+ /// into a string of JSON text.
+ ///
+ /// @return returns a string containing the JSON rendition of the request
+ std::string toJSON() const;
+
+ /// @brief Validates the content of a populated request. This method is
+ /// used by both the full constructor and from-wire marshalling to ensure
+ /// that the request is content valid. Currently it enforces the
+ /// following rules:
+ ///
+ /// - FQDN must not be blank.
+ /// - The IP address must be a valid address.
+ /// - The DHCID must not be blank.
+ /// - The lease expiration date must be a valid date/time.
+ /// - That at least one of the two direction flags, forward change and
+ /// reverse change is true.
+ ///
+ /// @todo This is an initial implementation which provides a minimal amount
+ /// of validation. FQDN, DHCID, and IP Address members are all currently
+ /// strings, these may be replaced with richer classes.
+ ///
+ /// @throw throws a NcrMessageError if the request content violates any
+ /// of the validation rules.
+ void validateContent();
+
+ /// @brief Fetches the request change type.
+ ///
+ /// @return returns the change type
+ NameChangeType getChangeType() const {
+ return (change_type_);
+ }
+
+ /// @brief Sets the change type to the given value.
+ ///
+ /// @param value is the NameChangeType value to assign to the request.
+ void setChangeType(const NameChangeType value);
+
+ /// @brief Sets the change type to the value of the given Element.
+ ///
+ /// @param element is an integer Element containing the change type value.
+ ///
+ /// @throw throws a NcrMessageError if the element is not an integer
+ /// Element or contains an invalid value.
+ void setChangeType(isc::data::ConstElementPtr element);
+
+ /// @brief Checks forward change flag.
+ ///
+ /// @return returns a true if the forward change flag is true.
+ bool isForwardChange() const {
+ return (forward_change_);
+ }
+
+ /// @brief Sets the forward change flag to the given value.
+ ///
+ /// @param value contains the new value to assign to the forward change
+ /// flag
+ void setForwardChange(const bool value);
+
+ /// @brief Sets the forward change flag to the value of the given Element.
+ ///
+ /// @param element is a boolean Element containing the forward change flag
+ /// value.
+ ///
+ /// @throw throws a NcrMessageError if the element is not a boolean
+ /// Element
+ void setForwardChange(isc::data::ConstElementPtr element);
+
+ /// @brief Checks reverse change flag.
+ ///
+ /// @return returns a true if the reverse change flag is true.
+ bool isReverseChange() const {
+ return (reverse_change_);
+ }
+
+ /// @brief Sets the reverse change flag to the given value.
+ ///
+ /// @param value contains the new value to assign to the reverse change
+ /// flag
+ void setReverseChange(const bool value);
+
+ /// @brief Sets the reverse change flag to the value of the given Element.
+ ///
+ /// @param element is a boolean Element containing the reverse change flag
+ /// value.
+ ///
+ /// @throw throws a NcrMessageError if the element is not a boolean
+ /// Element
+ void setReverseChange(isc::data::ConstElementPtr element);
+
+ /// @brief Fetches the request FQDN
+ ///
+ /// @return returns a string containing the FQDN
+ const std::string getFqdn() const {
+ return (fqdn_);
+ }
+
+ /// @brief Sets the FQDN to the given value.
+ ///
+ /// @param value contains the new value to assign to the FQDN
+ void setFqdn(const std::string& value);
+
+ /// @brief Sets the FQDN to the value of the given Element.
+ ///
+ /// @param element is a string Element containing the FQDN
+ ///
+ /// @throw throws a NcrMessageError if the element is not a string
+ /// Element
+ void setFqdn(isc::data::ConstElementPtr element);
+
+ /// @brief Fetches the request IP address.
+ ///
+ /// @return returns a string containing the IP address
+ const std::string& getIpAddress() const {
+ return (ip_address_);
+ }
+
+ /// @brief Sets the IP address to the given value.
+ ///
+ /// @param value contains the new value to assign to the IP address
+ void setIpAddress(const std::string& value);
+
+ /// @brief Sets the IP address to the value of the given Element.
+ ///
+ /// @param element is a string Element containing the IP address
+ ///
+ /// @throw throws a NcrMessageError if the element is not a string
+ /// Element
+ void setIpAddress(isc::data::ConstElementPtr element);
+
+ /// @brief Fetches the request DHCID
+ ///
+ /// @return returns a reference to the request's D2Dhcid
+ const D2Dhcid& getDhcid() const {
+ return (dhcid_);
+ }
+
+ /// @brief Sets the DHCID based on the given string value.
+ ///
+ /// @param value is a string of hexadecimal digits. The format is simply
+ /// a contiguous stream of digits, with no delimiters. For example a string
+ /// containing "14A3" converts to a byte array containing: 0x14, 0xA3.
+ ///
+ /// @throw throws a NcrMessageError if the input data contains non-digits
+ /// or there is an odd number of digits.
+ void setDhcid(const std::string& value);
+
+ /// @brief Sets the DHCID based on the value of the given Element.
+ ///
+ /// @param element is a string Element containing the string of hexadecimal
+ /// digits. (See setDhcid(std::string&) above.)
+ ///
+ /// @throw throws a NcrMessageError if the input data contains non-digits
+ /// or there is an odd number of digits.
+ void setDhcid(isc::data::ConstElementPtr element);
+
+ /// @brief Fetches the request lease expiration as a timestamp.
+ ///
+ /// @return returns a pointer to the ptime containing the lease expiration
+ const TimePtr& getLeaseExpiresOn() const {
+ return (lease_expires_on_);
+ }
+
+ /// @brief Fetches the request lease expiration as string.
+ ///
+ /// The format of the string returned is:
+ ///
+ /// YYYYMMDDTHHMMSS where T is the date-time separator
+ ///
+ /// Example: 18:54:54 June 26, 2013 would be: 20130626T185455
+ ///
+ /// @return returns a ISO date-time string of the lease expiration.
+ std::string getLeaseExpiresOnStr() const;
+
+ /// @brief Sets the lease expiration to given ptime value.
+ ///
+ /// @param value is the ptime value to assign to the lease expiration.
+ ///
+ /// @throw throws a NcrMessageError if the value is not a valid
+ /// timestamp.
+ void setLeaseExpiresOn(const boost::posix_time::ptime& value);
+
+ /// @brief Sets the lease expiration based on the given string.
+ ///
+ /// @param value is an ISO date-time string from which to set the
+ /// lease expiration. The format of the input is:
+ ///
+ /// YYYYMMDDTHHMMSS where T is the date-time separator
+ ///
+ /// Example: 18:54:54 June 26, 2013 would be: 20130626T185455
+ ///
+ /// @throw throws a NcrMessageError if the ISO string is invalid.
+ void setLeaseExpiresOn(const std::string& value);
+
+ /// @brief Sets the lease expiration based on the given Element.
+ ///
+ /// @param element is string Element containing an ISO date-time string.
+ ///
+ /// @throw throws a NcrMessageError if the element is not a string
+ /// Element, or if the element value is an invalid ISO date-time string.
+ void setLeaseExpiresOn(isc::data::ConstElementPtr element);
+
+ /// @brief Fetches the request lease length.
+ ///
+ /// @return returns an integer containing the lease length
+ uint32_t getLeaseLength() const {
+ return (lease_length_);
+ }
+
+ /// @brief Sets the lease length to the given value.
+ ///
+ /// @param value contains the new value to assign to the lease length
+ void setLeaseLength(const uint32_t value);
+
+ /// @brief Sets the lease length to the value of the given Element.
+ ///
+ /// @param element is a integer Element containing the lease length
+ ///
+ /// @throw throws a NcrMessageError if the element is not a string
+ /// Element
+ void setLeaseLength(isc::data::ConstElementPtr element);
+
+ /// @brief Fetches the request status.
+ ///
+ /// @return returns the request status as a NameChangeStatus
+ NameChangeStatus getStatus() const {
+ return (status_);
+ }
+
+ /// @brief Sets the request status to the given value.
+ ///
+ /// @param value contains the new value to assign to request status
+ void setStatus(const NameChangeStatus value);
+
+ /// @brief Given a name, finds and returns an element from a map of
+ /// elements.
+ ///
+ /// @param name is the name of the desired element
+ /// @param element_map is the map of elements to search
+ ///
+ /// @return returns a pointer to the element if located
+ /// @throw throws a NcrMessageError if the element cannot be found within
+ /// the map
+ isc::data::ConstElementPtr getElement(const std::string& name,
+ const ElementMap& element_map) const;
+
+ /// @brief Returns a text rendition of the contents of the request.
+ /// This method is primarily for logging purposes.
+ ///
+ /// @return returns a string containing the text.
+ std::string toText() const;
+
+private:
+ /// @brief Denotes the type of this change as either an Add or a Remove.
+ NameChangeType change_type_;
+
+ /// @brief Indicates if this change should sent to forward DNS servers.
+ bool forward_change_;
+
+ /// @brief Indicates if this change should sent to reverse DNS servers.
+ bool reverse_change_;
+
+ /// @brief The domain name whose DNS entry(ies) are to be updated.
+ /// @todo Currently, this is a std::string but may be replaced with
+ /// dns::Name which provides additional validation and domain name
+ /// manipulation.
+ std::string fqdn_;
+
+ /// @brief The ip address leased to the FQDN.
+ std::string ip_address_;
+
+ /// @brief The lease client's unique DHCID.
+ /// @todo Currently, this is uses D2Dhcid it but may be replaced with
+ /// dns::DHCID which provides additional validation.
+ D2Dhcid dhcid_;
+
+ /// @brief The date-time the lease expires.
+ TimePtr lease_expires_on_;
+
+ /// @brief The amount of time in seconds for which the lease is valid (TTL).
+ uint32_t lease_length_;
+
+ /// @brief The processing status of the request. Used internally.
+ NameChangeStatus status_;
+};
+
+
+}; // end of isc::d2 namespace
+}; // end of isc namespace
+
+#endif
diff --git a/src/bin/d2/tests/Makefile.am b/src/bin/d2/tests/Makefile.am
index bd7773b..53b284e 100644
--- a/src/bin/d2/tests/Makefile.am
+++ b/src/bin/d2/tests/Makefile.am
@@ -56,11 +56,22 @@ d2_unittests_SOURCES += ../d_process.h
d2_unittests_SOURCES += ../d_controller.cc ../d2_controller.h
d2_unittests_SOURCES += ../d2_process.cc ../d2_process.h
d2_unittests_SOURCES += ../d2_controller.cc ../d2_controller.h
+d2_unittests_SOURCES += ../d_cfg_mgr.cc ../d_cfg_mgr.h
+d2_unittests_SOURCES += ../d2_config.cc ../d2_config.h
+d2_unittests_SOURCES += ../d2_cfg_mgr.cc ../d2_cfg_mgr.h
+d2_unittests_SOURCES += ../d2_update_message.cc ../d2_update_message.h
+d2_unittests_SOURCES += ../d2_zone.cc ../d2_zone.h
+d2_unittests_SOURCES += ../ncr_msg.cc ../ncr_msg.h
d2_unittests_SOURCES += d_test_stubs.cc d_test_stubs.h
d2_unittests_SOURCES += d2_unittests.cc
d2_unittests_SOURCES += d2_process_unittests.cc
d2_unittests_SOURCES += d_controller_unittests.cc
d2_unittests_SOURCES += d2_controller_unittests.cc
+d2_unittests_SOURCES += d_cfg_mgr_unittests.cc
+d2_unittests_SOURCES += d2_cfg_mgr_unittests.cc
+d2_unittests_SOURCES += d2_update_message_unittests.cc
+d2_unittests_SOURCES += d2_zone_unittests.cc
+d2_unittests_SOURCES += ncr_unittests.cc
nodist_d2_unittests_SOURCES = ../d2_messages.h ../d2_messages.cc
d2_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
@@ -71,6 +82,10 @@ d2_unittests_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
d2_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
d2_unittests_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
d2_unittests_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/dhcpsrv/libb10-dhcpsrv.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/dns/libb10-dns++.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
+
endif
noinst_PROGRAMS = $(TESTS)
diff --git a/src/bin/d2/tests/d2_cfg_mgr_unittests.cc b/src/bin/d2/tests/d2_cfg_mgr_unittests.cc
new file mode 100644
index 0000000..9f5a660
--- /dev/null
+++ b/src/bin/d2/tests/d2_cfg_mgr_unittests.cc
@@ -0,0 +1,1238 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config/module_spec.h>
+#include <d2/d2_config.h>
+#include <d2/d2_cfg_mgr.h>
+#include <d_test_stubs.h>
+#include <test_data_files_config.h>
+
+#include <boost/foreach.hpp>
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::d2;
+
+namespace {
+
+std::string specfile(const std::string& name) {
+ return (std::string(D2_SRC_DIR) + "/" + name);
+}
+
+/// @brief Test fixture class for testing D2CfgMgr class.
+/// It maintains an member instance of D2CfgMgr and provides methods for
+/// converting JSON strings to configuration element sets, checking parse
+/// results, and accessing the configuration context.
+class D2CfgMgrTest : public ConfigParseTest {
+public:
+
+ /// @brief Constructor
+ D2CfgMgrTest():cfg_mgr_(new D2CfgMgr) {
+ }
+
+ /// @brief Destructor
+ ~D2CfgMgrTest() {
+ }
+
+ /// @brief Configuration manager instance.
+ D2CfgMgrPtr cfg_mgr_;
+};
+
+/// @brief Tests that the spec file is valid.
+/// Verifies that the BIND10 DHCP-DDNS configuration specification file
+// is valid.
+TEST(D2SpecTest, basicSpecTest) {
+ ASSERT_NO_THROW(isc::config::
+ moduleSpecFromFile(specfile("dhcp-ddns.spec")));
+}
+
+/// @brief Convenience function which compares the contents of the given
+/// DnsServerInfo against the given set of values.
+///
+/// It is structured in such a way that each value is checked, and output
+/// is generate for all that do not match.
+///
+/// @param server is a pointer to the server to check against.
+/// @param hostname is the value to compare against server's hostname_.
+/// @param ip_address is the string value to compare against server's
+/// ip_address_.
+/// @param port is the value to compare against server's port.
+///
+/// @return returns true if there is a match across the board, otherwise it
+/// returns false.
+bool checkServer(DnsServerInfoPtr server, const char* hostname,
+ const char *ip_address, uint32_t port)
+{
+ // Return value, assume its a match.
+ bool result = true;
+
+ if (!server) {
+ EXPECT_TRUE(server);
+ return false;
+ }
+
+ // Check hostname.
+ if (server->getHostname() != hostname) {
+ EXPECT_EQ(hostname, server->getHostname());
+ result = false;
+ }
+
+ // Check IP address.
+ if (server->getIpAddress().toText() != ip_address) {
+ EXPECT_EQ(ip_address, server->getIpAddress().toText());
+ result = false;
+ }
+
+ // Check port.
+ if (server->getPort() != port) {
+ EXPECT_EQ (port, server->getPort());
+ result = false;
+ }
+
+ return (result);
+}
+
+/// @brief Convenience function which compares the contents of the given
+/// TSIGKeyInfo against the given set of values.
+///
+/// It is structured in such a way that each value is checked, and output
+/// is generate for all that do not match.
+///
+/// @param key is a pointer to the key to check against.
+/// @param name is the value to compare against key's name_.
+/// @param algorithm is the string value to compare against key's algorithm.
+/// @param secret is the value to compare against key's secret.
+///
+/// @return returns true if there is a match across the board, otherwise it
+/// returns false.
+bool checkKey(TSIGKeyInfoPtr key, const char* name,
+ const char *algorithm, const char* secret)
+{
+ // Return value, assume its a match.
+ bool result = true;
+ if (!key) {
+ EXPECT_TRUE(key);
+ return false;
+ }
+
+ // Check name.
+ if (key->getName() != name) {
+ EXPECT_EQ(name, key->getName());
+ result = false;
+ }
+
+ // Check algorithm.
+ if (key->getAlgorithm() != algorithm) {
+ EXPECT_EQ(algorithm, key->getAlgorithm());
+ result = false;
+ }
+
+ // Check secret.
+ if (key->getSecret() != secret) {
+ EXPECT_EQ (secret, key->getSecret());
+ result = false;
+ }
+
+ return (result);
+}
+
+/// @brief Test fixture class for testing DnsServerInfo parsing.
+class TSIGKeyInfoTest : public ConfigParseTest {
+public:
+
+ /// @brief Constructor
+ TSIGKeyInfoTest() {
+ reset();
+ }
+
+ /// @brief Destructor
+ ~TSIGKeyInfoTest() {
+ }
+
+ /// @brief Wipe out the current storage and parser and replace
+ /// them with new ones.
+ void reset() {
+ keys_.reset(new TSIGKeyInfoMap());
+ parser_.reset(new TSIGKeyInfoParser("test", keys_));
+ }
+
+ /// @brief Storage for "committing" keys.
+ TSIGKeyInfoMapPtr keys_;
+
+ /// @brief Pointer to the current parser instance.
+ isc::dhcp::ParserPtr parser_;
+};
+
+/// @brief Test fixture class for testing DnsServerInfo parsing.
+class DnsServerInfoTest : public ConfigParseTest {
+public:
+
+ /// @brief Constructor
+ DnsServerInfoTest() {
+ reset();
+ }
+
+ /// @brief Destructor
+ ~DnsServerInfoTest() {
+ }
+
+ /// @brief Wipe out the current storage and parser and replace
+ /// them with new ones.
+ void reset() {
+ servers_.reset(new DnsServerInfoStorage());
+ parser_.reset(new DnsServerInfoParser("test", servers_));
+ }
+
+ /// @brief Storage for "committing" servers.
+ DnsServerInfoStoragePtr servers_;
+
+ /// @brief Pointer to the current parser instance.
+ isc::dhcp::ParserPtr parser_;
+};
+
+
+/// @brief Test fixture class for testing DDnsDomain parsing.
+class DdnsDomainTest : public ConfigParseTest {
+public:
+
+ /// @brief Constructor
+ DdnsDomainTest() {
+ reset();
+ }
+
+ /// @brief Destructor
+ ~DdnsDomainTest() {
+ }
+
+ /// @brief Wipe out the current storage and parser and replace
+ /// them with new ones.
+ void reset() {
+ keys_.reset(new TSIGKeyInfoMap());
+ domains_.reset(new DdnsDomainMap());
+ parser_.reset(new DdnsDomainParser("test", domains_, keys_));
+ }
+
+ /// @brief Add TSIGKeyInfos to the key map
+ ///
+ /// @param name the name of the key
+ /// @param algorithm the algorithm of the key
+ /// @param secret the secret value of the key
+ void addKey(const std::string& name, const std::string& algorithm,
+ const std::string& secret) {
+ TSIGKeyInfoPtr key_info(new TSIGKeyInfo(name, algorithm, secret));
+ (*keys_)[name]=key_info;
+ }
+
+ /// @brief Storage for "committing" domains.
+ DdnsDomainMapPtr domains_;
+
+ /// @brief Storage for TSIGKeys
+ TSIGKeyInfoMapPtr keys_;
+
+ /// @brief Pointer to the current parser instance.
+ isc::dhcp::ParserPtr parser_;
+};
+
+/// @brief Tests the enforcement of data validation when parsing TSIGKeyInfos.
+/// It verifies that:
+/// 1. Name cannot be blank.
+/// 2. Algorithm cannot be blank.
+/// 3. Secret cannot be blank.
+/// @TODO TSIG keys are not fully functional. Only basic validation is
+/// currently supported. This test will need to expand as they evolve.
+TEST_F(TSIGKeyInfoTest, invalidEntryTests) {
+ // Config with a blank name entry.
+ std::string config = "{"
+ " \"name\": \"\" , "
+ " \"algorithm\": \"md5\" , "
+ " \"secret\": \"0123456789\" "
+ "}";
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that build succeeds but commit fails on blank name.
+ EXPECT_NO_THROW(parser_->build(config_set_));
+ EXPECT_THROW(parser_->commit(), D2CfgError);
+
+ // Config with a blank algorithm entry.
+ config = "{"
+ " \"name\": \"d2_key_one\" , "
+ " \"algorithm\": \"\" , "
+ " \"secret\": \"0123456789\" "
+ "}";
+
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that build succeeds but commit fails on blank algorithm.
+ EXPECT_NO_THROW(parser_->build(config_set_));
+ EXPECT_THROW(parser_->commit(), D2CfgError);
+
+ // Config with a blank secret entry.
+ config = "{"
+ " \"name\": \"d2_key_one\" , "
+ " \"algorithm\": \"md5\" , "
+ " \"secret\": \"\" "
+ "}";
+
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that build succeeds but commit fails on blank secret.
+ EXPECT_NO_THROW(parser_->build(config_set_));
+ EXPECT_THROW(parser_->commit(), D2CfgError);
+}
+
+/// @brief Verifies that TSIGKeyInfo parsing creates a proper TSIGKeyInfo
+/// when given a valid combination of entries.
+TEST_F(TSIGKeyInfoTest, validEntryTests) {
+ // Valid entries for TSIG key, all items are required.
+ std::string config = "{"
+ " \"name\": \"d2_key_one\" , "
+ " \"algorithm\": \"md5\" , "
+ " \"secret\": \"0123456789\" "
+ "}";
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that it builds and commits without throwing.
+ ASSERT_NO_THROW(parser_->build(config_set_));
+ ASSERT_NO_THROW(parser_->commit());
+
+ // Verify the correct number of keys are present
+ int count = keys_->size();
+ EXPECT_EQ(1, count);
+
+ // Find the key and retrieve it.
+ TSIGKeyInfoMap::iterator gotit = keys_->find("d2_key_one");
+ ASSERT_TRUE(gotit != keys_->end());
+ TSIGKeyInfoPtr& key = gotit->second;
+
+ // Verify the key contents.
+ EXPECT_TRUE(checkKey(key, "d2_key_one", "md5", "0123456789"));
+}
+
+/// @brief Verifies that attempting to parse an invalid list of TSIGKeyInfo
+/// entries is detected.
+TEST_F(TSIGKeyInfoTest, invalidTSIGKeyList) {
+ // Construct a list of keys with an invalid key entry.
+ std::string config = "["
+
+ " { \"name\": \"key1\" , "
+ " \"algorithm\": \"algo1\" ,"
+ " \"secret\": \"secret11\" "
+ " },"
+ " { \"name\": \"key2\" , "
+ " \"algorithm\": \"\" ,"
+ " \"secret\": \"secret12\" "
+ " },"
+ " { \"name\": \"key3\" , "
+ " \"algorithm\": \"algo3\" ,"
+ " \"secret\": \"secret13\" "
+ " }"
+ " ]";
+
+ ASSERT_TRUE(fromJSON(config));
+
+ // Create the list parser.
+ isc::dhcp::ParserPtr parser;
+ ASSERT_NO_THROW(parser.reset(new TSIGKeyInfoListParser("test", keys_)));
+
+ // Verify that the list builds without errors.
+ ASSERT_NO_THROW(parser->build(config_set_));
+
+ // Verify that the list commit fails.
+ EXPECT_THROW(parser->commit(), D2CfgError);
+}
+
+/// @brief Verifies that attempting to parse an invalid list of TSIGKeyInfo
+/// entries is detected.
+TEST_F(TSIGKeyInfoTest, duplicateTSIGKey) {
+ // Construct a list of keys with an invalid key entry.
+ std::string config = "["
+
+ " { \"name\": \"key1\" , "
+ " \"algorithm\": \"algo1\" ,"
+ " \"secret\": \"secret11\" "
+ " },"
+ " { \"name\": \"key2\" , "
+ " \"algorithm\": \"algo2\" ,"
+ " \"secret\": \"secret12\" "
+ " },"
+ " { \"name\": \"key1\" , "
+ " \"algorithm\": \"algo3\" ,"
+ " \"secret\": \"secret13\" "
+ " }"
+ " ]";
+
+ ASSERT_TRUE(fromJSON(config));
+
+ // Create the list parser.
+ isc::dhcp::ParserPtr parser;
+ ASSERT_NO_THROW(parser.reset(new TSIGKeyInfoListParser("test", keys_)));
+
+ // Verify that the list builds without errors.
+ ASSERT_NO_THROW(parser->build(config_set_));
+
+ // Verify that the list commit fails.
+ EXPECT_THROW(parser->commit(), D2CfgError);
+}
+
+/// @brief Verifies a valid list of TSIG Keys parses correctly.
+TEST_F(TSIGKeyInfoTest, validTSIGKeyList) {
+ // Construct a valid list of keys.
+ std::string config = "["
+
+ " { \"name\": \"key1\" , "
+ " \"algorithm\": \"algo1\" ,"
+ " \"secret\": \"secret1\" "
+ " },"
+ " { \"name\": \"key2\" , "
+ " \"algorithm\": \"algo2\" ,"
+ " \"secret\": \"secret2\" "
+ " },"
+ " { \"name\": \"key3\" , "
+ " \"algorithm\": \"algo3\" ,"
+ " \"secret\": \"secret3\" "
+ " }"
+ " ]";
+
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that the list builds and commits without errors.
+ // Create the list parser.
+ isc::dhcp::ParserPtr parser;
+ ASSERT_NO_THROW(parser.reset(new TSIGKeyInfoListParser("test", keys_)));
+ ASSERT_NO_THROW(parser->build(config_set_));
+ ASSERT_NO_THROW(parser->commit());
+
+ // Verify the correct number of keys are present
+ int count = keys_->size();
+ ASSERT_EQ(count, 3);
+
+ // Find the 1st key and retrieve it.
+ TSIGKeyInfoMap::iterator gotit = keys_->find("key1");
+ ASSERT_TRUE(gotit != keys_->end());
+ TSIGKeyInfoPtr& key = gotit->second;
+
+ // Verify the key contents.
+ EXPECT_TRUE(checkKey(key, "key1", "algo1", "secret1"));
+
+ // Find the 2nd key and retrieve it.
+ gotit = keys_->find("key2");
+ ASSERT_TRUE(gotit != keys_->end());
+ key = gotit->second;
+
+ // Verify the key contents.
+ EXPECT_TRUE(checkKey(key, "key2", "algo2", "secret2"));
+
+ // Find the 3rd key and retrieve it.
+ gotit = keys_->find("key3");
+ ASSERT_TRUE(gotit != keys_->end());
+ key = gotit->second;
+
+ // Verify the key contents.
+ EXPECT_TRUE(checkKey(key, "key3", "algo3", "secret3"));
+}
+
+/// @brief Tests the enforcement of data validation when parsing DnsServerInfos.
+/// It verifies that:
+/// 1. Specifying both a hostname and an ip address is not allowed.
+/// 2. Specifying both blank a hostname and blank ip address is not allowed.
+/// 3. Specifying a negative port number is not allowed.
+TEST_F(DnsServerInfoTest, invalidEntryTests) {
+ // Create a config in which both host and ip address are supplied.
+ // Verify that it builds without throwing but commit fails.
+ std::string config = "{ \"hostname\": \"pegasus.tmark\", "
+ " \"ip_address\": \"127.0.0.1\" } ";
+ ASSERT_TRUE(fromJSON(config));
+ EXPECT_NO_THROW(parser_->build(config_set_));
+ EXPECT_THROW(parser_->commit(), D2CfgError);
+
+ // Neither host nor ip address supplied
+ // Verify that it builds without throwing but commit fails.
+ config = "{ \"hostname\": \"\", "
+ " \"ip_address\": \"\" } ";
+ ASSERT_TRUE(fromJSON(config));
+ EXPECT_NO_THROW(parser_->build(config_set_));
+ EXPECT_THROW(parser_->commit(), D2CfgError);
+
+ // Create a config with a negative port number.
+ // Verify that build fails.
+ config = "{ \"ip_address\": \"192.168.5.6\" ,"
+ " \"port\": -100 }";
+ ASSERT_TRUE(fromJSON(config));
+ EXPECT_THROW (parser_->build(config_set_), isc::BadValue);
+}
+
+
+/// @brief Verifies that DnsServerInfo parsing creates a proper DnsServerInfo
+/// when given a valid combination of entries.
+/// It verifies that:
+/// 1. A DnsServerInfo entry is correctly made, when given only a hostname.
+/// 2. A DnsServerInfo entry is correctly made, when given ip address and port.
+/// 3. A DnsServerInfo entry is correctly made, when given only an ip address.
+TEST_F(DnsServerInfoTest, validEntryTests) {
+ // Valid entries for dynamic host
+ std::string config = "{ \"hostname\": \"pegasus.tmark\" }";
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that it builds and commits without throwing.
+ ASSERT_NO_THROW(parser_->build(config_set_));
+ ASSERT_NO_THROW(parser_->commit());
+
+ // Verify the correct number of servers are present
+ int count = servers_->size();
+ EXPECT_EQ(1, count);
+
+ // Verify the server exists and has the correct values.
+ DnsServerInfoPtr server = (*servers_)[0];
+ EXPECT_TRUE(checkServer(server, "pegasus.tmark",
+ DnsServerInfo::EMPTY_IP_STR,
+ DnsServerInfo::STANDARD_DNS_PORT));
+
+ // Start over for a new test.
+ reset();
+
+ // Valid entries for static ip
+ config = " { \"ip_address\": \"127.0.0.1\" , "
+ " \"port\": 100 }";
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that it builds and commits without throwing.
+ ASSERT_NO_THROW(parser_->build(config_set_));
+ ASSERT_NO_THROW(parser_->commit());
+
+ // Verify the correct number of servers are present
+ count = servers_->size();
+ EXPECT_EQ(1, count);
+
+ // Verify the server exists and has the correct values.
+ server = (*servers_)[0];
+ EXPECT_TRUE(checkServer(server, "", "127.0.0.1", 100));
+
+ // Start over for a new test.
+ reset();
+
+ // Valid entries for static ip, no port
+ config = " { \"ip_address\": \"192.168.2.5\" }";
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that it builds and commits without throwing.
+ ASSERT_NO_THROW(parser_->build(config_set_));
+ ASSERT_NO_THROW(parser_->commit());
+
+ // Verify the correct number of servers are present
+ count = servers_->size();
+ EXPECT_EQ(1, count);
+
+ // Verify the server exists and has the correct values.
+ server = (*servers_)[0];
+ EXPECT_TRUE(checkServer(server, "", "192.168.2.5",
+ DnsServerInfo::STANDARD_DNS_PORT));
+}
+
+/// @brief Verifies that attempting to parse an invalid list of DnsServerInfo
+/// entries is detected.
+TEST_F(ConfigParseTest, invalidServerList) {
+ // Construct a list of servers with an invalid server entry.
+ std::string config = "[ { \"hostname\": \"one.tmark\" }, "
+ "{ \"hostname\": \"\" }, "
+ "{ \"hostname\": \"three.tmark\" } ]";
+ ASSERT_TRUE(fromJSON(config));
+
+ // Create the server storage and list parser.
+ DnsServerInfoStoragePtr servers(new DnsServerInfoStorage());
+ isc::dhcp::ParserPtr parser;
+ ASSERT_NO_THROW(parser.reset(new DnsServerInfoListParser("test", servers)));
+
+ // Verify that the list builds without errors.
+ ASSERT_NO_THROW(parser->build(config_set_));
+
+ // Verify that the list commit fails.
+ EXPECT_THROW(parser->commit(), D2CfgError);
+}
+
+/// @brief Verifies that a list of DnsServerInfo entries parses correctly given
+/// a valid configuration.
+TEST_F(ConfigParseTest, validServerList) {
+ // Create a valid list of servers.
+ std::string config = "[ { \"hostname\": \"one.tmark\" }, "
+ "{ \"hostname\": \"two.tmark\" }, "
+ "{ \"hostname\": \"three.tmark\" } ]";
+ ASSERT_TRUE(fromJSON(config));
+
+ // Create the server storage and list parser.
+ DnsServerInfoStoragePtr servers(new DnsServerInfoStorage());
+ isc::dhcp::ParserPtr parser;
+ ASSERT_NO_THROW(parser.reset(new DnsServerInfoListParser("test", servers)));
+
+ // Verfiy that the list builds and commits without error.
+ ASSERT_NO_THROW(parser->build(config_set_));
+ ASSERT_NO_THROW(parser->commit());
+
+ // Verify that the server storage contains the correct number of servers.
+ int count = servers->size();
+ EXPECT_EQ(3, count);
+
+ // Verify the first server exists and has the correct values.
+ DnsServerInfoPtr server = (*servers)[0];
+ EXPECT_TRUE(checkServer(server, "one.tmark", DnsServerInfo::EMPTY_IP_STR,
+ DnsServerInfo::STANDARD_DNS_PORT));
+
+ // Verify the second server exists and has the correct values.
+ server = (*servers)[1];
+ EXPECT_TRUE(checkServer(server, "two.tmark", DnsServerInfo::EMPTY_IP_STR,
+ DnsServerInfo::STANDARD_DNS_PORT));
+
+ // Verify the third server exists and has the correct values.
+ server = (*servers)[2];
+ EXPECT_TRUE(checkServer(server, "three.tmark", DnsServerInfo::EMPTY_IP_STR,
+ DnsServerInfo::STANDARD_DNS_PORT));
+}
+
+/// @brief Tests the enforcement of data validation when parsing DdnsDomains.
+/// It verifies that:
+/// 1. Domain storage cannot be null when constructing a DdnsDomainParser.
+/// 2. The name entry is not optional.
+/// 3. The server list man not be empty.
+/// 4. That a mal-formed server entry is detected.
+/// 5. That an undefined key name is detected.
+TEST_F(DdnsDomainTest, invalidDdnsDomainEntry) {
+ // Verify that attempting to construct the parser with null storage fails.
+ DdnsDomainMapPtr domains;
+ ASSERT_THROW(isc::dhcp::ParserPtr(
+ new DdnsDomainParser("test", domains, keys_)), D2CfgError);
+
+ // Create a domain configuration without a name
+ std::string config = "{ \"key_name\": \"d2_key.tmark.org\" , "
+ " \"dns_servers\" : [ "
+ " { \"ip_address\": \"127.0.0.1\" , "
+ " \"port\": 100 },"
+ " { \"ip_address\": \"127.0.0.2\" , "
+ " \"port\": 200 },"
+ " { \"ip_address\": \"127.0.0.3\" , "
+ " \"port\": 300 } ] } ";
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that the domain configuration builds but commit fails.
+ ASSERT_NO_THROW(parser_->build(config_set_));
+ ASSERT_THROW(parser_->commit(), isc::dhcp::DhcpConfigError);
+
+ // Create a domain configuration with an empty server list.
+ config = "{ \"name\": \"tmark.org\" , "
+ " \"key_name\": \"d2_key.tmark.org\" , "
+ " \"dns_servers\" : [ "
+ " ] } ";
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that the domain configuration build fails.
+ ASSERT_THROW(parser_->build(config_set_), D2CfgError);
+
+ // Create a domain configuration with a mal-formed server entry.
+ config = "{ \"name\": \"tmark.org\" , "
+ " \"key_name\": \"d2_key.tmark.org\" , "
+ " \"dns_servers\" : [ "
+ " { \"ip_address\": \"127.0.0.3\" , "
+ " \"port\": -1 } ] } ";
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that the domain configuration build fails.
+ ASSERT_THROW(parser_->build(config_set_), isc::BadValue);
+
+ // Create a domain configuration without an defined key name
+ config = "{ \"name\": \"tmark.org\" , "
+ " \"key_name\": \"d2_key.tmark.org\" , "
+ " \"dns_servers\" : [ "
+ " { \"ip_address\": \"127.0.0.3\" , "
+ " \"port\": 300 } ] } ";
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that the domain configuration build succeeds but commit fails.
+ ASSERT_NO_THROW(parser_->build(config_set_));
+ ASSERT_THROW(parser_->commit(), D2CfgError);
+}
+
+/// @brief Verifies the basics of parsing DdnsDomains.
+/// It verifies that:
+/// 1. Valid construction of DdnsDomainParser functions.
+/// 2. Given a valid, configuration entry, DdnsDomainParser parses
+/// correctly.
+/// (It indirectly verifies the operation of DdnsDomainMap).
+TEST_F(DdnsDomainTest, ddnsDomainParsing) {
+ // Create a valid domain configuration entry containing three valid
+ // servers.
+ std::string config =
+ "{ \"name\": \"tmark.org\" , "
+ " \"key_name\": \"d2_key.tmark.org\" , "
+ " \"dns_servers\" : [ "
+ " { \"ip_address\": \"127.0.0.1\" , "
+ " \"port\": 100 },"
+ " { \"ip_address\": \"127.0.0.2\" , "
+ " \"port\": 200 },"
+ " { \"ip_address\": \"127.0.0.3\" , "
+ " \"port\": 300 } ] } ";
+ ASSERT_TRUE(fromJSON(config));
+
+ // Add a TSIG key to the test key map, so key validation will pass.
+ addKey("d2_key.tmark.org", "md5", "0123456789");
+
+ // Verify that the domain configuration builds and commits without error.
+ ASSERT_NO_THROW(parser_->build(config_set_));
+ ASSERT_NO_THROW(parser_->commit());
+
+ // Verify that the domain storage contains the correct number of domains.
+ int count = domains_->size();
+ EXPECT_EQ(1, count);
+
+ // Verify that the expected domain exists and can be retrieved from
+ // the storage.
+ DdnsDomainMap::iterator gotit = domains_->find("tmark.org");
+ ASSERT_TRUE(gotit != domains_->end());
+ DdnsDomainPtr& domain = gotit->second;
+
+ // Verify the name and key_name values.
+ EXPECT_EQ("tmark.org", domain->getName());
+ EXPECT_EQ("d2_key.tmark.org", domain->getKeyName());
+
+ // Verify that the server list exists and contains the correct number of
+ // servers.
+ const DnsServerInfoStoragePtr& servers = domain->getServers();
+ EXPECT_TRUE(servers);
+ count = servers->size();
+ EXPECT_EQ(3, count);
+
+ // Fetch each server and verify its contents.
+ DnsServerInfoPtr server = (*servers)[0];
+ EXPECT_TRUE(server);
+
+ EXPECT_TRUE(checkServer(server, "", "127.0.0.1", 100));
+
+ server = (*servers)[1];
+ EXPECT_TRUE(server);
+
+ EXPECT_TRUE(checkServer(server, "", "127.0.0.2", 200));
+
+ server = (*servers)[2];
+ EXPECT_TRUE(server);
+
+ EXPECT_TRUE(checkServer(server, "", "127.0.0.3", 300));
+}
+
+/// @brief Tests the fundamentals of parsing DdnsDomain lists.
+/// This test verifies that given a valid domain list configuration
+/// it will accurately parse and populate each domain in the list.
+TEST_F(DdnsDomainTest, DdnsDomainListParsing) {
+ // Create a valid domain list configuration, with two domains
+ // that have three servers each.
+ std::string config =
+ "[ "
+ "{ \"name\": \"tmark.org\" , "
+ " \"key_name\": \"d2_key.tmark.org\" , "
+ " \"dns_servers\" : [ "
+ " { \"ip_address\": \"127.0.0.1\" , "
+ " \"port\": 100 },"
+ " { \"ip_address\": \"127.0.0.2\" , "
+ " \"port\": 200 },"
+ " { \"ip_address\": \"127.0.0.3\" , "
+ " \"port\": 300 } ] } "
+ ", "
+ "{ \"name\": \"billcat.net\" , "
+ " \"key_name\": \"d2_key.billcat.net\" , "
+ " \"dns_servers\" : [ "
+ " { \"ip_address\": \"127.0.0.4\" , "
+ " \"port\": 400 },"
+ " { \"ip_address\": \"127.0.0.5\" , "
+ " \"port\": 500 },"
+ " { \"ip_address\": \"127.0.0.6\" , "
+ " \"port\": 600 } ] } "
+ "] ";
+
+ ASSERT_TRUE(fromJSON(config));
+
+ // Add keys to key map so key validation passes.
+ addKey("d2_key.tmark.org", "algo1", "secret1");
+ addKey("d2_key.billcat.net", "algo2", "secret2");
+
+ // Create the list parser
+ isc::dhcp::ParserPtr list_parser;
+ ASSERT_NO_THROW(list_parser.reset(
+ new DdnsDomainListParser("test", domains_, keys_)));
+
+ // Verify that the domain configuration builds and commits without error.
+ ASSERT_NO_THROW(list_parser->build(config_set_));
+ ASSERT_NO_THROW(list_parser->commit());
+
+ // Verify that the domain storage contains the correct number of domains.
+ int count = domains_->size();
+ EXPECT_EQ(2, count);
+
+ // Verify that the first domain exists and can be retrieved.
+ DdnsDomainMap::iterator gotit = domains_->find("tmark.org");
+ ASSERT_TRUE(gotit != domains_->end());
+ DdnsDomainPtr& domain = gotit->second;
+
+ // Verify the name and key_name values of the first domain.
+ EXPECT_EQ("tmark.org", domain->getName());
+ EXPECT_EQ("d2_key.tmark.org", domain->getKeyName());
+
+ // Verify the each of the first domain's servers
+ DnsServerInfoStoragePtr servers = domain->getServers();
+ EXPECT_TRUE(servers);
+ count = servers->size();
+ EXPECT_EQ(3, count);
+
+ DnsServerInfoPtr server = (*servers)[0];
+ EXPECT_TRUE(server);
+ EXPECT_TRUE(checkServer(server, "", "127.0.0.1", 100));
+
+ server = (*servers)[1];
+ EXPECT_TRUE(server);
+ EXPECT_TRUE(checkServer(server, "", "127.0.0.2", 200));
+
+ server = (*servers)[2];
+ EXPECT_TRUE(server);
+ EXPECT_TRUE(checkServer(server, "", "127.0.0.3", 300));
+
+ // Verify second domain
+ gotit = domains_->find("billcat.net");
+ ASSERT_TRUE(gotit != domains_->end());
+ domain = gotit->second;
+
+ // Verify the name and key_name values of the second domain.
+ EXPECT_EQ("billcat.net", domain->getName());
+ EXPECT_EQ("d2_key.billcat.net", domain->getKeyName());
+
+ // Verify the each of second domain's servers
+ servers = domain->getServers();
+ EXPECT_TRUE(servers);
+ count = servers->size();
+ EXPECT_EQ(3, count);
+
+ server = (*servers)[0];
+ EXPECT_TRUE(server);
+ EXPECT_TRUE(checkServer(server, "", "127.0.0.4", 400));
+
+ server = (*servers)[1];
+ EXPECT_TRUE(server);
+ EXPECT_TRUE(checkServer(server, "", "127.0.0.5", 500));
+
+ server = (*servers)[2];
+ EXPECT_TRUE(server);
+ EXPECT_TRUE(checkServer(server, "", "127.0.0.6", 600));
+}
+
+/// @brief Tests that a domain list configuration cannot contain duplicates.
+TEST_F(DdnsDomainTest, duplicateDomainTest) {
+ // Create a domain list configuration that contains two domains with
+ // the same name.
+ std::string config =
+ "[ "
+ "{ \"name\": \"tmark.org\" , "
+ " \"dns_servers\" : [ "
+ " { \"ip_address\": \"127.0.0.3\" , "
+ " \"port\": 300 } ] } "
+ ", "
+ "{ \"name\": \"tmark.org\" , "
+ " \"dns_servers\" : [ "
+ " { \"ip_address\": \"127.0.0.3\" , "
+ " \"port\": 300 } ] } "
+ "] ";
+ ASSERT_TRUE(fromJSON(config));
+
+ // Create the list parser
+ isc::dhcp::ParserPtr list_parser;
+ ASSERT_NO_THROW(list_parser.reset(
+ new DdnsDomainListParser("test", domains_, keys_)));
+
+ // Verify that the parse build succeeds but the commit fails.
+ ASSERT_NO_THROW(list_parser->build(config_set_));
+ ASSERT_THROW(list_parser->commit(), D2CfgError);
+}
+
+/// @brief Tests construction of D2CfgMgr
+/// This test verifies that a D2CfgMgr constructs properly.
+TEST(D2CfgMgr, construction) {
+ D2CfgMgr *cfg_mgr = NULL;
+
+ // Verify that configuration manager constructions without error.
+ ASSERT_NO_THROW(cfg_mgr = new D2CfgMgr());
+
+ // Verify that the context can be retrieved and is not null.
+ D2CfgContextPtr context;
+ ASSERT_NO_THROW(context = cfg_mgr->getD2CfgContext());
+ EXPECT_TRUE(context);
+
+ // Verify that the forward manager can be retrieved and is not null.
+ EXPECT_TRUE(context->getForwardMgr());
+
+ // Verify that the reverse manager can be retrieved and is not null.
+ EXPECT_TRUE(context->getReverseMgr());
+
+ // Verify that the manager can be destructed without error.
+ EXPECT_NO_THROW(delete cfg_mgr);
+}
+
+/// @brief Tests the parsing of a complete, valid DHCP-DDNS configuration.
+/// This tests passes the configuration into an instance of D2CfgMgr just
+/// as it would be done by d2_process in response to a configuration update
+/// event.
+TEST_F(D2CfgMgrTest, fullConfigTest) {
+ // Create a configuration with all of application level parameters, plus
+ // both the forward and reverse ddns managers. Both managers have two
+ // domains with three servers per domain.
+ std::string config = "{ "
+ "\"interface\" : \"eth1\" , "
+ "\"ip_address\" : \"192.168.1.33\" , "
+ "\"port\" : 88 , "
+ "\"tsig_keys\": ["
+ "{"
+ " \"name\": \"d2_key.tmark.org\" , "
+ " \"algorithm\": \"md5\" , "
+ " \"secret\": \"ssh-dont-tell\" "
+ "},"
+ "{"
+ " \"name\": \"d2_key.billcat.net\" , "
+ " \"algorithm\": \"md5\" , "
+ " \"secret\": \"ollie-ollie-in-free\" "
+ "}"
+ "],"
+ "\"forward_ddns\" : {"
+ "\"ddns_domains\": [ "
+ "{ \"name\": \"tmark.org\" , "
+ " \"key_name\": \"d2_key.tmark.org\" , "
+ " \"dns_servers\" : [ "
+ " { \"hostname\": \"one.tmark\" } , "
+ " { \"hostname\": \"two.tmark\" } , "
+ " { \"hostname\": \"three.tmark\"} "
+ " ] } "
+ ", "
+ "{ \"name\": \"billcat.net\" , "
+ " \"key_name\": \"d2_key.billcat.net\" , "
+ " \"dns_servers\" : [ "
+ " { \"hostname\": \"four.billcat\" } , "
+ " { \"hostname\": \"five.billcat\" } , "
+ " { \"hostname\": \"six.billcat\" } "
+ " ] } "
+ "] },"
+ "\"reverse_ddns\" : {"
+ "\"ddns_domains\": [ "
+ "{ \"name\": \" 0.168.192.in.addr.arpa.\" , "
+ " \"key_name\": \"d2_key.tmark.org\" , "
+ " \"dns_servers\" : [ "
+ " { \"hostname\": \"one.rev\" } , "
+ " { \"hostname\": \"two.rev\" } , "
+ " { \"hostname\": \"three.rev\" } "
+ " ] } "
+ ", "
+ "{ \"name\": \" 0.247.106.in.addr.arpa.\" , "
+ " \"key_name\": \"d2_key.billcat.net\" , "
+ " \"dns_servers\" : [ "
+ " { \"hostname\": \"four.rev\" }, "
+ " { \"hostname\": \"five.rev\" } , "
+ " { \"hostname\": \"six.rev\" } "
+ " ] } "
+ "] } }";
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that we can parse the configuration.
+ answer_ = cfg_mgr_->parseConfig(config_set_);
+ ASSERT_TRUE(checkAnswer(0));
+
+ // Verify that the D2 context can be retrieved and is not null.
+ D2CfgContextPtr context;
+ ASSERT_NO_THROW(context = cfg_mgr_->getD2CfgContext());
+
+ // Verify that the application level scalars have the proper values.
+ std::string interface;
+ EXPECT_NO_THROW (context->getParam("interface", interface));
+ EXPECT_EQ("eth1", interface);
+
+ std::string ip_address;
+ EXPECT_NO_THROW (context->getParam("ip_address", ip_address));
+ EXPECT_EQ("192.168.1.33", ip_address);
+
+ uint32_t port = 0;
+ EXPECT_NO_THROW (context->getParam("port", port));
+ EXPECT_EQ(88, port);
+
+ // Verify that the forward manager can be retrieved.
+ DdnsDomainListMgrPtr mgr = context->getForwardMgr();
+ ASSERT_TRUE(mgr);
+
+ // Verify that the forward manager has the correct number of domains.
+ DdnsDomainMapPtr domains = mgr->getDomains();
+ ASSERT_TRUE(domains);
+ int count = domains->size();
+ EXPECT_EQ(2, count);
+
+ // Verify that the server count in each of the forward manager domains.
+ // NOTE that since prior tests have validated server parsing, we are are
+ // assuming that the servers did in fact parse correctly if the correct
+ // number of them are there.
+ DdnsDomainMapPair domain_pair;
+ BOOST_FOREACH(domain_pair, (*domains)) {
+ DdnsDomainPtr domain = domain_pair.second;
+ DnsServerInfoStoragePtr servers = domain->getServers();
+ count = servers->size();
+ EXPECT_TRUE(servers);
+ EXPECT_EQ(3, count);
+ }
+
+ // Verify that the reverse manager can be retrieved.
+ mgr = context->getReverseMgr();
+ ASSERT_TRUE(mgr);
+
+ // Verify that the reverse manager has the correct number of domains.
+ domains = mgr->getDomains();
+ count = domains->size();
+ EXPECT_EQ(2, count);
+
+ // Verify that the server count in each of the reverse manager domains.
+ // NOTE that since prior tests have validated server parsing, we are are
+ // assuming that the servers did in fact parse correctly if the correct
+ // number of them are there.
+ BOOST_FOREACH(domain_pair, (*domains)) {
+ DdnsDomainPtr domain = domain_pair.second;
+ DnsServerInfoStoragePtr servers = domain->getServers();
+ count = servers->size();
+ EXPECT_TRUE(servers);
+ EXPECT_EQ(3, count);
+ }
+}
+
+/// @brief Tests the basics of the D2CfgMgr FQDN-domain matching
+/// This test uses a valid configuration to exercise the D2CfgMgr
+/// forward FQDN-to-domain matching.
+/// It verifies that:
+/// 1. Given an FQDN which exactly matches a domain's name, that domain is
+/// returned as match.
+/// 2. Given a FQDN for sub-domain in the list, returns the proper match.
+/// 3. Given a FQDN that matches no domain name, returns the wild card domain
+/// as a match.
+TEST_F(D2CfgMgrTest, forwardMatchTest) {
+ // Create configuration with one domain, one sub domain, and the wild
+ // card.
+ std::string config = "{ "
+ "\"interface\" : \"eth1\" , "
+ "\"ip_address\" : \"192.168.1.33\" , "
+ "\"port\" : 88 , "
+ "\"tsig_keys\": [] ,"
+ "\"forward_ddns\" : {"
+ "\"ddns_domains\": [ "
+ "{ \"name\": \"tmark.org\" , "
+ " \"dns_servers\" : [ "
+ " { \"ip_address\": \"127.0.0.1\" } "
+ " ] } "
+ ", "
+ "{ \"name\": \"one.tmark.org\" , "
+ " \"dns_servers\" : [ "
+ " { \"ip_address\": \"127.0.0.2\" } "
+ " ] } "
+ ", "
+ "{ \"name\": \"*\" , "
+ " \"dns_servers\" : [ "
+ " { \"hostname\": \"global.net\" } "
+ " ] } "
+ "] }, "
+ "\"reverse_ddns\" : {} "
+ "}";
+
+
+ ASSERT_TRUE(fromJSON(config));
+ // Verify that we can parse the configuration.
+ answer_ = cfg_mgr_->parseConfig(config_set_);
+ ASSERT_TRUE(checkAnswer(0));
+
+ // Verify that the D2 context can be retrieved and is not null.
+ D2CfgContextPtr context;
+ ASSERT_NO_THROW(context = cfg_mgr_->getD2CfgContext());
+
+ DdnsDomainPtr match;
+ // Verify that an exact match works.
+ EXPECT_TRUE(cfg_mgr_->matchForward("tmark.org", match));
+ EXPECT_EQ("tmark.org", match->getName());
+
+ // Verify that an exact match works.
+ EXPECT_TRUE(cfg_mgr_->matchForward("one.tmark.org", match));
+ EXPECT_EQ("one.tmark.org", match->getName());
+
+ // Verify that a FQDN for sub-domain matches.
+ EXPECT_TRUE(cfg_mgr_->matchForward("blue.tmark.org", match));
+ EXPECT_EQ("tmark.org", match->getName());
+
+ // Verify that a FQDN for sub-domain matches.
+ EXPECT_TRUE(cfg_mgr_->matchForward("red.one.tmark.org", match));
+ EXPECT_EQ("one.tmark.org", match->getName());
+
+ // Verify that an FQDN with no match, returns the wild card domain.
+ EXPECT_TRUE(cfg_mgr_->matchForward("shouldbe.wildcard", match));
+ EXPECT_EQ("*", match->getName());
+
+ // Verify that an attempt to match an empty FQDN throws.
+ ASSERT_THROW(cfg_mgr_->matchForward("", match), D2CfgError);
+}
+
+/// @brief Tests domain matching when there is no wild card domain.
+/// This test verifies that matches are found only for FQDNs that match
+/// some or all of a domain name. FQDNs without matches should not return
+/// a match.
+TEST_F(D2CfgMgrTest, matchNoWildcard) {
+ // Create a configuration with one domain, one sub-domain, and NO wild card.
+ std::string config = "{ "
+ "\"interface\" : \"eth1\" , "
+ "\"ip_address\" : \"192.168.1.33\" , "
+ "\"port\" : 88 , "
+ "\"tsig_keys\": [] ,"
+ "\"forward_ddns\" : {"
+ "\"ddns_domains\": [ "
+ "{ \"name\": \"tmark.org\" , "
+ " \"dns_servers\" : [ "
+ " { \"ip_address\": \"127.0.0.1\" } "
+ " ] } "
+ ", "
+ "{ \"name\": \"one.tmark.org\" , "
+ " \"dns_servers\" : [ "
+ " { \"ip_address\": \"127.0.0.2\" } "
+ " ] } "
+ "] }, "
+ "\"reverse_ddns\" : {} "
+ " }";
+
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that we can parse the configuration.
+ answer_ = cfg_mgr_->parseConfig(config_set_);
+ ASSERT_TRUE(checkAnswer(0));
+
+ // Verify that the D2 context can be retrieved and is not null.
+ D2CfgContextPtr context;
+ ASSERT_NO_THROW(context = cfg_mgr_->getD2CfgContext());
+
+ DdnsDomainPtr match;
+ // Verify that full or partial matches, still match.
+ EXPECT_TRUE(cfg_mgr_->matchForward("tmark.org", match));
+ EXPECT_EQ("tmark.org", match->getName());
+
+ EXPECT_TRUE(cfg_mgr_->matchForward("blue.tmark.org", match));
+ EXPECT_EQ("tmark.org", match->getName());
+
+ EXPECT_TRUE(cfg_mgr_->matchForward("red.one.tmark.org", match));
+ EXPECT_EQ("one.tmark.org", match->getName());
+
+ // Verify that a FQDN with no match, fails to match.
+ EXPECT_FALSE(cfg_mgr_->matchForward("shouldbe.wildcard", match));
+}
+
+/// @brief Tests domain matching when there is ONLY a wild card domain.
+/// This test verifies that any FQDN matches the wild card.
+TEST_F(D2CfgMgrTest, matchAll) {
+ std::string config = "{ "
+ "\"interface\" : \"eth1\" , "
+ "\"ip_address\" : \"192.168.1.33\" , "
+ "\"port\" : 88 , "
+ "\"tsig_keys\": [] ,"
+ "\"forward_ddns\" : {"
+ "\"ddns_domains\": [ "
+ "{ \"name\": \"*\" , "
+ " \"dns_servers\" : [ "
+ " { \"ip_address\": \"127.0.0.1\" } "
+ " ] } "
+ "] }, "
+ "\"reverse_ddns\" : {} "
+ "}";
+
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that we can parse the configuration.
+ answer_ = cfg_mgr_->parseConfig(config_set_);
+ ASSERT_TRUE(checkAnswer(0));
+
+ // Verify that the D2 context can be retrieved and is not null.
+ D2CfgContextPtr context;
+ ASSERT_NO_THROW(context = cfg_mgr_->getD2CfgContext());
+
+ // Verify that wild card domain is returned for any FQDN.
+ DdnsDomainPtr match;
+ EXPECT_TRUE(cfg_mgr_->matchForward("tmark.org", match));
+ EXPECT_EQ("*", match->getName());
+ EXPECT_TRUE(cfg_mgr_->matchForward("shouldbe.wildcard", match));
+ EXPECT_EQ("*", match->getName());
+
+ // Verify that an attempt to match an empty FQDN still throws.
+ ASSERT_THROW(cfg_mgr_->matchReverse("", match), D2CfgError);
+
+}
+
+/// @brief Tests the basics of the D2CfgMgr reverse FQDN-domain matching
+/// This test uses a valid configuration to exercise the D2CfgMgr's
+/// reverse FQDN-to-domain matching.
+/// It verifies that:
+/// 1. Given an FQDN which exactly matches a domain's name, that domain is
+/// returned as match.
+/// 2. Given a FQDN for sub-domain in the list, returns the proper match.
+/// 3. Given a FQDN that matches no domain name, returns the wild card domain
+/// as a match.
+TEST_F(D2CfgMgrTest, matchReverse) {
+ std::string config = "{ "
+ "\"interface\" : \"eth1\" , "
+ "\"ip_address\" : \"192.168.1.33\" , "
+ "\"port\" : 88 , "
+ "\"tsig_keys\": [] ,"
+ "\"forward_ddns\" : {}, "
+ "\"reverse_ddns\" : {"
+ "\"ddns_domains\": [ "
+ "{ \"name\": \"100.168.192.in-addr.arpa\" , "
+ " \"dns_servers\" : [ "
+ " { \"ip_address\": \"127.0.0.1\" } "
+ " ] }, "
+ "{ \"name\": \"168.192.in-addr.arpa\" , "
+ " \"dns_servers\" : [ "
+ " { \"ip_address\": \"127.0.0.1\" } "
+ " ] }, "
+ "{ \"name\": \"*\" , "
+ " \"dns_servers\" : [ "
+ " { \"ip_address\": \"127.0.0.1\" } "
+ " ] } "
+ "] } }";
+
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that we can parse the configuration.
+ answer_ = cfg_mgr_->parseConfig(config_set_);
+ ASSERT_TRUE(checkAnswer(0));
+
+ // Verify that the D2 context can be retrieved and is not null.
+ D2CfgContextPtr context;
+ ASSERT_NO_THROW(context = cfg_mgr_->getD2CfgContext());
+
+ DdnsDomainPtr match;
+ // Verify an exact match.
+ EXPECT_TRUE(cfg_mgr_->matchReverse("100.168.192.in-addr.arpa", match));
+ EXPECT_EQ("100.168.192.in-addr.arpa", match->getName());
+
+ // Verify a sub-domain match.
+ EXPECT_TRUE(cfg_mgr_->matchReverse("27.100.168.192.in-addr.arpa", match));
+ EXPECT_EQ("100.168.192.in-addr.arpa", match->getName());
+
+ // Verify a sub-domain match.
+ EXPECT_TRUE(cfg_mgr_->matchReverse("30.133.168.192.in-addr.arpa", match));
+ EXPECT_EQ("168.192.in-addr.arpa", match->getName());
+
+ // Verify a wild card match.
+ EXPECT_TRUE(cfg_mgr_->matchReverse("shouldbe.wildcard", match));
+ EXPECT_EQ("*", match->getName());
+
+ // Verify that an attempt to match an empty FQDN throws.
+ ASSERT_THROW(cfg_mgr_->matchReverse("", match), D2CfgError);
+}
+
+} // end of anonymous namespace
diff --git a/src/bin/d2/tests/d2_controller_unittests.cc b/src/bin/d2/tests/d2_controller_unittests.cc
index a2b3374..f4b4d41 100644
--- a/src/bin/d2/tests/d2_controller_unittests.cc
+++ b/src/bin/d2/tests/d2_controller_unittests.cc
@@ -54,7 +54,7 @@ public:
};
/// @brief Basic Controller instantiation testing.
-/// Verfies that the controller singleton gets created and that the
+/// Verifies that the controller singleton gets created and that the
/// basic derivation from the base class is intact.
TEST_F(D2ControllerTest, basicInstanceTesting) {
// Verify the we can the singleton instance can be fetched and that
@@ -80,12 +80,12 @@ TEST_F(D2ControllerTest, basicInstanceTesting) {
}
/// @brief Tests basic command line processing.
-/// Verfies that:
+/// Verifies that:
/// 1. Standard command line options are supported.
/// 2. Invalid options are detected.
TEST_F(D2ControllerTest, commandLineArgs) {
- char* argv[] = { const_cast<char*>("progName"),
- const_cast<char*>("-s"),
+ char* argv[] = { const_cast<char*>("progName"),
+ const_cast<char*>("-s"),
const_cast<char*>("-v") };
int argc = 3;
@@ -101,7 +101,7 @@ TEST_F(D2ControllerTest, commandLineArgs) {
EXPECT_TRUE(checkVerbose(true));
// Verify that an unknown option is detected.
- char* argv2[] = { const_cast<char*>("progName"),
+ char* argv2[] = { const_cast<char*>("progName"),
const_cast<char*>("-x") };
argc = 2;
EXPECT_THROW(parseArgs(argc, argv2), InvalidUsage);
@@ -119,8 +119,8 @@ TEST_F(D2ControllerTest, initProcessTesting) {
/// launches with a valid, stand-alone command line and no simulated errors.
TEST_F(D2ControllerTest, launchNormalShutdown) {
// command line to run standalone
- char* argv[] = { const_cast<char*>("progName"),
- const_cast<char*>("-s"),
+ char* argv[] = { const_cast<char*>("progName"),
+ const_cast<char*>("-s"),
const_cast<char*>("-v") };
int argc = 3;
@@ -165,10 +165,9 @@ TEST_F(D2ControllerTest, configUpdateTests) {
ASSERT_NO_THROW(initProcess());
EXPECT_TRUE(checkProcess());
- // Create a configuration set. Content is arbitrary, just needs to be
- // valid JSON.
- std::string config = "{ \"test-value\": 1000 } ";
- isc::data::ElementPtr config_set = isc::data::Element::fromJSON(config);
+ // Create a configuration set using a small, valid D2 configuration.
+ isc::data::ElementPtr config_set =
+ isc::data::Element::fromJSON(valid_d2_config);
// We are not stand-alone, so configuration should be rejected as there is
// no session. This is a pretty contrived situation that shouldn't be
@@ -182,6 +181,13 @@ TEST_F(D2ControllerTest, configUpdateTests) {
answer = DControllerBase::configHandler(config_set);
isc::config::parseAnswer(rcode, answer);
EXPECT_EQ(0, rcode);
+
+ // Use an invalid configuration to verify parsing error return.
+ std::string config = "{ \"bogus\": 1000 } ";
+ config_set = isc::data::Element::fromJSON(config);
+ answer = DControllerBase::configHandler(config_set);
+ isc::config::parseAnswer(rcode, answer);
+ EXPECT_EQ(1, rcode);
}
/// @brief Command execution tests.
diff --git a/src/bin/d2/tests/d2_process_unittests.cc b/src/bin/d2/tests/d2_process_unittests.cc
index 40c85f3..c507f2c 100644
--- a/src/bin/d2/tests/d2_process_unittests.cc
+++ b/src/bin/d2/tests/d2_process_unittests.cc
@@ -15,6 +15,7 @@
#include <config/ccsession.h>
#include <d2/d2_process.h>
+#include <d_test_stubs.h>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <gtest/gtest.h>
@@ -83,17 +84,23 @@ TEST(D2Process, construction) {
}
/// @brief Verifies basic configure method behavior.
-/// @TODO This test is simplistic and will need to be augmented as configuration
+/// This test is simplistic and will need to be augmented as configuration
/// ability is implemented.
TEST_F(D2ProcessTest, configure) {
- // Verify that given a configuration "set", configure returns
- // a successful response.
int rcode = -1;
- string config = "{ \"test-value\": 1000 } ";
- isc::data::ElementPtr json = isc::data::Element::fromJSON(config);
+
+ // Use a small, valid D2 configuration to verify successful parsing.
+ isc::data::ElementPtr json = isc::data::Element::fromJSON(valid_d2_config);
isc::data::ConstElementPtr answer = process_->configure(json);
isc::config::parseAnswer(rcode, answer);
EXPECT_EQ(0, rcode);
+
+ // Use an invalid configuration to verify parsing error return.
+ string config = "{ \"bogus\": 1000 } ";
+ json = isc::data::Element::fromJSON(config);
+ answer = process_->configure(json);
+ isc::config::parseAnswer(rcode, answer);
+ EXPECT_EQ(1, rcode);
}
/// @brief Verifies basic command method behavior.
diff --git a/src/bin/d2/tests/d2_update_message_unittests.cc b/src/bin/d2/tests/d2_update_message_unittests.cc
new file mode 100644
index 0000000..28e01c7
--- /dev/null
+++ b/src/bin/d2/tests/d2_update_message_unittests.cc
@@ -0,0 +1,591 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+
+#include <d2/d2_update_message.h>
+#include <d2/d2_zone.h>
+#include <dns/messagerenderer.h>
+#include <dns/rdataclass.h>
+#include <dns/rdata.h>
+#include <dns/rrttl.h>
+
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::d2;
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+using namespace isc::util;
+
+namespace {
+
+// @brief Test fixture class for testing D2UpdateMessage object.
+class D2UpdateMessageTest : public ::testing::Test {
+public:
+ // @brief Constructor.
+ //
+ // Does nothing.
+ D2UpdateMessageTest() { }
+
+ // @brief Destructor.
+ //
+ // Does nothing.
+ ~D2UpdateMessageTest() { };
+
+ // @brief Return string representation of the name encoded in wire format.
+ //
+ // This function reads the number of bytes specified in the second
+ // argument from the buffer. It doesn't check if buffer has sufficient
+ // length for reading given number of bytes. Caller should verify it
+ // prior to calling this function.
+ //
+ // @param buf input buffer, its internal pointer will be moved to
+ // the position after a name being read from it.
+ // @param name_length length of the name stored in the buffer
+ // @param no_zero_byte if true it indicates that the given buffer does not
+ // comprise the zero byte, which signals end of the name. This is
+ // the case, when dealing with compressed messages which don't have
+ // this byte.
+ //
+ // @return string representation of the name.
+ std::string readNameFromWire(InputBuffer& buf, size_t name_length,
+ const bool no_zero_byte = false) {
+ std::vector<uint8_t> name_data;
+ // Create another InputBuffer which holds only the name in the wire
+ // format.
+ buf.readVector(name_data, name_length);
+ if (no_zero_byte) {
+ ++name_length;
+ name_data.push_back(0);
+ }
+ InputBuffer name_buf(&name_data[0], name_length);
+ // Parse the name and return its textual representation.
+ Name name(name_buf);
+ return (name.toText());
+ }
+};
+
+// This test verifies that DNS Update message ID can be set using
+// setId function.
+TEST_F(D2UpdateMessageTest, setId) {
+ // Message ID is initialized to 0.
+ D2UpdateMessage msg;
+ EXPECT_EQ(0, msg.getId());
+ // Override the default value and verify that it has been set.
+ msg.setId(0x1234);
+ EXPECT_EQ(0x1234, msg.getId());
+}
+
+// This test verifies that the DNS Update message RCODE can be set
+// using setRcode function.
+TEST_F(D2UpdateMessageTest, setRcode) {
+ D2UpdateMessage msg;
+ // Rcode must be explicitly set before it is accessed.
+ msg.setRcode(Rcode::NOERROR());
+ EXPECT_EQ(Rcode::NOERROR().getCode(), msg.getRcode().getCode());
+ // Let's override current value to make sure that getter does
+ // not return fixed value.
+ msg.setRcode(Rcode::NOTIMP());
+ EXPECT_EQ(Rcode::NOTIMP().getCode(), msg.getRcode().getCode());
+}
+
+// This test verifies that the Zone section in the DNS Update message
+// can be set.
+TEST_F(D2UpdateMessageTest, setZone) {
+ D2UpdateMessage msg;
+ // The zone pointer is initialized to NULL.
+ D2ZonePtr zone = msg.getZone();
+ EXPECT_FALSE(zone);
+ // Let's create a new Zone and check that it is returned
+ // via getter.
+ msg.setZone(Name("example.com"), RRClass::ANY());
+ zone = msg.getZone();
+ EXPECT_TRUE(zone);
+ EXPECT_EQ("example.com.", zone->getName().toText());
+ EXPECT_EQ(RRClass::ANY().getCode(), zone->getClass().getCode());
+
+ // Now, let's check that the existing Zone object can be
+ // overriden with a new one.
+ msg.setZone(Name("foo.example.com"), RRClass::NONE());
+ zone = msg.getZone();
+ EXPECT_TRUE(zone);
+ EXPECT_EQ("foo.example.com.", zone->getName().toText());
+ EXPECT_EQ(RRClass::NONE().getCode(), zone->getClass().getCode());
+}
+
+// This test verifies that the DNS message is properly decoded from the
+// wire format.
+TEST_F(D2UpdateMessageTest, fromWire) {
+ // The following table holds the DNS response in on-wire format.
+ // This message comprises the following sections:
+ // - HEADER
+ // - PREREQUISITE section with one RR
+ // - UPDATE section with 1 RR.
+ // Such a response may be generated by the DNS server as a result
+ // of copying the contents of the REQUEST message sent by DDNS client.
+ const uint8_t bin_msg[] = {
+ // HEADER section starts here (see RFC 2136, section 2).
+ 0x05, 0xAF, // ID=0x05AF
+ 0xA8, 0x6, // QR=1, Opcode=6, RCODE=YXDOMAIN
+ 0x0, 0x1, // ZOCOUNT=1
+ 0x0, 0x2, // PRCOUNT=2
+ 0x0, 0x1, // UPCOUNT=1
+ 0x0, 0x0, // ADCOUNT=0
+
+ // Zone section starts here. The The first field comprises
+ // the Zone name encoded as a set of labels, each preceded
+ // by a length of the following label. The whole Zone name is
+ // terminated with a NULL char.
+ // For Zone section format see (RFC 2136, section 2.3).
+ 0x7, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example (7 is length)
+ 0x3, 0x63, 0x6F, 0x6D, //.com. (0x3 is a length)
+ 0x0, // NULL character terminates the Zone name.
+ 0x0, 0x6, // ZTYPE='SOA'
+ 0x0, 0x1, // ZCLASS='IN'
+
+ // Prerequisite section starts here. This section comprises two
+ // prerequisites:
+ // - 'Name is not in use'
+ // - 'Name is in use'
+ // See RFC 2136, section 2.4 for the format of Prerequisite section.
+ // Each prerequisite RR starts with its name. It is expressed in the
+ // compressed format as described in RFC 1035, section 4.1.4. The first
+ // label is expressed as in case of non-compressed name. It is preceded
+ // by the length value. The following two bytes are the pointer to the
+ // offset in the message where 'example.com' was used. That is, in the
+ // Zone name at offset 12. Pointer starts with two bits set - they
+ // mark start of the pointer.
+
+ // First prerequisite. NONE class indicates that the update requires
+ // that the name 'foo.example.com' is not use/
+ 0x03, 0x66, 0x6F, 0x6F, // foo.
+ 0xC0, 0x0C, // pointer to example.com.
+ 0x0, 0x1C, // TYPE=AAAA
+ 0x0, 0xFE, // CLASS=NONE
+ 0x0, 0x0, 0x0, 0x0, // TTL=0
+ 0x0, 0x0, // RDLENGTH=0
+
+ // Second prerequisite. ANY class indicates tha the update requires
+ // that the name 'bar.example.com' exists.
+ 0x03, 0x62, 0x61, 0x72, // bar.
+ 0xC0, 0x0C, // pointer to example.com.
+ 0x0, 0x1C, // TYPE=AAAA
+ 0x0, 0xFF, // CLASS=ANY
+ 0x0, 0x0, 0x0, 0x0, // TTL=0
+ 0x0, 0x0, // RDLENGTH=0
+
+ // Update section starts here. The format of this section conforms to
+ // RFC 2136, section 2.5. The name of the RR is again expressed in
+ // compressed format. The two pointer bytes point to the offset in the
+ // message where 'foo.example.com' was used already - 29.
+ 0xC0, 0x1D, // pointer to foo.example.com.
+ 0x0, 0x1C, // TYPE=AAAA
+ 0x0, 0x1, // CLASS=IN
+ 0xAA, 0xBB, 0xCC, 0xDD, // TTL=0xAABBCCDD
+ 0x0, 0x10, // RDLENGTH=16
+ // The following 16 bytes of RDATA hold IPv6 address: 2001:db8:1::1.
+ 0x20, 0x01, 0x0D, 0xB8, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01
+ };
+ InputBuffer buf(bin_msg, sizeof(bin_msg));
+
+ // Create an object to be used to decode the message from the wire format.
+ D2UpdateMessage msg(D2UpdateMessage::INBOUND);
+ // Decode the message.
+ ASSERT_NO_THROW(msg.fromWire(buf));
+
+ // Check that the message header is valid.
+ EXPECT_EQ(0x05AF, msg.getId());
+ EXPECT_EQ(D2UpdateMessage::RESPONSE, msg.getQRFlag());
+ EXPECT_EQ(Rcode::YXDOMAIN_CODE, msg.getRcode().getCode());
+
+ // The ZOCOUNT must contain exactly one zone. If it does, we should get
+ // the name, class and type of the zone and verify they are valid.
+ ASSERT_EQ(1, msg.getRRCount(D2UpdateMessage::SECTION_ZONE));
+ D2ZonePtr zone = msg.getZone();
+ ASSERT_TRUE(zone);
+ EXPECT_EQ("example.com.", zone->getName().toText());
+ EXPECT_EQ(RRClass::IN().getCode(), zone->getClass().getCode());
+
+ // Check the Prerequisite section. It should contain two records.
+ ASSERT_EQ(2, msg.getRRCount(D2UpdateMessage::SECTION_PREREQUISITE));
+
+ // Proceed to the first prerequisite.
+ RRsetIterator rrset_it =
+ msg.beginSection(D2UpdateMessage::SECTION_PREREQUISITE);
+ RRsetPtr prereq1 = *rrset_it;
+ ASSERT_TRUE(prereq1);
+ // Check record fields.
+ EXPECT_EQ("foo.example.com.", prereq1->getName().toText()); // NAME
+ EXPECT_EQ(RRType::AAAA().getCode(), prereq1->getType().getCode()); // TYPE
+ EXPECT_EQ(RRClass::NONE().getCode(),
+ prereq1->getClass().getCode()); // CLASS
+ EXPECT_EQ(0, prereq1->getTTL().getValue()); // TTL
+ EXPECT_EQ(0, prereq1->getRdataCount()); // RDLENGTH
+
+ // Move to next prerequisite section.
+ ++rrset_it;
+ RRsetPtr prereq2 = *rrset_it;
+ ASSERT_TRUE(prereq2);
+ // Check record fields.
+ EXPECT_EQ("bar.example.com.", prereq2->getName().toText()); // NAME
+ EXPECT_EQ(RRType::AAAA().getCode(), prereq2->getType().getCode()); // TYPE
+ EXPECT_EQ(RRClass::ANY().getCode(), prereq2->getClass().getCode()); // CLASS
+ EXPECT_EQ(0, prereq2->getTTL().getValue()); // TTL
+ EXPECT_EQ(0, prereq2->getRdataCount()); // RDLENGTH
+
+ // Check the Update section. There is only one record, so beginSection()
+ // should return the pointer to this sole record.
+ ASSERT_EQ(1, msg.getRRCount(D2UpdateMessage::SECTION_UPDATE));
+ rrset_it = msg.beginSection(D2UpdateMessage::SECTION_UPDATE);
+ RRsetPtr update = *rrset_it;
+ ASSERT_TRUE(update);
+ // Check the record fields.
+ EXPECT_EQ("foo.example.com.", update->getName().toText()); // NAME
+ EXPECT_EQ(RRType::AAAA().getCode(), update->getType().getCode()); // TYPE
+ EXPECT_EQ(RRClass::IN().getCode(), update->getClass().getCode()); // CLASS
+ EXPECT_EQ(0xAABBCCDD, update->getTTL().getValue()); // TTL
+ // There should be exactly one record holding the IPv6 address.
+ // This record can be accessed using RdataIterator. This record
+ // can be compared with the reference record, holding expected IPv6
+ // address using compare function.
+ ASSERT_EQ(1, update->getRdataCount());
+ RdataIteratorPtr rdata_it = update->getRdataIterator();
+ ASSERT_TRUE(rdata_it);
+ in::AAAA rdata_ref("2001:db8:1::1");
+ EXPECT_EQ(0, rdata_ref.compare(rdata_it->getCurrent()));
+
+ // @todo: at this point we don't test Additional Data records. We may
+ // consider implementing tests for it in the future.
+}
+
+// This test verifies that the fromWire function throws appropriate exception
+// if the message being parsed comprises invalid Opcode (is not a DNS Update).
+TEST_F(D2UpdateMessageTest, fromWireInvalidOpcode) {
+ // This is a binary representation of the DNS message.
+ // It comprises invalid Opcode=3, expected value is 6
+ // (Update).
+ const uint8_t bin_msg[] = {
+ 0x05, 0xAF, // ID=0x05AF
+ 0x98, 0x6, // QR=1, Opcode=3, RCODE=YXDOMAIN
+ 0x0, 0x0, // ZOCOUNT=0
+ 0x0, 0x0, // PRCOUNT=0
+ 0x0, 0x0, // UPCOUNT=0
+ 0x0, 0x0 // ADCOUNT=0
+ };
+ InputBuffer buf(bin_msg, sizeof(bin_msg));
+ // The 'true' argument passed to the constructor turns the
+ // message into the parse mode in which the fromWire function
+ // can be used to decode the binary mesasage data.
+ D2UpdateMessage msg(D2UpdateMessage::INBOUND);
+ // When using invalid Opcode, the fromWire function should
+ // throw NotUpdateMessage exception.
+ EXPECT_THROW(msg.fromWire(buf), isc::d2::NotUpdateMessage);
+}
+
+// This test verifies that the fromWire function throws appropriate exception
+// if the message being parsed comprises invalid QR flag. The QR bit is
+// expected to be set to indicate that the message is a RESPONSE.
+TEST_F(D2UpdateMessageTest, fromWireInvalidQRFlag) {
+ // This is a binary representation of the DNS message.
+ // It comprises invalid QR flag = 0.
+ const uint8_t bin_msg[] = {
+ 0x05, 0xAF, // ID=0x05AF
+ 0x28, 0x6, // QR=0, Opcode=6, RCODE=YXDOMAIN
+ 0x0, 0x0, // ZOCOUNT=0
+ 0x0, 0x0, // PRCOUNT=0
+ 0x0, 0x0, // UPCOUNT=0
+ 0x0, 0x0 // ADCOUNT=0
+ };
+ InputBuffer buf(bin_msg, sizeof(bin_msg));
+ // The 'true' argument passed to the constructor turns the
+ // message into the parse mode in which the fromWire function
+ // can be used to decode the binary mesasage data.
+ D2UpdateMessage msg(D2UpdateMessage::INBOUND);
+ // When using invalid QR flag, the fromWire function should
+ // throw InvalidQRFlag exception.
+ EXPECT_THROW(msg.fromWire(buf), isc::d2::InvalidQRFlag);
+}
+
+// This test verifies that the fromWire function throws appropriate exception
+// if the message being parsed comprises more than one (two in this case)
+// Zone records.
+TEST_F(D2UpdateMessageTest, fromWireTooManyZones) {
+ // This is a binary representation of the DNS message. This message
+ // comprises two Zone records.
+ const uint8_t bin_msg[] = {
+ 0x05, 0xAF, // ID=0x05AF
+ 0xA8, 0x6, // QR=1, Opcode=6, RCODE=YXDOMAIN
+ 0x0, 0x2, // ZOCOUNT=2
+ 0x0, 0x0, // PRCOUNT=0
+ 0x0, 0x0, // UPCOUNT=0
+ 0x0, 0x0, // ADCOUNT=0
+
+ // Start first Zone record.
+ 0x7, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example (7 is length)
+ 0x3, 0x63, 0x6F, 0x6D, //.com. (0x3 is a length)
+ 0x0, // NULL character terminates the Zone name.
+ 0x0, 0x6, // ZTYPE='SOA'
+ 0x0, 0x1, // ZCLASS='IN'
+
+ // Start second Zone record. Presence of this record should result
+ // in error when parsing this message.
+ 0x3, 0x63, 0x6F, 0x6D, // com. (0x3 is a length)
+ 0x0, // NULL character terminates the Zone name.
+ 0x0, 0x6, // ZTYPE='SOA'
+ 0x0, 0x1 // ZCLASS='IN'
+ };
+ InputBuffer buf(bin_msg, sizeof(bin_msg));
+
+ // The 'true' argument passed to the constructor turns the
+ // message into the parse mode in which the fromWire function
+ // can be used to decode the binary mesasage data.
+ D2UpdateMessage msg(D2UpdateMessage::INBOUND);
+ // When parsing a message with more than one Zone record,
+ // exception should be thrown.
+ EXPECT_THROW(msg.fromWire(buf), isc::d2::InvalidZoneSection);
+}
+
+// This test verifies that the wire format of the message is produced
+// in the render mode.
+TEST_F(D2UpdateMessageTest, toWire) {
+ D2UpdateMessage msg;
+ // Set message ID.
+ msg.setId(0x1234);
+ // Rcode to NOERROR.
+ msg.setRcode(Rcode(Rcode::NOERROR_CODE));
+
+ // Set Zone section. This section must comprise exactly
+ // one Zone. toWire function would fail if Zone is not set.
+ msg.setZone(Name("example.com"), RRClass::IN());
+
+ // Set prerequisities.
+
+ // 'Name Is Not In Use' prerequisite (RFC 2136, section 2.4.5)
+ RRsetPtr prereq1(new RRset(Name("foo.example.com"), RRClass::NONE(),
+ RRType::ANY(), RRTTL(0)));
+ msg.addRRset(D2UpdateMessage::SECTION_PREREQUISITE, prereq1);
+
+ // 'Name is In Use' prerequisite (RFC 2136, section 2.4.4)
+ RRsetPtr prereq2(new RRset(Name("bar.example.com"), RRClass::ANY(),
+ RRType::ANY(), RRTTL(0)));
+ msg.addRRset(D2UpdateMessage::SECTION_PREREQUISITE, prereq2);
+
+ // Set Update Section.
+
+ // Create RR holding a name being added. This RR is constructed
+ // in conformance to RFC 2136, section 2.5.1.
+ RRsetPtr updaterr1(new RRset(Name("foo.example.com"), RRClass::IN(),
+ RRType::A(), RRTTL(10)));
+ // RR record is of the type A, thus RDATA holds 4 octet Internet
+ // address. This address is 10.10.1.1.
+ char rdata1[] = {
+ 0xA, 0xA , 0x1, 0x1
+ };
+ InputBuffer buf_rdata1(rdata1, 4);
+ updaterr1->addRdata(createRdata(RRType::A(), RRClass::IN(), buf_rdata1,
+ buf_rdata1.getLength()));
+ // Add the RR to the message.
+ msg.addRRset(D2UpdateMessage::SECTION_UPDATE, updaterr1);
+
+ // Render message into the wire format.
+ MessageRenderer renderer;
+ ASSERT_NO_THROW(msg.toWire(renderer));
+
+ // Make sure that created packet is not truncated.
+ ASSERT_EQ(77, renderer.getLength());
+
+ // Create input buffer from the rendered data. InputBuffer
+ // is handy to validate the byte contents of the rendered
+ // message.
+ InputBuffer buf(renderer.getData(), renderer.getLength());
+
+ // Start validating the message header.
+
+ // Verify message ID.
+ EXPECT_EQ(0x1234, buf.readUint16());
+ // The 2-bytes following message ID comprise the following fields:
+ // - QR - 1 bit indicating that it is REQUEST. Should be 0.
+ // - Opcode - 4 bits which should hold value of 5 indicating this is
+ // an Update message. Binary form is "0101".
+ // - Z - These bits are unused for Update Message and should be 0.
+ // - RCODE - Response code, set to NOERROR for REQUEST. It is 0.
+ //8706391835
+ // The binary value is:
+ // 0 1 2 3 4 5 6 7 8 9 A B C D E F
+ // +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+ // | QR| Opcode | Z | RCODE |
+ // +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+ // | 0 | 0 1 0 1 | 0 0 0 0 0 0 0 | 0 0 0 0 |
+ // +---+---+---+-------+---+---+---+---+---+---+---+---+---+---+---+
+ // and the hexadecimal representation is 0x2800.
+ EXPECT_EQ(0x2800, buf.readUint16());
+
+ // ZOCOUNT - holds the number of zones for the update. For Request
+ // message it must be exactly one record (RFC2136, section 2.3).
+ EXPECT_EQ(1, buf.readUint16());
+
+ // PRCOUNT - holds the number of prerequisites. Earlier we have added
+ // two prerequisites. Thus, expect that this conter is 2.
+ EXPECT_EQ(2, buf.readUint16());
+
+ // UPCOUNT - holds the number of RRs in the Update Section. We have
+ // added 1 RR, which adds the name foo.example.com to the Zone.
+ EXPECT_EQ(1, buf.readUint16());
+
+ // ADCOUNT - holds the number of RRs in the Additional Data Section.
+ EXPECT_EQ(0, buf.readUint16());
+
+ // Start validating the Zone section. This section comprises the
+ // following data:
+ // - ZNAME
+ // - ZTYPE
+ // - ZCLASS
+
+ // ZNAME holds 'example.com.' encoded as set of labels. Each label
+ // is preceded by its length. The name is ended with the byte holding
+ // zero value. This yields the total size of the name in wire format
+ // of 13 bytes.
+
+ // The simplest way to convert the name from wire format to a string
+ // is to use dns::Name class. It should be ok to rely on the Name class
+ // to decode the name, because it is unit tested elswhere.
+ std::string zone_name = readNameFromWire(buf, 13);
+ EXPECT_EQ("example.com.", zone_name);
+
+ // ZTYPE of the Zone section must be SOA according to RFC 2136,
+ // section 2.3.
+ EXPECT_EQ(RRType::SOA().getCode(), buf.readUint16());
+
+ // ZCLASS of the Zone section is IN.
+ EXPECT_EQ(RRClass::IN().getCode(), buf.readUint16());
+
+ // Start checks on Prerequisite section. Each prerequisite comprises
+ // the following fields:
+ // - NAME - name of the RR in wire format
+ // - TYPE - two octets with one of the RR TYPE codes
+ // - CLASS - two octets with one of the RR CLASS codes
+ // - TTL - a 32-bit signed integer specifying Time-To-Live
+ // - RDLENGTH - length of the RDATA field
+ // - RDATA - a variable length string of octets containing
+ // resource data.
+ // In case of this message, we expect to have two prerequisite RRs.
+ // Their structure is checked below.
+
+ // First prerequisite should comprise the 'Name is not in use prerequisite'
+ // for 'foo.example.com'.
+
+ // Check the name first. Message renderer is using compression for domain
+ // names as described in RFC 1035, section 4.1.4. The name in this RR is
+ // foo.example.com. The name of the zone is example.com and it has occured
+ // in this message already at offset 12 (the size of the header is 12).
+ // Therefore, name of this RR is encoded as 'foo', followed by a pointer
+ // to offset in this message where the remainder of this name was used.
+ // This pointer has the following format:
+ // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ // | 1 1| OFFSET |
+ // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ // | 1 1| 0 0 0 0 0 0 0 0 0 0 1 1 0 0|
+ // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ // which has a following hexadecimal representation: 0xC00C
+
+ // Let's read the non-compressed part first - 'foo.'
+ std::string name_prereq1 = readNameFromWire(buf, 4, true);
+ EXPECT_EQ("foo.", name_prereq1);
+ // The remaining two bytes hold the pointer to 'example.com'.
+ EXPECT_EQ(0xC00C, buf.readUint16());
+ // TYPE is ANY
+ EXPECT_EQ(RRType::ANY().getCode(), buf.readUint16());
+ // CLASS is NONE
+ EXPECT_EQ(RRClass::NONE().getCode(), buf.readUint16());
+ // TTL is a 32-but value, expecting 0
+ EXPECT_EQ(0, buf.readUint32());
+ // There is no RDATA, so RDLENGTH is 0
+ EXPECT_EQ(0, buf.readUint16());
+
+ // Start checking second prerequisite.
+
+ std::string name_prereq2 = readNameFromWire(buf, 4, true);
+ EXPECT_EQ("bar.", name_prereq2);
+ // The remaining two bytes hold the pointer to 'example.com'.
+ EXPECT_EQ(0xC00C, buf.readUint16());
+ // TYPE is ANY
+ EXPECT_EQ(RRType::ANY().getCode(), buf.readUint16());
+ // CLASS is ANY
+ EXPECT_EQ(RRClass::ANY().getCode(), buf.readUint16());
+ // TTL is a 32-but value, expecting 0
+ EXPECT_EQ(0, buf.readUint32());
+ // There is no RDATA, so RDLENGTH is 0
+ EXPECT_EQ(0, buf.readUint16());
+
+ // Start checking Update section. This section contains RRset with
+ // one A RR.
+
+ // The name of the RR is 'foo.example.com'. It is encoded in the
+ // compressed format - as a pointer to the name of prerequisite 1.
+ // This name is in offset 0x1D in this message.
+ EXPECT_EQ(0xC01D, buf.readUint16());
+ // TYPE is A
+ EXPECT_EQ(RRType::A().getCode(), buf.readUint16());
+ // CLASS is IN (same as zone class)
+ EXPECT_EQ(RRClass::IN().getCode(), buf.readUint16());
+ // TTL is a 32-but value, set here to 10.
+ EXPECT_EQ(10, buf.readUint32());
+ // For A records, the RDATA comprises the 4-byte Internet address.
+ // So, RDLENGTH is 4.
+ EXPECT_EQ(4, buf.readUint16());
+ // We have stored the following address in RDATA field: 10.10.1.1
+ // (which is 0A 0A 01 01) in hexadecimal format.
+ EXPECT_EQ(0x0A0A0101, buf.readUint32());
+
+ // @todo: consider extending this test to verify Additional Data
+ // section.
+}
+
+// This test verifies that an attempt to call toWire function on the
+// received message will result in an exception.
+TEST_F(D2UpdateMessageTest, toWireInvalidQRFlag) {
+ // This is a binary representation of the DNS message.
+ // This message is valid and should be parsed with no
+ // error.
+ const uint8_t bin_msg[] = {
+ 0x05, 0xAF, // ID=0x05AF
+ 0xA8, 0x6, // QR=1, Opcode=6, RCODE=YXDOMAIN
+ 0x0, 0x0, // ZOCOUNT=0
+ 0x0, 0x0, // PRCOUNT=0
+ 0x0, 0x0, // UPCOUNT=0
+ 0x0, 0x0 // ADCOUNT=0
+ };
+
+ InputBuffer buf(bin_msg, sizeof(bin_msg));
+ // The 'true' argument passed to the constructor turns the
+ // message into the parse mode in which the fromWire function
+ // can be used to decode the binary mesasage data.
+ D2UpdateMessage msg(D2UpdateMessage::INBOUND);
+ ASSERT_NO_THROW(msg.fromWire(buf));
+
+ // The message is parsed. The QR Flag should now indicate that
+ // it is a Response message.
+ ASSERT_EQ(D2UpdateMessage::RESPONSE, msg.getQRFlag());
+
+ // An attempt to call toWire on the Response message should
+ // result in the InvalidQRFlag exception.
+ MessageRenderer renderer;
+ EXPECT_THROW(msg.toWire(renderer), isc::d2::InvalidQRFlag);
+}
+
+} // End of anonymous namespace
diff --git a/src/bin/d2/tests/d2_zone_unittests.cc b/src/bin/d2/tests/d2_zone_unittests.cc
new file mode 100644
index 0000000..853cdbe
--- /dev/null
+++ b/src/bin/d2/tests/d2_zone_unittests.cc
@@ -0,0 +1,75 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <d2/d2_zone.h>
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::d2;
+using namespace isc::dns;
+
+namespace {
+
+// This test verifies that Zone object is created and its constructor sets
+// appropriate values for its members.
+TEST(D2ZoneTest, constructor) {
+ // Create first object.
+ D2Zone zone1(Name("example.com"), RRClass::ANY());
+ EXPECT_EQ("example.com.", zone1.getName().toText());
+ EXPECT_EQ(RRClass::ANY().getCode(), zone1.getClass().getCode());
+ // Create another object to make sure that constructor doesn't assign
+ // fixed values, but they change when constructor's parameters change.
+ D2Zone zone2(Name("foo.example.com"), RRClass::IN());
+ EXPECT_EQ("foo.example.com.", zone2.getName().toText());
+ EXPECT_EQ(RRClass::IN().getCode(), zone2.getClass().getCode());
+}
+
+// This test verifies that toText() function returns text representation of
+// of the zone in expected format.
+TEST(D2ZoneTest, toText) {
+ // Create first object.
+ D2Zone zone1(Name("example.com"), RRClass::ANY());
+ EXPECT_EQ("example.com. ANY SOA\n", zone1.toText());
+ // Create another object with different parameters to make sure that the
+ // function's output changes accordingly.
+ D2Zone zone2(Name("foo.example.com"), RRClass::IN());
+ EXPECT_EQ("foo.example.com. IN SOA\n", zone2.toText());
+}
+
+// This test verifies that the equality and inequality operators behave as
+// expected.
+TEST(D2ZoneTest, compare) {
+ const Name a("a"), b("b");
+ const RRClass in(RRClass::IN()), any(RRClass::ANY());
+
+ // Equality check
+ EXPECT_TRUE(D2Zone(a, any) == D2Zone(a, any));
+ EXPECT_FALSE(D2Zone(a, any) != D2Zone(a, any));
+
+ // Inequality check, objects differ by class.
+ EXPECT_FALSE(D2Zone(a, any) == D2Zone(a, in));
+ EXPECT_TRUE(D2Zone(a, any) != D2Zone(a, in));
+
+ // Inequality check, objects differ by name.
+ EXPECT_FALSE(D2Zone(a, any) == D2Zone(b, any));
+ EXPECT_TRUE(D2Zone(a, any) != D2Zone(b, any));
+
+ // Inequality check, objects differ by name and class.
+ EXPECT_FALSE(D2Zone(a, any) == D2Zone(b, in));
+ EXPECT_TRUE(D2Zone(a, any) != D2Zone(b, in));
+}
+
+} // End of anonymous namespace
diff --git a/src/bin/d2/tests/d_cfg_mgr_unittests.cc b/src/bin/d2/tests/d_cfg_mgr_unittests.cc
new file mode 100644
index 0000000..512b896
--- /dev/null
+++ b/src/bin/d2/tests/d_cfg_mgr_unittests.cc
@@ -0,0 +1,386 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config/ccsession.h>
+#include <config/module_spec.h>
+#include <dhcpsrv/dhcp_parsers.h>
+#include <d2/d_cfg_mgr.h>
+#include <d_test_stubs.h>
+
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <gtest/gtest.h>
+
+#include <config.h>
+#include <sstream>
+
+using namespace std;
+using namespace isc;
+using namespace isc::config;
+using namespace isc::d2;
+using namespace boost::posix_time;
+
+namespace {
+
+/// @brief Test Class for verifying that configuration context cannot be null
+/// during construction.
+class DCtorTestCfgMgr : public DCfgMgrBase {
+public:
+ /// @brief Constructor - Note that is passes in an empty configuration
+ /// pointer to the base class constructor.
+ DCtorTestCfgMgr() : DCfgMgrBase(DCfgContextBasePtr()) {
+ }
+
+ /// @brief Destructor
+ virtual ~DCtorTestCfgMgr() {
+ }
+
+ /// @brief Dummy implementation as this method is abstract.
+ virtual isc::dhcp::ParserPtr
+ createConfigParser(const std::string& /* element_id */) {
+ return (isc::dhcp::ParserPtr());
+ }
+};
+
+/// @brief Test fixture class for testing DCfgMgrBase class.
+/// It maintains an member instance of DStubCfgMgr and derives from
+/// ConfigParseTest fixture, thus providing methods for converting JSON
+/// strings to configuration element sets, checking parse results, and
+/// accessing the configuration context.
+class DStubCfgMgrTest : public ConfigParseTest {
+public:
+
+ /// @brief Constructor
+ DStubCfgMgrTest():cfg_mgr_(new DStubCfgMgr) {
+ }
+
+ /// @brief Destructor
+ ~DStubCfgMgrTest() {
+ }
+
+ /// @brief Convenience method which returns a DStubContextPtr to the
+ /// configuration context.
+ ///
+ /// @return returns a DStubContextPtr.
+ DStubContextPtr getStubContext() {
+ return (boost::dynamic_pointer_cast<DStubContext>
+ (cfg_mgr_->getContext()));
+ }
+
+ /// @brief Configuration manager instance.
+ DStubCfgMgrPtr cfg_mgr_;
+};
+
+///@brief Tests basic construction/destruction of configuration manager.
+/// Verifies that:
+/// 1. Proper construction succeeds.
+/// 2. Configuration context is initialized by construction.
+/// 3. Destruction works properly.
+/// 4. Construction with a null context is not allowed.
+TEST(DCfgMgrBase, construction) {
+ DCfgMgrBasePtr cfg_mgr;
+
+ // Verify that configuration manager constructions without error.
+ ASSERT_NO_THROW(cfg_mgr.reset(new DStubCfgMgr()));
+
+ // Verify that the context can be retrieved and is not null.
+ DCfgContextBasePtr context = cfg_mgr->getContext();
+ EXPECT_TRUE(context);
+
+ // Verify that the manager can be destructed without error.
+ EXPECT_NO_THROW(cfg_mgr.reset());
+
+ // Verify that an attempt to construct a manger with a null context fails.
+ ASSERT_THROW(DCtorTestCfgMgr(), DCfgMgrBaseError);
+}
+
+///@brief Tests fundamental aspects of configuration parsing.
+/// Verifies that:
+/// 1. A correctly formed simple configuration parses without error.
+/// 2. An error building the element is handled.
+/// 3. An error committing the element is handled.
+/// 4. An unknown element error is handled.
+TEST_F(DStubCfgMgrTest, basicParseTest) {
+ // Create a simple configuration.
+ string config = "{ \"test-value\": 1000 } ";
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that we can parse a simple configuration.
+ answer_ = cfg_mgr_->parseConfig(config_set_);
+ EXPECT_TRUE(checkAnswer(0));
+
+ // Verify that an error building the element is caught and returns a
+ // failed parse result.
+ SimFailure::set(SimFailure::ftElementBuild);
+ answer_ = cfg_mgr_->parseConfig(config_set_);
+ EXPECT_TRUE(checkAnswer(1));
+
+ // Verify that an error committing the element is caught and returns a
+ // failed parse result.
+ SimFailure::set(SimFailure::ftElementCommit);
+ answer_ = cfg_mgr_->parseConfig(config_set_);
+ EXPECT_TRUE(checkAnswer(1));
+
+ // Verify that an unknown element error is caught and returns a failed
+ // parse result.
+ SimFailure::set(SimFailure::ftElementUnknown);
+ answer_ = cfg_mgr_->parseConfig(config_set_);
+ EXPECT_TRUE(checkAnswer(1));
+}
+
+///@brief Tests ordered and non-ordered element parsing
+/// This test verifies that:
+/// 1. Non-ordered parsing parses elements in the order they are presented
+/// by the configuration set (as-they-come).
+/// 2. A parse order list with too few elements is detected.
+/// 3. Ordered parsing parses the elements in the order specified by the
+/// configuration manager's parse order list.
+/// 4. A parse order list with too many elements is detected.
+TEST_F(DStubCfgMgrTest, parseOrderTest) {
+ // Element ids used for test.
+ std::string charlie("charlie");
+ std::string bravo("bravo");
+ std::string alpha("alpha");
+
+ // Create the test configuration with the elements in "random" order.
+
+ // NOTE that element sets produced by isc::data::Element::fromJSON(),
+ // are in lexical order by element_id. This means that iterating over
+ // such an element set, will present the elements in lexical order. Should
+ // this change, this test will need to be modified accordingly.
+ string config = "{ \"bravo\": 2, "
+ " \"alpha\": 1, "
+ " \"charlie\": 3 } ";
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that non-ordered parsing, results in an as-they-come parse order.
+ // Create an expected parse order.
+ // (NOTE that iterating over Element sets produced by fromJSON() will
+ // present the elements in lexical order. Should this change, the expected
+ // order list below would need to be changed accordingly).
+ ElementIdList order_expected;
+ order_expected.push_back(alpha);
+ order_expected.push_back(bravo);
+ order_expected.push_back(charlie);
+
+ // Verify that the manager has an EMPTY parse order list. (Empty list
+ // instructs the manager to parse them as-they-come.)
+ EXPECT_EQ(0, cfg_mgr_->getParseOrder().size());
+
+ // Parse the configuration, verify it parses without error.
+ answer_ = cfg_mgr_->parseConfig(config_set_);
+ EXPECT_TRUE(checkAnswer(0));
+
+ // Verify that the parsed order matches what we expected.
+ EXPECT_TRUE(cfg_mgr_->parsed_order_ == order_expected);
+
+ // Clear the manager's parse order "memory".
+ cfg_mgr_->parsed_order_.clear();
+
+ // Create a parse order list that has too few entries. Verify that
+ // when parsing the test config, it fails.
+ cfg_mgr_->addToParseOrder(charlie);
+ // Verify the parse order list is the size we expect.
+ EXPECT_EQ(1, cfg_mgr_->getParseOrder().size());
+
+ // Verify the configuration fails.
+ answer_ = cfg_mgr_->parseConfig(config_set_);
+ EXPECT_TRUE(checkAnswer(1));
+
+ // Verify that the configuration parses correctly, when the parse order
+ // is correct. Add the needed entries to the parse order
+ cfg_mgr_->addToParseOrder(bravo);
+ cfg_mgr_->addToParseOrder(alpha);
+
+ // Verify the parse order list is the size we expect.
+ EXPECT_EQ(3, cfg_mgr_->getParseOrder().size());
+
+ // Clear the manager's parse order "memory".
+ cfg_mgr_->parsed_order_.clear();
+
+ // Verify the configuration parses without error.
+ answer_ = cfg_mgr_->parseConfig(config_set_);
+ EXPECT_TRUE(checkAnswer(0));
+
+ // Verify that the parsed order is the order we configured.
+ EXPECT_TRUE(cfg_mgr_->getParseOrder() == cfg_mgr_->parsed_order_);
+
+ // Create a parse order list that has too many entries. Verify that
+ // when parsing the test config, it fails.
+ cfg_mgr_->addToParseOrder("delta");
+
+ // Verify the parse order list is the size we expect.
+ EXPECT_EQ(4, cfg_mgr_->getParseOrder().size());
+
+ // Verify the configuration fails.
+ answer_ = cfg_mgr_->parseConfig(config_set_);
+ EXPECT_TRUE(checkAnswer(1));
+}
+
+/// @brief Tests that element ids supported by the base class as well as those
+/// added by the derived class function properly.
+/// This test verifies that:
+/// 1. Boolean parameters can be parsed and retrieved.
+/// 2. Uint32 parameters can be parsed and retrieved.
+/// 3. String parameters can be parsed and retrieved.
+/// 4. Derivation-specific parameters can be parsed and retrieved.
+/// 5. Parsing a second configuration, updates the existing context values
+/// correctly.
+TEST_F(DStubCfgMgrTest, simpleTypesTest) {
+ // Fetch a derivation specific pointer to the context.
+ DStubContextPtr context = getStubContext();
+ ASSERT_TRUE(context);
+
+ // Create a configuration with all of the parameters.
+ string config = "{ \"bool_test\": true , "
+ " \"uint32_test\": 77 , "
+ " \"string_test\": \"hmmm chewy\" , "
+ " \"extra_test\": 430 } ";
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that the configuration parses without error.
+ answer_ = cfg_mgr_->parseConfig(config_set_);
+ ASSERT_TRUE(checkAnswer(0));
+
+ // Verify that the boolean parameter was parsed correctly by retrieving
+ // its value from the context.
+ bool actual_bool = false;
+ EXPECT_NO_THROW(context->getParam("bool_test", actual_bool));
+ EXPECT_EQ(true, actual_bool);
+
+ // Verify that the uint32 parameter was parsed correctly by retrieving
+ // its value from the context.
+ uint32_t actual_uint32 = 0;
+ EXPECT_NO_THROW(context->getParam("uint32_test", actual_uint32));
+ EXPECT_EQ(77, actual_uint32);
+
+ // Verify that the string parameter was parsed correctly by retrieving
+ // its value from the context.
+ std::string actual_string = "";
+ EXPECT_NO_THROW(context->getParam("string_test", actual_string));
+ EXPECT_EQ("hmmm chewy", actual_string);
+
+ // Verify that the "extra" parameter was parsed correctly by retrieving
+ // its value from the context.
+ uint32_t actual_extra = 0;
+ EXPECT_NO_THROW(context->getExtraParam("extra_test", actual_extra));
+ EXPECT_EQ(430, actual_extra);
+
+ // Create a configuration which "updates" all of the parameter values.
+ string config2 = "{ \"bool_test\": false , "
+ " \"uint32_test\": 88 , "
+ " \"string_test\": \"ewww yuk!\" , "
+ " \"extra_test\": 11 } ";
+ ASSERT_TRUE(fromJSON(config2));
+
+ // Verify that the configuration parses without error.
+ answer_ = cfg_mgr_->parseConfig(config_set_);
+ EXPECT_TRUE(checkAnswer(0));
+
+ // Verify that the boolean parameter was updated correctly by retrieving
+ // its value from the context.
+ actual_bool = true;
+ EXPECT_NO_THROW(context->getParam("bool_test", actual_bool));
+ EXPECT_FALSE(actual_bool);
+
+ // Verify that the uint32 parameter was updated correctly by retrieving
+ // its value from the context.
+ actual_uint32 = 0;
+ EXPECT_NO_THROW(context->getParam("uint32_test", actual_uint32));
+ EXPECT_EQ(88, actual_uint32);
+
+ // Verify that the string parameter was updated correctly by retrieving
+ // its value from the context.
+ actual_string = "";
+ EXPECT_NO_THROW(context->getParam("string_test", actual_string));
+ EXPECT_EQ("ewww yuk!", actual_string);
+
+ // Verify that the "extra" parameter was updated correctly by retrieving
+ // its value from the context.
+ actual_extra = 0;
+ EXPECT_NO_THROW(context->getExtraParam("extra_test", actual_extra));
+ EXPECT_EQ(11, actual_extra);
+}
+
+/// @brief Tests that the configuration context is preserved after failure
+/// during parsing causes a rollback.
+/// 1. Verifies configuration context rollback.
+TEST_F(DStubCfgMgrTest, rollBackTest) {
+ // Fetch a derivation specific pointer to the context.
+ DStubContextPtr context = getStubContext();
+ ASSERT_TRUE(context);
+
+ // Create a configuration with all of the parameters.
+ string config = "{ \"bool_test\": true , "
+ " \"uint32_test\": 77 , "
+ " \"string_test\": \"hmmm chewy\" , "
+ " \"extra_test\": 430 } ";
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that the configuration parses without error.
+ answer_ = cfg_mgr_->parseConfig(config_set_);
+ EXPECT_TRUE(checkAnswer(0));
+
+ // Verify that all of parameters have the expected values.
+ bool actual_bool = false;
+ EXPECT_NO_THROW(context->getParam("bool_test", actual_bool));
+ EXPECT_EQ(true, actual_bool);
+
+ uint32_t actual_uint32 = 0;
+ EXPECT_NO_THROW(context->getParam("uint32_test", actual_uint32));
+ EXPECT_EQ(77, actual_uint32);
+
+ std::string actual_string = "";
+ EXPECT_NO_THROW(context->getParam("string_test", actual_string));
+ EXPECT_EQ("hmmm chewy", actual_string);
+
+ uint32_t actual_extra = 0;
+ EXPECT_NO_THROW(context->getExtraParam("extra_test", actual_extra));
+ EXPECT_EQ(430, actual_extra);
+
+ // Create a configuration which "updates" all of the parameter values
+ // plus one unknown at the end.
+ string config2 = "{ \"bool_test\": false , "
+ " \"uint32_test\": 88 , "
+ " \"string_test\": \"ewww yuk!\" , "
+ " \"extra_test\": 11 , "
+ " \"zeta_unknown\": 33 } ";
+ ASSERT_TRUE(fromJSON(config2));
+
+ // Force a failure on the last element
+ SimFailure::set(SimFailure::ftElementUnknown);
+ answer_ = cfg_mgr_->parseConfig(config_set_);
+ EXPECT_TRUE(checkAnswer(1));
+
+ // Refresh our local pointer.
+ context = getStubContext();
+
+ // Verify that all of parameters have the original values.
+ actual_bool = false;
+ EXPECT_NO_THROW(context->getParam("bool_test", actual_bool));
+ EXPECT_EQ(true, actual_bool);
+
+ actual_uint32 = 0;
+ EXPECT_NO_THROW(context->getParam("uint32_test", actual_uint32));
+ EXPECT_EQ(77, actual_uint32);
+
+ actual_string = "";
+ EXPECT_NO_THROW(context->getParam("string_test", actual_string));
+ EXPECT_EQ("hmmm chewy", actual_string);
+
+ actual_extra = 0;
+ EXPECT_NO_THROW(context->getExtraParam("extra_test", actual_extra));
+ EXPECT_EQ(430, actual_extra);
+}
+
+} // end of anonymous namespace
diff --git a/src/bin/d2/tests/d_test_stubs.cc b/src/bin/d2/tests/d_test_stubs.cc
index 33a2ff6..4e1cee6 100644
--- a/src/bin/d2/tests/d_test_stubs.cc
+++ b/src/bin/d2/tests/d_test_stubs.cc
@@ -21,6 +21,31 @@ using namespace asio;
namespace isc {
namespace d2 {
+const char* valid_d2_config = "{ "
+ "\"interface\" : \"eth1\" , "
+ "\"ip_address\" : \"192.168.1.33\" , "
+ "\"port\" : 88 , "
+ "\"tsig_keys\": ["
+ "{ \"name\": \"d2_key.tmark.org\" , "
+ " \"algorithm\": \"md5\" ,"
+ " \"secret\": \"0123456989\" "
+ "} ],"
+ "\"forward_ddns\" : {"
+ "\"ddns_domains\": [ "
+ "{ \"name\": \"tmark.org\" , "
+ " \"key_name\": \"d2_key.tmark.org\" , "
+ " \"dns_servers\" : [ "
+ " { \"hostname\": \"one.tmark\" } "
+ "] } ] }, "
+ "\"reverse_ddns\" : {"
+ "\"ddns_domains\": [ "
+ "{ \"name\": \" 0.168.192.in.addr.arpa.\" , "
+ " \"key_name\": \"d2_key.tmark.org\" , "
+ " \"dns_servers\" : [ "
+ " { \"ip_address\": \"127.0.0.101\" , "
+ " \"port\": 100 } ] } "
+ "] } }";
+
// Initialize the static failure flag.
SimFailure::FailureType SimFailure::failure_type_ = SimFailure::ftNoFailure;
@@ -28,7 +53,7 @@ SimFailure::FailureType SimFailure::failure_type_ = SimFailure::ftNoFailure;
const char* DStubProcess::stub_proc_command_("cool_proc_cmd");
DStubProcess::DStubProcess(const char* name, IOServicePtr io_service)
- : DProcessBase(name, io_service) {
+ : DProcessBase(name, io_service, DCfgMgrBasePtr(new DStubCfgMgr())) {
};
void
@@ -130,7 +155,7 @@ DStubController::DStubController()
if (getenv("B10_FROM_BUILD")) {
setSpecFileName(std::string(getenv("B10_FROM_BUILD")) +
- "/src/bin/d2/d2.spec");
+ "/src/bin/d2/dhcp-ddns.spec");
} else {
setSpecFileName(D2_SPECFILE_LOCATION);
}
@@ -191,5 +216,102 @@ DStubController::~DStubController() {
// Initialize controller wrapper's static instance getter member.
DControllerTest::InstanceGetter DControllerTest::instanceGetter_ = NULL;
+//************************** TestParser *************************
+
+TestParser::TestParser(const std::string& param_name):param_name_(param_name) {
+}
+
+TestParser::~TestParser(){
+}
+
+void
+TestParser::build(isc::data::ConstElementPtr new_config) {
+ if (SimFailure::shouldFailOn(SimFailure::ftElementBuild)) {
+ // Simulates an error during element data parsing.
+ isc_throw (DCfgMgrBaseError, "Simulated build exception");
+ }
+
+ value_ = new_config;
+}
+
+void
+TestParser::commit() {
+ if (SimFailure::shouldFailOn(SimFailure::ftElementCommit)) {
+ // Simulates an error while committing the parsed element data.
+ throw std::runtime_error("Simulated commit exception");
+ }
+}
+
+//************************** DStubContext *************************
+
+DStubContext::DStubContext(): extra_values_(new isc::dhcp::Uint32Storage()) {
+}
+
+DStubContext::~DStubContext() {
+}
+
+void
+DStubContext::getExtraParam(const std::string& name, uint32_t& value) {
+ value = extra_values_->getParam(name);
+}
+
+isc::dhcp::Uint32StoragePtr
+DStubContext::getExtraStorage() {
+ return (extra_values_);
+}
+
+DCfgContextBasePtr
+DStubContext::clone() {
+ return (DCfgContextBasePtr(new DStubContext(*this)));
+}
+
+DStubContext::DStubContext(const DStubContext& rhs): DCfgContextBase(rhs),
+ extra_values_(new isc::dhcp::Uint32Storage(*(rhs.extra_values_))) {
+}
+
+//************************** DStubCfgMgr *************************
+
+DStubCfgMgr::DStubCfgMgr()
+ : DCfgMgrBase(DCfgContextBasePtr(new DStubContext())) {
+}
+
+DStubCfgMgr::~DStubCfgMgr() {
+}
+
+isc::dhcp::ParserPtr
+DStubCfgMgr::createConfigParser(const std::string& element_id) {
+ isc::dhcp::DhcpConfigParser* parser = NULL;
+ DStubContextPtr context =
+ boost::dynamic_pointer_cast<DStubContext>(getContext());
+
+ if (element_id == "bool_test") {
+ parser = new isc::dhcp::BooleanParser(element_id,
+ context->getBooleanStorage());
+ } else if (element_id == "uint32_test") {
+ parser = new isc::dhcp::Uint32Parser(element_id,
+ context->getUint32Storage());
+ } else if (element_id == "string_test") {
+ parser = new isc::dhcp::StringParser(element_id,
+ context->getStringStorage());
+ } else if (element_id == "extra_test") {
+ parser = new isc::dhcp::Uint32Parser(element_id,
+ context->getExtraStorage());
+ } else {
+ // Fail only if SimFailure dictates we should. This makes it easier
+ // to test parse ordering, by permitting a wide range of element ids
+ // to "succeed" without specifically supporting them.
+ if (SimFailure::shouldFailOn(SimFailure::ftElementUnknown)) {
+ isc_throw(DCfgMgrBaseError, "Configuration parameter not supported: "
+ << element_id);
+ }
+
+ parsed_order_.push_back(element_id);
+ parser = new TestParser(element_id);
+ }
+
+ return (isc::dhcp::ParserPtr(parser));
+}
+
+
}; // namespace isc::d2
}; // namespace isc
diff --git a/src/bin/d2/tests/d_test_stubs.h b/src/bin/d2/tests/d_test_stubs.h
index e634762..846fb75 100644
--- a/src/bin/d2/tests/d_test_stubs.h
+++ b/src/bin/d2/tests/d_test_stubs.h
@@ -21,11 +21,18 @@
#include <config/ccsession.h>
#include <d2/d_controller.h>
+#include <d2/d_cfg_mgr.h>
+
#include <gtest/gtest.h>
namespace isc {
namespace d2 {
+/// @brief Provides a valid DHCP-DDNS configuration for testing basic
+/// parsing fundamentals.
+extern const char* valid_d2_config;
+
+
/// @brief Class is used to set a globally accessible value that indicates
/// a specific type of failure to simulate. Test derivations of base classes
/// can exercise error handling code paths by testing for specific SimFailure
@@ -43,7 +50,10 @@ public:
ftProcessConfigure,
ftControllerCommand,
ftProcessCommand,
- ftProcessShutdown
+ ftProcessShutdown,
+ ftElementBuild,
+ ftElementCommit,
+ ftElementUnknown
};
/// @brief Sets the SimFailure value to the given value.
@@ -80,10 +90,12 @@ public:
return (false);
}
+ /// @brief Resets the failure type to none.
static void clear() {
failure_type_ = ftNoFailure;
}
+ /// @brief Static value for holding the failure type to simulate.
static enum FailureType failure_type_;
};
@@ -433,6 +445,207 @@ public:
}
};
+/// @brief Simple parser derivation for testing the basics of configuration
+/// parsing.
+class TestParser : public isc::dhcp::DhcpConfigParser {
+public:
+
+ /// @brief Constructor
+ ///
+ /// See @ref DhcpConfigParser class for details.
+ ///
+ /// @param param_name name of the parsed parameter
+ TestParser(const std::string& param_name);
+
+ /// @brief Destructor
+ virtual ~TestParser();
+
+ /// @brief Builds parameter value.
+ ///
+ /// See @ref DhcpConfigParser class for details.
+ ///
+ /// @param new_config pointer to the new configuration
+ /// @throw throws DCfgMgrBaseError if the SimFailure is set to
+ /// ftElementBuild. This allows for the simulation of an
+ /// exception during the build portion of parsing an element.
+ virtual void build(isc::data::ConstElementPtr new_config);
+
+ /// @brief Commits the parsed value to storage.
+ ///
+ /// See @ref DhcpConfigParser class for details.
+ ///
+ /// @throw throws DCfgMgrBaseError if SimFailure is set to ftElementCommit.
+ /// This allows for the simulation of an exception during the commit
+ /// portion of parsing an element.
+ virtual void commit();
+
+private:
+ /// name of the parsed parameter
+ std::string param_name_;
+
+ /// pointer to the parsed value of the parameter
+ isc::data::ConstElementPtr value_;
+};
+
+/// @brief Test Derivation of the DCfgContextBase class.
+///
+/// This class is used to test basic functionality of configuration context.
+/// It adds an additional storage container "extra values" to mimic an
+/// application extension of configuration storage. This permits testing that
+/// both the base class content as well as the application content is
+/// correctly copied during cloning. This is vital to configuration backup
+/// and rollback during configuration parsing.
+class DStubContext : public DCfgContextBase {
+public:
+
+ /// @brief Constructor
+ DStubContext();
+
+ /// @brief Destructor
+ virtual ~DStubContext();
+
+ /// @brief Fetches the value for a given "extra" configuration parameter
+ /// from the context.
+ ///
+ /// @param name is the name of the parameter to retrieve.
+ /// @param value is an output parameter in which to return the retrieved
+ /// value.
+ /// @throw throws DhcpConfigError if the context does not contain the
+ /// parameter.
+ void getExtraParam(const std::string& name, uint32_t& value);
+
+ /// @brief Fetches the extra storage.
+ ///
+ /// @return returns a pointer to the extra storage.
+ isc::dhcp::Uint32StoragePtr getExtraStorage();
+
+ /// @brief Creates a clone of a DStubContext.
+ ///
+ /// @return returns a pointer to the new clone.
+ virtual DCfgContextBasePtr clone();
+
+protected:
+ /// @brief Copy constructor
+ DStubContext(const DStubContext& rhs);
+
+private:
+ /// @brief Private assignment operator, not implemented.
+ DStubContext& operator=(const DStubContext& rhs);
+
+ /// @brief Extra storage for uint32 parameters.
+ isc::dhcp::Uint32StoragePtr extra_values_;
+};
+
+/// @brief Defines a pointer to DStubContext.
+typedef boost::shared_ptr<DStubContext> DStubContextPtr;
+
+/// @brief Test Derivation of the DCfgMgrBase class.
+///
+/// This class is used to test basic functionality of configuration management.
+/// It supports the following configuration elements:
+///
+/// "bool_test" - Boolean element, tests parsing and committing a boolean
+/// configuration parameter.
+/// "uint32_test" - Uint32 element, tests parsing and committing a uint32_t
+/// configuration parameter.
+/// "string_test" - String element, tests parsing and committing a string
+/// configuration parameter.
+/// "extra_test" - "Extra" element, tests parsing and committing an extra
+/// configuration parameter. (This is used to demonstrate
+/// derivation's addition of storage to configuration context.
+///
+/// It also keeps track of the element ids that are parsed in the order they
+/// are parsed. This is used to test ordered and non-ordered parsing.
+class DStubCfgMgr : public DCfgMgrBase {
+public:
+ /// @brief Constructor
+ DStubCfgMgr();
+
+ /// @brief Destructor
+ virtual ~DStubCfgMgr();
+
+ /// @brief Given an element_id returns an instance of the appropriate
+ /// parser. It supports the element ids as described in the class brief.
+ ///
+ /// @param element_id is the string name of the element as it will appear
+ /// in the configuration set.
+ ///
+ /// @return returns a ParserPtr to the parser instance.
+ /// @throw throws DCfgMgrBaseError if SimFailure is ftElementUnknown.
+ virtual isc::dhcp::ParserPtr
+ createConfigParser(const std::string& element_id);
+
+ /// @brief A list for remembering the element ids in the order they were
+ /// parsed.
+ ElementIdList parsed_order_;
+};
+
+/// @brief Defines a pointer to DStubCfgMgr.
+typedef boost::shared_ptr<DStubCfgMgr> DStubCfgMgrPtr;
+
+/// @brief Test fixture base class for any fixtures which test parsing.
+/// It provides methods for converting JSON strings to configuration element
+/// sets and checking parse results
+class ConfigParseTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor
+ ConfigParseTest(){
+ }
+
+ /// @brief Destructor
+ ~ConfigParseTest() {
+ }
+
+ /// @brief Converts a given JSON string into an Element set and stores the
+ /// result the member variable, config_set_.
+ ///
+ /// @param json_text contains the configuration text in JSON format to
+ /// convert.
+ /// @return returns AssertionSuccess if there were no parsing errors,
+ /// AssertionFailure otherwise.
+ ::testing::AssertionResult fromJSON(std::string& json_text) {
+ try {
+ config_set_ = isc::data::Element::fromJSON(json_text);
+ } catch (const isc::Exception &ex) {
+ return ::testing::AssertionFailure()
+ << "JSON text failed to parse:" << ex.what();
+ }
+
+ return ::testing::AssertionSuccess();
+ }
+
+
+ /// @brief Compares the status in the parse result stored in member
+ /// variable answer_ to a given value.
+ ///
+ /// @param should_be is an integer against which to compare the status.
+ ///
+ /// @return returns AssertionSuccess if there were no parsing errors,
+ /// AssertionFailure otherwise.
+ ::testing::AssertionResult checkAnswer(int should_be) {
+ int rcode = 0;
+ isc::data::ConstElementPtr comment;
+ comment = isc::config::parseAnswer(rcode, answer_);
+ if (rcode == should_be) {
+ return testing::AssertionSuccess();
+ }
+
+ return ::testing::AssertionFailure() << "checkAnswer rcode:"
+ << rcode << " comment: " << *comment;
+ }
+
+ /// @brief Configuration set being tested.
+ isc::data::ElementPtr config_set_;
+
+ /// @brief Results of most recent element parsing.
+ isc::data::ConstElementPtr answer_;
+};
+
+/// @brief Defines a small but valid DHCP-DDNS compliant configuration for
+/// testing configuration parsing fundamentals.
+extern const char* valid_d2_config;
+
}; // namespace isc::d2
}; // namespace isc
diff --git a/src/bin/d2/tests/ncr_unittests.cc b/src/bin/d2/tests/ncr_unittests.cc
new file mode 100644
index 0000000..70031c0
--- /dev/null
+++ b/src/bin/d2/tests/ncr_unittests.cc
@@ -0,0 +1,428 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <d2/ncr_msg.h>
+
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <gtest/gtest.h>
+
+#include <algorithm>
+
+using namespace std;
+using namespace isc;
+using namespace isc::d2;
+
+using namespace boost::posix_time;
+
+namespace {
+
+/// @brief Defines a list of valid JSON NameChangeRequest renditions.
+/// They are used as input to test conversion from JSON.
+const char *valid_msgs[] =
+{
+ // Valid Add.
+ "{"
+ " \"change_type\" : 0 , "
+ " \"forward_change\" : true , "
+ " \"reverse_change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip_address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease_expires_on\" : \"19620121T132405\" , "
+ " \"lease_length\" : 1300 "
+ "}",
+ // Valid Remove.
+ "{"
+ " \"change_type\" : 1 , "
+ " \"forward_change\" : true , "
+ " \"reverse_change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip_address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease_expires_on\" : \"19620121T132405\" , "
+ " \"lease_length\" : 1300 "
+ "}",
+ // Valid Add with IPv6 address
+ "{"
+ " \"change_type\" : 0 , "
+ " \"forward_change\" : true , "
+ " \"reverse_change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip_address\" : \"fe80::2acf:e9ff:fe12:e56f\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease_expires_on\" : \"19620121T132405\" , "
+ " \"lease_length\" : 1300 "
+ "}"
+};
+
+/// @brief Defines a list of invalid JSON NameChangeRequest renditions.
+/// They are used as input to test conversion from JSON.
+const char *invalid_msgs[] =
+{
+ // Invalid change type.
+ "{"
+ " \"change_type\" : 7 , "
+ " \"forward_change\" : true , "
+ " \"reverse_change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip_address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease_expires_on\" : \"19620121T132405\" , "
+ " \"lease_length\" : 1300 "
+ "}",
+ // Invalid forward change.
+ "{"
+ " \"change_type\" : 0 , "
+ " \"forward_change\" : \"bogus\" , "
+ " \"reverse_change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip_address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease_expires_on\" : \"19620121T132405\" , "
+ " \"lease_length\" : 1300 "
+ "}",
+ // Invalid reverse change.
+ "{"
+ " \"change_type\" : 0 , "
+ " \"forward_change\" : true , "
+ " \"reverse_change\" : 500 , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip_address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease_expires_on\" : \"19620121T132405\" , "
+ " \"lease_length\" : 1300 "
+ "}",
+ // Forward and reverse change both false.
+ "{"
+ " \"change_type\" : 0 , "
+ " \"forward_change\" : false , "
+ " \"reverse_change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip_address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease_expires_on\" : \"19620121T132405\" , "
+ " \"lease_length\" : 1300 "
+ "}",
+ // Blank FQDN
+ "{"
+ " \"change_type\" : 0 , "
+ " \"forward_change\" : true , "
+ " \"reverse_change\" : false , "
+ " \"fqdn\" : \"\" , "
+ " \"ip_address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease_expires_on\" : \"19620121T132405\" , "
+ " \"lease_length\" : 1300 "
+ "}",
+ // Bad IP address
+ "{"
+ " \"change_type\" : 0 , "
+ " \"forward_change\" : true , "
+ " \"reverse_change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip_address\" : \"xxxxxx\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease_expires_on\" : \"19620121T132405\" , "
+ " \"lease_length\" : 1300 "
+ "}",
+ // Blank DHCID
+ "{"
+ " \"change_type\" : 0 , "
+ " \"forward_change\" : true , "
+ " \"reverse_change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip_address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"\" , "
+ " \"lease_expires_on\" : \"19620121T132405\" , "
+ " \"lease_length\" : 1300 "
+ "}",
+ // Odd number of digits in DHCID
+ "{"
+ " \"change_type\" : 0 , "
+ " \"forward_change\" : true , "
+ " \"reverse_change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip_address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3\" , "
+ " \"lease_expires_on\" : \"19620121T132405\" , "
+ " \"lease_length\" : 1300 "
+ "}",
+ // Text in DHCID
+ "{"
+ " \"change_type\" : 0 , "
+ " \"forward_change\" : true , "
+ " \"reverse_change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip_address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"THIS IS BOGUS!!!\" , "
+ " \"lease_expires_on\" : \"19620121T132405\" , "
+ " \"lease_length\" : 1300 "
+ "}",
+ // Invalid lease expiration string
+ "{"
+ " \"change_type\" : 0 , "
+ " \"forward_change\" : true , "
+ " \"reverse_change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip_address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease_expires_on\" : \"Wed Jun 26 13:46:46 EDT 2013\" , "
+ " \"lease_length\" : 1300 "
+ "}",
+ // Non-integer for lease length.
+ "{"
+ " \"change_type\" : 0 , "
+ " \"forward_change\" : true , "
+ " \"reverse_change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip_address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease_expires_on\" : \"19620121T132405\" , "
+ " \"lease_length\" : \"BOGUS\" "
+ "}"
+
+};
+
+/// @brief Tests the NameChangeRequest constructors.
+/// This test verifies that:
+/// 1. Default constructor works.
+/// 2. "Full" constructor, when given valid parameter values, works.
+/// 3. "Full" constructor, given a blank FQDN fails
+/// 4. "Full" constructor, given an invalid IP Address FQDN fails
+/// 5. "Full" constructor, given a blank DHCID fails
+/// 6. "Full" constructor, given an invalid lease expiration fails
+/// 7. "Full" constructor, given false for both forward and reverse fails
+TEST(NameChangeRequestTest, constructionTests) {
+ // Verify the default constructor works.
+ NameChangeRequestPtr ncr;
+ EXPECT_NO_THROW(ncr.reset(new NameChangeRequest()));
+ EXPECT_TRUE(ncr);
+
+ // Verify that full constructor works.
+ ptime expiry(second_clock::universal_time());
+ D2Dhcid dhcid("010203040A7F8E3D");
+
+ EXPECT_NO_THROW(ncr.reset(new NameChangeRequest(
+ CHG_ADD, true, true, "walah.walah.com",
+ "192.168.1.101", dhcid, expiry, 1300)));
+ EXPECT_TRUE(ncr);
+ ncr.reset();
+
+ // Verify blank FQDN is detected.
+ EXPECT_THROW(NameChangeRequest(CHG_ADD, true, true, "",
+ "192.168.1.101", dhcid, expiry, 1300), NcrMessageError);
+
+ // Verify that an invalid IP address is detected.
+ EXPECT_THROW(NameChangeRequest(CHG_ADD, true, true, "valid.fqdn",
+ "xxx.168.1.101", dhcid, expiry, 1300), NcrMessageError);
+
+ // Verify that a blank DHCID is detected.
+ D2Dhcid blank_dhcid;
+ EXPECT_THROW(NameChangeRequest(CHG_ADD, true, true, "walah.walah.com",
+ "192.168.1.101", blank_dhcid, expiry, 1300), NcrMessageError);
+
+ // Verify that an invalid lease expiration is detected.
+ ptime blank_expiry;
+ EXPECT_THROW(NameChangeRequest(CHG_ADD, true, true, "valid.fqdn",
+ "192.168.1.101", dhcid, blank_expiry, 1300), NcrMessageError);
+
+ // Verify that one or both of direction flags must be true.
+ EXPECT_THROW(NameChangeRequest(CHG_ADD, false, false, "valid.fqdn",
+ "192.168.1.101", dhcid, expiry, 1300), NcrMessageError);
+
+}
+
+/// @brief Tests the basic workings of D2Dhcid to and from string conversions.
+/// It verifies that:
+/// 1. DHCID input strings must contain an even number of characters
+/// 2. DHCID input strings must contain only hexadecimal character digits
+/// 3. A valid DHCID string converts correctly.
+/// 4. Converting a D2Dhcid to a string works correctly.
+TEST(NameChangeRequestTest, dhcidTest) {
+ D2Dhcid dhcid;
+
+ // Odd number of digits should be rejected.
+ std::string test_str = "010203040A7F8E3";
+ EXPECT_THROW(dhcid.fromStr(test_str), NcrMessageError);
+
+ // Non digit content should be rejected.
+ test_str = "0102BOGUSA7F8E3D";
+ EXPECT_THROW(dhcid.fromStr(test_str), NcrMessageError);
+
+ // Verify that valid input converts into a proper byte array.
+ test_str = "010203040A7F8E3D";
+ ASSERT_NO_THROW(dhcid.fromStr(test_str));
+
+ // Create a test vector of expected byte contents.
+ const uint8_t bytes[] = { 0x1, 0x2, 0x3, 0x4, 0xA, 0x7F, 0x8E, 0x3D };
+ std::vector<uint8_t> expected_bytes(bytes, bytes + sizeof(bytes));
+
+ // Fetch the byte vector from the dhcid and verify if equals the expected
+ // content.
+ const std::vector<uint8_t>& converted_bytes = dhcid.getBytes();
+ EXPECT_EQ(expected_bytes.size(), converted_bytes.size());
+ EXPECT_TRUE (std::equal(expected_bytes.begin(),
+ expected_bytes.begin()+expected_bytes.size(),
+ converted_bytes.begin()));
+
+ // Convert the new dhcid back to string and verify it matches the original
+ // DHCID input string.
+ std::string next_str = dhcid.toStr();
+ EXPECT_EQ(test_str, next_str);
+}
+
+/// @brief Tests that boost::posix_time library functions as expected.
+/// This test verifies that converting ptimes to and from ISO strings
+/// works properly. This test is perhaps unnecessary but just to avoid any
+/// OS specific surprises it is better safe than sorry.
+TEST(NameChangeRequestTest, boostTime) {
+ // Create a ptime with the time now.
+ ptime pt1(second_clock::universal_time());
+
+ // Get the ISO date-time string.
+ std::string pt1_str = to_iso_string(pt1);
+
+ // Use the ISO date-time string to create a new ptime.
+ ptime pt2 = from_iso_string(pt1_str);
+
+ // Verify the two times are equal.
+ EXPECT_EQ (pt1, pt2);
+}
+
+/// @brief Verifies the fundamentals of converting from and to JSON.
+/// It verifies that:
+/// 1. A NameChangeRequest can be created from a valid JSON string.
+/// 2. A valid JSON string can be created from a NameChangeRequest
+TEST(NameChangeRequestTest, basicJsonTest) {
+ // Define valid JSON rendition of a request.
+ std::string msg_str = "{"
+ "\"change_type\":1,"
+ "\"forward_change\":true,"
+ "\"reverse_change\":false,"
+ "\"fqdn\":\"walah.walah.com\","
+ "\"ip_address\":\"192.168.2.1\","
+ "\"dhcid\":\"010203040A7F8E3D\","
+ "\"lease_expires_on\":\"19620121T132405\","
+ "\"lease_length\":1300"
+ "}";
+
+ // Verify that a NameChangeRequests can be instantiated from the
+ // a valid JSON rendition.
+ NameChangeRequestPtr ncr;
+ ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(msg_str));
+ ASSERT_TRUE(ncr);
+
+ // Verify that the JSON string created by the new request equals the
+ // original input string.
+ std::string json_str = ncr->toJSON();
+ EXPECT_EQ(msg_str, json_str);
+}
+
+/// @brief Tests a variety of invalid JSON message strings.
+/// This test iterates over a list of JSON messages, each containing a single
+/// content error. The list of messages is defined by the global array,
+/// invalid_messages. Currently that list contains the following invalid
+/// conditions:
+/// 1. Invalid change type
+/// 2. Invalid forward change
+/// 3. Invalid reverse change
+/// 4. Forward and reverse change both false
+/// 5. Invalid forward change
+/// 6. Blank FQDN
+/// 7. Bad IP address
+/// 8. Blank DHCID
+/// 9. Odd number of digits in DHCID
+/// 10. Text in DHCID
+/// 11. Invalid lease expiration string
+/// 12. Non-integer for lease length.
+/// If more permutations arise they can easily be added to the list.
+TEST(NameChangeRequestTest, invalidMsgChecks) {
+ // Iterate over the list of JSON strings, attempting to create a
+ // NameChangeRequest. The attempt should throw a NcrMessageError.
+ int num_msgs = sizeof(invalid_msgs)/sizeof(char*);
+ for (int i = 0; i < num_msgs; i++) {
+ EXPECT_THROW(NameChangeRequest::fromJSON(invalid_msgs[i]),
+ NcrMessageError) << "Invalid message not caught idx: "
+ << i << std::endl << " text:[" << invalid_msgs[i] << "]"
+ << std::endl;
+ }
+}
+
+/// @brief Tests a variety of valid JSON message strings.
+/// This test iterates over a list of JSON messages, each containing a single
+/// valid request rendition. The list of messages is defined by the global
+/// array, valid_messages. Currently that list contains the following valid
+/// messages:
+/// 1. Valid, IPv4 Add
+/// 2. Valid, IPv4 Remove
+/// 3. Valid, IPv6 Add
+/// If more permutations arise they can easily be added to the list.
+TEST(NameChangeRequestTest, validMsgChecks) {
+ // Iterate over the list of JSON strings, attempting to create a
+ // NameChangeRequest. The attempt should succeed.
+ int num_msgs = sizeof(valid_msgs)/sizeof(char*);
+ for (int i = 0; i < num_msgs; i++) {
+ EXPECT_NO_THROW(NameChangeRequest::fromJSON(valid_msgs[i]))
+ << "Valid message failed, message idx: " << i
+ << std::endl << " text:[" << valid_msgs[i] << "]"
+ << std::endl;
+ }
+}
+
+/// @brief Tests converting to and from JSON via isc::util buffer classes.
+/// This test verifies that:
+/// 1. A NameChangeRequest can be rendered in JSON written to an OutputBuffer
+/// 2. A InputBuffer containing a valid JSON request rendition can be used
+/// to create a NameChangeRequest.
+TEST(NameChangeRequestTest, toFromBufferTest) {
+ // Define a string containing a valid JSON NameChangeRequest rendition.
+ std::string msg_str = "{"
+ "\"change_type\":1,"
+ "\"forward_change\":true,"
+ "\"reverse_change\":false,"
+ "\"fqdn\":\"walah.walah.com\","
+ "\"ip_address\":\"192.168.2.1\","
+ "\"dhcid\":\"010203040A7F8E3D\","
+ "\"lease_expires_on\":\"19620121T132405\","
+ "\"lease_length\":1300"
+ "}";
+
+ // Create a request from JSON directly.
+ NameChangeRequestPtr ncr;
+ ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(msg_str));
+
+ // Verify that we output the request as JSON text to a buffer
+ // without error.
+ isc::util::OutputBuffer output_buffer(1024);
+ ASSERT_NO_THROW(ncr->toFormat(FMT_JSON, output_buffer));
+
+ // Make an InputBuffer from the OutputBuffer.
+ isc::util::InputBuffer input_buffer(output_buffer.getData(),
+ output_buffer.getLength());
+
+ // Verify that we can create a new request from the InputBuffer.
+ NameChangeRequestPtr ncr2;
+ ASSERT_NO_THROW(ncr2 =
+ NameChangeRequest::fromFormat(FMT_JSON, input_buffer));
+
+ // Convert the new request to JSON directly.
+ std::string final_str = ncr2->toJSON();
+
+ // Verify that the final string matches the original.
+ ASSERT_EQ(final_str, msg_str);
+}
+
+
+} // end of anonymous namespace
+
diff --git a/src/bin/d2/tests/test_data_files_config.h.in b/src/bin/d2/tests/test_data_files_config.h.in
new file mode 100644
index 0000000..6064d3d
--- /dev/null
+++ b/src/bin/d2/tests/test_data_files_config.h.in
@@ -0,0 +1,17 @@
+// Copyright (C) 2009 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/// @brief Path to D2 source dir so tests against the dhcp-ddns.spec file
+/// can find it reliably.
+#define D2_SRC_DIR "@abs_top_srcdir@/src/bin/d2"
diff --git a/src/bin/dhcp4/dhcp4_srv.cc b/src/bin/dhcp4/dhcp4_srv.cc
index 01e7da7..8aa913c 100644
--- a/src/bin/dhcp4/dhcp4_srv.cc
+++ b/src/bin/dhcp4/dhcp4_srv.cc
@@ -126,7 +126,8 @@ bool
Dhcpv4Srv::run() {
while (!shutdown_) {
/// @todo: calculate actual timeout once we have lease database
- int timeout = 1000;
+ //cppcheck-suppress variableScope This is temporary anyway
+ const int timeout = 1000;
// client's message and server's response
Pkt4Ptr query;
diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc
index 4f5133c..ef69a94 100644
--- a/src/bin/dhcp6/dhcp6_srv.cc
+++ b/src/bin/dhcp6/dhcp6_srv.cc
@@ -134,7 +134,8 @@ bool Dhcpv6Srv::run() {
/// For now, we are just calling select for 1000 seconds. There
/// were some issues reported on some systems when calling select()
/// with too large values. Unfortunately, I don't recall the details.
- int timeout = 1000;
+ //cppcheck-suppress variableScope This is temporary anyway
+ const int timeout = 1000;
// client's message and server's response
Pkt6Ptr query;
diff --git a/src/bin/memmgr/.gitignore b/src/bin/memmgr/.gitignore
new file mode 100644
index 0000000..3743d58
--- /dev/null
+++ b/src/bin/memmgr/.gitignore
@@ -0,0 +1,4 @@
+/b10-memmgr
+/memmgr.py
+/memmgr.spec
+/b10-memmgr.8
diff --git a/src/bin/memmgr/Makefile.am b/src/bin/memmgr/Makefile.am
new file mode 100644
index 0000000..55c4601
--- /dev/null
+++ b/src/bin/memmgr/Makefile.am
@@ -0,0 +1,62 @@
+SUBDIRS = . tests
+
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+
+pkglibexec_SCRIPTS = b10-memmgr
+
+b10_memmgrdir = $(pkgdatadir)
+b10_memmgr_DATA = memmgr.spec
+
+nodist_pylogmessage_PYTHON = $(PYTHON_LOGMSGPKG_DIR)/work/memmgr_messages.py
+pylogmessagedir = $(pyexecdir)/isc/log_messages/
+
+CLEANFILES = b10-memmgr memmgr.pyc
+CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/memmgr_messages.py
+CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/memmgr_messages.pyc
+CLEANFILES += memmgr.spec
+
+EXTRA_DIST = memmgr_messages.mes
+
+man_MANS = b10-memmgr.8
+DISTCLEANFILES = $(man_MANS)
+EXTRA_DIST += $(man_MANS) b10-memmgr.xml
+
+if GENERATE_DOCS
+
+b10-memmgr.8: b10-memmgr.xml
+ @XSLTPROC@ --novalid --xinclude --nonet -o $@ http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $(srcdir)/b10-memmgr.xml
+
+else
+
+$(man_MANS):
+ @echo Man generation disabled. Creating dummy $@. Configure with --enable-generate-docs to enable it.
+ @echo Man generation disabled. Remove this file, configure with --enable-generate-docs, and rebuild BIND 10 > $@
+
+endif
+
+# Define rule to build logging source files from message file
+$(PYTHON_LOGMSGPKG_DIR)/work/memmgr_messages.py : memmgr_messages.mes
+ $(top_builddir)/src/lib/log/compiler/message \
+ -d $(PYTHON_LOGMSGPKG_DIR)/work -p $(srcdir)/memmgr_messages.mes
+
+memmgr.spec: memmgr.spec.pre
+ $(SED) -e "s|@@LOCALSTATEDIR@@|$(localstatedir)|" memmgr.spec.pre > $@
+
+# this is done here since configure.ac AC_OUTPUT doesn't expand exec_prefix
+b10-memmgr: memmgr.py $(PYTHON_LOGMSGPKG_DIR)/work/memmgr_messages.py
+ $(SED) -e "s|@@PYTHONPATH@@|@pyexecdir@|" memmgr.py >$@
+ chmod a+x $@
+
+CLEANDIRS = __pycache__
+
+# install the default directory for memory-mapped files. Note that the
+# path must be identical to the default value in memmgr.spec. We'll make
+# it readable only for the owner to minimize the risk of accidents.
+install-data-local:
+ $(mkinstalldirs) $(DESTDIR)@localstatedir@/@PACKAGE@/mapped_files
+
+install-data-hook:
+ -chmod 700 $(DESTDIR)@localstatedir@/@PACKAGE@/mapped_files
+
+clean-local:
+ rm -rf $(CLEANDIRS)
diff --git a/src/bin/memmgr/b10-memmgr.xml b/src/bin/memmgr/b10-memmgr.xml
new file mode 100644
index 0000000..ee7fee2
--- /dev/null
+++ b/src/bin/memmgr/b10-memmgr.xml
@@ -0,0 +1,109 @@
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+ "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"
+ [<!ENTITY mdash "—">]>
+<!--
+ - Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+ -
+ - Permission to use, copy, modify, and/or distribute this software for any
+ - purpose with or without fee is hereby granted, provided that the above
+ - copyright notice and this permission notice appear in all copies.
+ -
+ - THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+ - REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+ - AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+ - INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+ - LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+ - OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ - PERFORMANCE OF THIS SOFTWARE.
+-->
+
+<refentry>
+
+ <refentryinfo>
+ <date>June 11, 2013</date>
+ </refentryinfo>
+
+ <refmeta>
+ <refentrytitle>b10-memmgr</refentrytitle>
+ <manvolnum>8</manvolnum>
+ <refmiscinfo>BIND10</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+ <refname>b10-memmgr</refname>
+ <refpurpose>BIND 10 memory manager daemon</refpurpose>
+ </refnamediv>
+
+ <docinfo>
+ <copyright>
+ <year>2013</year>
+ <holder>Internet Systems Consortium, Inc. ("ISC")</holder>
+ </copyright>
+ </docinfo>
+
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ <command>b10-memmgr</command>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>DESCRIPTION</title>
+ <para>The <command>b10-memmgr</command> daemon manages shared
+ memory segments storing in-memory DNS zone data, and
+ communicates with other BIND 10 modules about the segment
+ information so the entire system will use these segments
+ in a consistent manner.
+ </para>
+ </refsect1>
+
+ <refsect1>
+ <title>ARGUMENTS</title>
+
+ <para>The <command>b10-memmgr</command> daemon does not take
+ any command line arguments.
+ </para>
+ </refsect1>
+
+ <refsect1>
+ <title>CONFIGURATION AND COMMANDS</title>
+ <para>
+ The configurable settings are:
+ </para>
+ <para>
+ <varname>mapped_file_dir</varname>
+ A path to store files to be mapped to memory. This must be
+ writable to the <command>b10-memmgr</command> daemon.
+ </para>
+
+ <para>
+ The module commands are:
+ </para>
+ <para>
+ <command>shutdown</command> exits <command>b10-memmgr</command>.
+ </para>
+ </refsect1>
+
+
+ <refsect1>
+ <title>SEE ALSO</title>
+ <para>
+ <citerefentry>
+ <refentrytitle>bind10</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>,
+ <citetitle>BIND 10 Guide</citetitle>.
+ </para>
+ </refsect1>
+
+ <refsect1>
+ <title>HISTORY</title>
+ <para>
+ The <command>b10-memmgr</command> daemon was first implemented
+ in 2013 for the ISC BIND 10 project.
+ </para>
+ </refsect1>
+</refentry><!--
+ - Local variables:
+ - mode: sgml
+ - End:
+-->
diff --git a/src/bin/memmgr/memmgr.py.in b/src/bin/memmgr/memmgr.py.in
new file mode 100755
index 0000000..5c9040f
--- /dev/null
+++ b/src/bin/memmgr/memmgr.py.in
@@ -0,0 +1,214 @@
+#!@PYTHON@
+
+# Copyright (C) 2013 Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+import copy
+import os
+import sys
+import signal
+import socket
+import threading
+
+sys.path.append('@@PYTHONPATH@@')
+import isc.log
+from isc.config import ModuleSpecError, ModuleCCSessionError
+from isc.log_messages.memmgr_messages import *
+from isc.server_common.bind10_server import BIND10Server, BIND10ServerFatal
+from isc.server_common.datasrc_clients_mgr \
+ import DataSrcClientsMgr, ConfigError
+from isc.memmgr.datasrc_info import DataSrcInfo
+from isc.memmgr.builder import MemorySegmentBuilder
+import isc.util.process
+
+MODULE_NAME = 'memmgr'
+
+isc.log.init('b10-memmgr', buffer=True)
+logger = isc.log.Logger(MODULE_NAME)
+
+isc.util.process.rename()
+
+class ConfigError(Exception):
+ """An exception class raised for configuration errors of Memmgr."""
+ pass
+
+class Memmgr(BIND10Server):
+ def __init__(self):
+ BIND10Server.__init__(self)
+ # Running configurable parameters: on initial configuration this will
+ # be a dict: str=>config_value.
+ # This is defined as "protected" so tests can inspect it; others
+ # shouldn't use it directly.
+ self._config_params = None
+
+ # The manager to keep track of data source configuration. Allow
+ # tests to inspect/tweak it.
+ self._datasrc_clients_mgr = DataSrcClientsMgr(use_cache=True)
+
+ # List of DataSrcInfo. Used as a queue to maintain info for all
+ # active configuration generations. Allow tests to inspec it.
+ self._datasrc_info_list = []
+
+ self._builder_setup = False
+ self._builder_command_queue = []
+ self._builder_response_queue = []
+
+ def _config_handler(self, new_config):
+ """Configuration handler, called via BIND10Server.
+
+ This method must be exception free. We assume minimum validity
+ about the parameter, though: it should be a valid dict, and conform
+ to the type specification of the spec file.
+
+ """
+ logger.debug(logger.DBGLVL_TRACE_BASIC, MEMMGR_CONFIG_UPDATE)
+
+ # Default answer:
+ answer = isc.config.create_answer(0)
+
+ # If this is the first time, initialize the local attributes with the
+ # latest full config data, which consist of the defaults with
+ # possibly overridden by user config. Otherwise, just apply the latest
+ # diff.
+ if self._config_params is None:
+ new_config = self.mod_ccsession.get_full_config()
+ try:
+ self.__update_config(new_config)
+ except Exception as ex:
+ logger.error(MEMMGR_CONFIG_FAIL, ex)
+ answer = isc.config.create_answer(
+ 1, 'Memmgr failed to apply configuration updates: ' + str(ex))
+
+ return answer
+
+ def __update_config(self, new_config):
+ """Apply config changes to local attributes.
+
+ This is a subroutine of _config_handler. It's supposed to provide
+ strong exception guarantee: either all changes successfully apply
+ or, if any error is found, none applies. In the latter case the
+ entire original configuration should be kept.
+
+ Errors are to be reported as an exception.
+
+ """
+ # If this is the first time, build everything from the scratch.
+ # Otherwise, make a full local copy and update it.
+ if self._config_params is None:
+ new_config_params = {}
+ else:
+ new_config_params = copy.deepcopy(self._config_params)
+
+ new_mapped_file_dir = new_config.get('mapped_file_dir')
+ if new_mapped_file_dir is not None:
+ if not os.path.isdir(new_mapped_file_dir):
+ raise ConfigError('mapped_file_dir is not a directory: ' +
+ new_mapped_file_dir)
+ if not os.access(new_mapped_file_dir, os.W_OK):
+ raise ConfigError('mapped_file_dir is not writable: ' +
+ new_mapped_file_dir)
+ new_config_params['mapped_file_dir'] = new_mapped_file_dir
+
+ # All copy, switch to the new configuration.
+ self._config_params = new_config_params
+
+ def __notify_from_builder(self):
+ # Nothing is implemented here for now. This method should have
+ # code to handle responses from the builder in
+ # self._builder_response_queue[]. Access must be synchronized
+ # using self._builder_lock.
+ pass
+
+ def __create_builder_thread(self):
+ # We get responses from the builder thread on this socket pair.
+ (self._master_sock, self._builder_sock) = \
+ socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)
+ self.watch_fileno(self._master_sock, rcallback=self.__notify_from_builder)
+
+ # See the documentation for MemorySegmentBuilder on how the
+ # following are used.
+ self._builder_lock = threading.Lock()
+ self._builder_cv = threading.Condition(lock=self._builder_lock)
+
+ self._builder = MemorySegmentBuilder(self._builder_sock,
+ self._builder_cv,
+ self._builder_command_queue,
+ self._builder_response_queue)
+ self._builder_thread = threading.Thread(target=self._builder.run)
+ self._builder_thread.start()
+
+ self._builder_setup = True
+
+ def __shutdown_builder_thread(self):
+ # Some unittests do not create the builder thread, so we check
+ # that.
+ if not self._builder_setup:
+ return
+
+ self._builder_setup = False
+
+ # This makes the MemorySegmentBuilder exit its main loop. It
+ # should make the builder thread joinable.
+ with self._builder_cv:
+ self._builder_command_queue.append('shutdown')
+ self._builder_cv.notify_all()
+
+ self._builder_thread.join()
+
+ self._master_sock.close()
+ self._builder_sock.close()
+
+ def _setup_module(self):
+ """Module specific initialization for BIND10Server."""
+ try:
+ # memmgr isn't usable if data source is not configured, and
+ # as long as cfgmgr is ready there's no timing issue. So we
+ # immediately shut it down if it's missing. See ddns.py.in
+ # about exceptions to catch.
+ self.mod_ccsession.add_remote_config_by_name(
+ 'data_sources', self._datasrc_config_handler)
+ except (ModuleSpecError, ModuleCCSessionError) as ex:
+ logger.error(MEMMGR_NO_DATASRC_CONF, ex)
+ raise BIND10ServerFatal('failed to setup memmgr module')
+
+ self.__create_builder_thread()
+
+ def _shutdown_module(self):
+ """Module specific finalization."""
+ self.__shutdown_builder_thread()
+
+ def _datasrc_config_handler(self, new_config, config_data):
+ """Callback of data_sources configuration update.
+
+ This method must be exception free, so we catch all expected
+ exceptions internally; unexpected ones should mean a programming
+ error and will terminate the program.
+
+ """
+ try:
+ self._datasrc_clients_mgr.reconfigure(new_config, config_data)
+ genid, clients_map = self._datasrc_clients_mgr.get_clients_map()
+ datasrc_info = DataSrcInfo(genid, clients_map, self._config_params)
+ self._datasrc_info_list.append(datasrc_info)
+
+ # Full datasrc reconfig will be rare, so would be worth logging
+ # at the info level.
+ logger.info(MEMMGR_DATASRC_RECONFIGURED, genid)
+ except isc.server_common.datasrc_clients_mgr.ConfigError as ex:
+ logger.error(MEMMGR_DATASRC_CONFIG_ERROR, ex)
+
+if '__main__' == __name__:
+ mgr = Memmgr()
+ sys.exit(mgr.run(MODULE_NAME))
diff --git a/src/bin/memmgr/memmgr.spec.pre.in b/src/bin/memmgr/memmgr.spec.pre.in
new file mode 100644
index 0000000..6729690
--- /dev/null
+++ b/src/bin/memmgr/memmgr.spec.pre.in
@@ -0,0 +1,25 @@
+{
+ "module_spec": {
+ "module_name": "Memmgr",
+ "config_data": [
+ { "item_name": "mapped_file_dir",
+ "item_type": "string",
+ "item_optional": true,
+ "item_default": "@@LOCALSTATEDIR@@/@PACKAGE@/mapped_files"
+ }
+ ],
+ "commands": [
+ {
+ "command_name": "shutdown",
+ "command_description": "Shut down Memmgr",
+ "command_args": [
+ {
+ "item_name": "pid",
+ "item_type": "integer",
+ "item_optional": true
+ }
+ ]
+ }
+ ]
+ }
+}
diff --git a/src/bin/memmgr/memmgr_messages.mes b/src/bin/memmgr/memmgr_messages.mes
new file mode 100644
index 0000000..6ca5c0f
--- /dev/null
+++ b/src/bin/memmgr/memmgr_messages.mes
@@ -0,0 +1,51 @@
+# Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+# When you add a message to this file, it is a good idea to run
+# <topsrcdir>/tools/reorder_message_file.py to make sure the
+# messages are in the correct order.
+
+% MEMMGR_CONFIG_FAIL failed to apply configuration updates: %1
+The memmgr daemon tried to apply configuration updates but found an error.
+The cause of the error is included in the message. None of the received
+updates applied, and the daemon keeps running with the previous configuration.
+
+% MEMMGR_CONFIG_UPDATE received new configuration
+A debug message. The memmgr daemon receives configuratiopn updates
+and is now applying them to its running configurations.
+
+% MEMMGR_DATASRC_CONFIG_ERROR failed to update data source configuration: %1
+Configuration for the global data sources is updated, but the update
+cannot be applied to memmgr. The memmgr module will still keep running
+with the previous configuration, but the cause of the failure and
+other log messages must be carefully examined because if only memmgr
+rejects the new configuration then the entire BIND 10 system will have
+inconsistent state among different modules. If other modules accept
+the update but memmgr produces this error, it's quite likely that the
+system isn't working as expected, and is probably better to be shut down
+to figure out and fix the cause.
+
+% MEMMGR_DATASRC_RECONFIGURED data source configuration has been updated, generation ID %1
+The memmgr daemon received a new version of data source configuration,
+and has successfully applied it to the local state. Loading of new zone
+data into memory will possibly take place.
+
+% MEMMGR_NO_DATASRC_CONF failed to add data source configuration: %1
+The memmgr daemon tried to incorporate data source configuration on
+its startup but failed to do so. Due to internal implementation
+details this shouldn't happen as long as the BIND 10 system has been
+installed correctly. So, if this error message is logged, you should
+probably reinstall the entire system, preferably from the scratch, and
+see if it still happens. The memmgr daemon cannot do any meaningful
+work without data sources, so it immediately terminates itself.
diff --git a/src/bin/memmgr/tests/Makefile.am b/src/bin/memmgr/tests/Makefile.am
new file mode 100644
index 0000000..347ff87
--- /dev/null
+++ b/src/bin/memmgr/tests/Makefile.am
@@ -0,0 +1,30 @@
+PYCOVERAGE_RUN=@PYCOVERAGE_RUN@
+PYTESTS = memmgr_test.py
+EXTRA_DIST = $(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/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.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/util/threads/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$(abs_top_builddir)/src/lib/acl/.libs:$$$(ENV_LIBRARY_PATH)
+endif
+
+# test using command-line arguments, so use check-local target instead of TESTS
+# We set B10_FROM_BUILD below, so that the test can refer to the in-source
+# spec file.
+check-local:
+if ENABLE_PYTHON_COVERAGE
+ touch $(abs_top_srcdir)/.coverage
+ rm -f .coverage
+ ${LN_S} $(abs_top_srcdir)/.coverage .coverage
+endif
+ for pytest in $(PYTESTS) ; do \
+ echo Running test: $$pytest ; \
+ B10_FROM_SOURCE=$(abs_top_srcdir) \
+ B10_FROM_BUILD=$(abs_top_builddir) \
+ $(LIBRARY_PATH_PLACEHOLDER) \
+ PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_builddir)/src/bin/memmgr:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/util/io/.libs \
+ TESTDATASRCDIR=$(abs_srcdir)/testdata/ \
+ TESTDATA_PATH=$(abs_top_srcdir)/src/lib/testutils/testdata \
+ $(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
+ done
diff --git a/src/bin/memmgr/tests/memmgr_test.py b/src/bin/memmgr/tests/memmgr_test.py
new file mode 100755
index 0000000..3a5c175
--- /dev/null
+++ b/src/bin/memmgr/tests/memmgr_test.py
@@ -0,0 +1,220 @@
+# Copyright (C) 2013 Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+import unittest
+import os
+import re
+
+import isc.log
+from isc.dns import RRClass
+import isc.config
+from isc.config import parse_answer
+import memmgr
+from isc.testutils.ccsession_mock import MockModuleCCSession
+
+class MyCCSession(MockModuleCCSession, isc.config.ConfigData):
+ def __init__(self, specfile, config_handler, command_handler):
+ super().__init__()
+ specfile = os.environ['B10_FROM_BUILD'] + '/src/bin/memmgr/memmgr.spec'
+ module_spec = isc.config.module_spec_from_file(specfile)
+ isc.config.ConfigData.__init__(self, module_spec)
+ self.add_remote_params = [] # for inspection
+ self.add_remote_exception = None # to raise exception from the method
+
+ def start(self):
+ pass
+
+ def add_remote_config_by_name(self, mod_name, handler):
+ if self.add_remote_exception is not None:
+ raise self.add_remote_exception
+ self.add_remote_params.append((mod_name, handler))
+
+class MockMemmgr(memmgr.Memmgr):
+ def _setup_ccsession(self):
+ orig_cls = isc.config.ModuleCCSession
+ isc.config.ModuleCCSession = MyCCSession
+ try:
+ super()._setup_ccsession()
+ finally:
+ isc.config.ModuleCCSession = orig_cls
+
+# Defined for easier tests with DataSrcClientsMgr.reconfigure(), which
+# only needs get_value() method
+class MockConfigData:
+ def __init__(self, data):
+ self.__data = data
+
+ def get_value(self, identifier):
+ return self.__data[identifier], False
+
+class TestMemmgr(unittest.TestCase):
+ def setUp(self):
+ # Some tests use this directory. Make sure it doesn't pre-exist.
+ self.__test_mapped_file_dir = \
+ os.environ['B10_FROM_BUILD'] + \
+ '/src/bin/memmgr/tests/test_mapped_files'
+ if os.path.isdir(self.__test_mapped_file_dir):
+ os.rmdir(self.__test_mapped_file_dir)
+
+ self.__mgr = MockMemmgr()
+ # Fake some 'os' module functions for easier tests
+ self.__orig_os_access = os.access
+ self.__orig_isdir = os.path.isdir
+
+ def tearDown(self):
+ # Not all unittests cause this method to be called, so we call
+ # it explicitly as it may be necessary in some cases where the
+ # builder thread has been created.
+ self.__mgr._shutdown_module()
+
+ # Assert that all commands sent to the builder thread were
+ # handled.
+ self.assertEqual(len(self.__mgr._builder_command_queue), 0)
+
+ # Restore faked values
+ os.access = self.__orig_os_access
+ os.path.isdir = self.__orig_isdir
+
+ # If at test created a mapped-files directory, delete it.
+ if os.path.isdir(self.__test_mapped_file_dir):
+ os.rmdir(self.__test_mapped_file_dir)
+
+ def test_init(self):
+ """Check some initial conditions"""
+ self.assertIsNone(self.__mgr._config_params)
+ self.assertEqual([], self.__mgr._datasrc_info_list)
+
+ # Try to configure a data source clients with the manager. This
+ # should confirm the manager object is instantiated enabling in-memory
+ # cache.
+ cfg_data = MockConfigData(
+ {"classes": {"IN": [{"type": "MasterFiles",
+ "cache-enable": True, "params": {}}]}})
+ self.__mgr._datasrc_clients_mgr.reconfigure({}, cfg_data)
+ clist = \
+ self.__mgr._datasrc_clients_mgr.get_client_list(RRClass.IN)
+ self.assertEqual(1, len(clist.get_status()))
+
+ def test_configure(self):
+ self.__mgr._setup_ccsession()
+
+ # Pretend specified directories exist and writable
+ os.path.isdir = lambda x: True
+ os.access = lambda x, y: True
+
+ # At the initial configuration, if mapped_file_dir isn't specified,
+ # the default value will be set.
+ self.assertEqual((0, None),
+ parse_answer(self.__mgr._config_handler({})))
+ self.assertEqual('mapped_files',
+ self.__mgr._config_params['mapped_file_dir'].
+ split('/')[-1])
+
+ # Update the configuration.
+ user_cfg = {'mapped_file_dir': '/some/path/dir'}
+ self.assertEqual((0, None),
+ parse_answer(self.__mgr._config_handler(user_cfg)))
+ self.assertEqual('/some/path/dir',
+ self.__mgr._config_params['mapped_file_dir'])
+
+ # Bad update: diretory doesn't exist (we assume it really doesn't
+ # exist in the tested environment). Update won't be made.
+ os.path.isdir = self.__orig_isdir # use real library
+ user_cfg = {'mapped_file_dir': '/another/path/dir'}
+ answer = parse_answer(self.__mgr._config_handler(user_cfg))
+ self.assertEqual(1, answer[0])
+ self.assertIsNotNone(re.search('not a directory', answer[1]))
+
+ # Bad update: directory exists but is not readable.
+ os.mkdir(self.__test_mapped_file_dir, 0o500) # drop writable bit
+ os.access = self.__orig_os_access
+ user_cfg = {'mapped_file_dir': self.__test_mapped_file_dir}
+ answer = parse_answer(self.__mgr._config_handler(user_cfg))
+ self.assertEqual(1, answer[0])
+ self.assertIsNotNone(re.search('not writable', answer[1]))
+
+ def test_setup_module(self):
+ # _setup_module should add data_sources remote module with
+ # expected parameters.
+ self.__mgr._setup_ccsession()
+ self.assertEqual([], self.__mgr.mod_ccsession.add_remote_params)
+ self.__mgr._setup_module()
+ self.assertEqual([('data_sources',
+ self.__mgr._datasrc_config_handler)],
+ self.__mgr.mod_ccsession.add_remote_params)
+
+ # If data source isn't configured it's considered fatal (checking the
+ # same scenario with two possible exception types)
+ self.__mgr.mod_ccsession.add_remote_exception = \
+ isc.config.ModuleCCSessionError('faked exception')
+ self.assertRaises(isc.server_common.bind10_server.BIND10ServerFatal,
+ self.__mgr._setup_module)
+
+ self.__mgr.mod_ccsession.add_remote_exception = \
+ isc.config.ModuleSpecError('faked exception')
+ self.assertRaises(isc.server_common.bind10_server.BIND10ServerFatal,
+ self.__mgr._setup_module)
+
+ def test_datasrc_config_handler(self):
+ self.__mgr._config_params = {'mapped_file_dir': '/some/path'}
+
+ # A simple (boring) case with real class implementations. This
+ # confirms the methods are called as expected.
+ cfg_data = MockConfigData(
+ {"classes": {"IN": [{"type": "MasterFiles",
+ "cache-enable": True, "params": {}}]}})
+ self.__mgr._datasrc_config_handler({}, cfg_data)
+ self.assertEqual(1, len(self.__mgr._datasrc_info_list))
+ self.assertEqual(1, self.__mgr._datasrc_info_list[0].gen_id)
+
+ # Below we're using a mock DataSrcClientMgr for easier tests
+ class MockDataSrcClientMgr:
+ def __init__(self, status_list, raise_on_reconfig=False):
+ self.__status_list = status_list
+ self.__raise_on_reconfig = raise_on_reconfig
+
+ def reconfigure(self, new_config, config_data):
+ if self.__raise_on_reconfig:
+ raise isc.server_common.datasrc_clients_mgr.ConfigError(
+ 'test error')
+ # otherwise do nothing
+
+ def get_clients_map(self):
+ return 42, {RRClass.IN: self}
+
+ def get_status(self): # mocking get_clients_map()[1].get_status()
+ return self.__status_list
+
+ # This confirms memmgr's config is passed and handled correctly.
+ # From memmgr's point of view it should be enough we have an object
+ # in segment_info_map. Note also that the new DataSrcInfo is appended
+ # to the list
+ self.__mgr._datasrc_clients_mgr = \
+ MockDataSrcClientMgr([('sqlite3', 'mapped', None)])
+ self.__mgr._datasrc_config_handler(None, None) # params don't matter
+ self.assertEqual(2, len(self.__mgr._datasrc_info_list))
+ self.assertIsNotNone(
+ self.__mgr._datasrc_info_list[1].segment_info_map[
+ (RRClass.IN, 'sqlite3')])
+
+ # Emulate the case reconfigure() fails. Exception isn't propagated,
+ # but the list doesn't change.
+ self.__mgr._datasrc_clients_mgr = MockDataSrcClientMgr(None, True)
+ self.__mgr._datasrc_config_handler(None, None)
+ self.assertEqual(2, len(self.__mgr._datasrc_info_list))
+
+if __name__== "__main__":
+ isc.log.resetUnitTestRootLogger()
+ unittest.main()
diff --git a/src/bin/msgq/msgq.py.in b/src/bin/msgq/msgq.py.in
index efa3cbd..b6b1106 100755
--- a/src/bin/msgq/msgq.py.in
+++ b/src/bin/msgq/msgq.py.in
@@ -61,12 +61,14 @@ VERSION = "b10-msgq 20110127 (BIND 10 @PACKAGE_VERSION@)"
# If B10_FROM_BUILD is set in the environment, we use data files
# from a directory relative to that, otherwise we use the ones
# installed on the system
-if "B10_FROM_BUILD" in os.environ:
- SPECFILE_PATH = os.environ["B10_FROM_BUILD"] + "/src/bin/msgq"
+if "B10_FROM_SOURCE" in os.environ:
+ SPECFILE_PATH = os.environ["B10_FROM_SOURCE"] + "/src/bin/msgq"
else:
PREFIX = "@prefix@"
DATAROOTDIR = "@datarootdir@"
- SPECFILE_PATH = "@datadir@/@PACKAGE@".replace("${datarootdir}", DATAROOTDIR).replace("${prefix}", PREFIX)
+ SPECFILE_PATH = "@datadir@/@PACKAGE@".replace("${datarootdir}",
+ DATAROOTDIR). \
+ replace("${prefix}", PREFIX)
SPECFILE_LOCATION = SPECFILE_PATH + "/msgq.spec"
class MsgQReceiveError(Exception): pass
@@ -122,12 +124,17 @@ class SubscriptionManager:
if target in self.subscriptions:
if socket in self.subscriptions[target]:
self.subscriptions[target].remove(socket)
+ return True
+ return False
def unsubscribe_all(self, socket):
"""Remove the socket from all subscriptions."""
- for socklist in self.subscriptions.values():
+ removed_from = []
+ for subs, socklist in self.subscriptions.items():
if socket in socklist:
socklist.remove(socket)
+ removed_from.append(subs)
+ return removed_from
def find_sub(self, group, instance):
"""Return an array of sockets which want this specific group,
@@ -184,6 +191,7 @@ class MsgQ:
self.hostname = socket.gethostname()
self.subs = SubscriptionManager(self.cfgmgr_ready)
self.lnames = {}
+ self.fd_to_lname = {}
self.sendbuffs = {}
self.running = False
self.__cfgmgr_ready = None
@@ -195,6 +203,33 @@ class MsgQ:
# not for performance, so we use wide lock scopes to be on the safe
# side.
self.__lock = threading.Lock()
+ self._session = None
+
+ def members_notify(self, event, params):
+ """
+ Thin wrapper around ccs's notify. Send a notification about change
+ of some list that can be requested by the members command.
+
+ The event is one of:
+ - connected (client connected to MsgQ)
+ - disconected (client disconnected from MsgQ)
+ - subscribed (client subscribed to a group)
+ - unsubscribed (client unsubscribed from a group)
+
+ The params is dict containing:
+ - client: The lname of the client in question.
+ - group (for 'subscribed' and 'unsubscribed' events):
+ The group the client subscribed or unsubscribed from.
+
+ The notification occurs after the event, so client a subscribing for
+ notifications will get a notification about its own subscription, but
+ will not get a notification when it unsubscribes.
+ """
+ # Due to the interaction between threads (and fear it might influence
+ # sending stuff), we test this method in msgq_run_test, instead of
+ # mocking the ccs.
+ if self._session: # Don't send before we have started up
+ self._session.notify('cc_members', event, params)
def cfgmgr_ready(self, ready=True):
"""Notify that the config manager is either subscribed, or
@@ -323,11 +358,13 @@ class MsgQ:
def register_socket(self, newsocket):
"""
- Internal function to insert a socket. Used by process_accept and some tests.
+ Internal function to insert a socket. Used by process_accept and some
+ tests.
"""
self.sockets[newsocket.fileno()] = newsocket
lname = self.newlname()
self.lnames[lname] = newsocket
+ self.fd_to_lname[newsocket.fileno()] = lname
logger.debug(TRACE_BASIC, MSGQ_SOCKET_REGISTERED, newsocket.fileno(),
lname)
@@ -337,6 +374,8 @@ class MsgQ:
else:
self.add_kqueue_socket(newsocket)
+ self.members_notify('connected', {'client': lname})
+
def kill_socket(self, fd, sock):
"""Fully close down the socket."""
# Unregister events on the socket. Note that we don't have to do
@@ -345,14 +384,23 @@ class MsgQ:
if self.poller:
self.poller.unregister(sock)
- self.subs.unsubscribe_all(sock)
- lname = [ k for k, v in self.lnames.items() if v == sock ][0]
+ unsubscribed_from = self.subs.unsubscribe_all(sock)
+ lname = self.fd_to_lname[fd]
+ del self.fd_to_lname[fd]
del self.lnames[lname]
sock.close()
del self.sockets[fd]
if fd in self.sendbuffs:
del self.sendbuffs[fd]
logger.debug(TRACE_BASIC, MSGQ_SOCK_CLOSE, fd)
+ # Filter out just the groups.
+ unsubscribed_from_groups = set(map(lambda x: x[0], unsubscribed_from))
+ for group in unsubscribed_from_groups:
+ self.members_notify('unsubscribed', {
+ 'client': lname,
+ 'group': group
+ })
+ self.members_notify('disconnected', {'client': lname})
def __getbytes(self, fd, sock, length, continued):
"""Get exactly the requested bytes, or raise an exception if
@@ -567,7 +615,8 @@ class MsgQ:
This is done by using an increasing counter and the current
time."""
self.connection_counter += 1
- return "%x_%x@%s" % (time.time(), self.connection_counter, self.hostname)
+ return "%x_%x@%s" % (time.time(), self.connection_counter,
+ self.hostname)
def process_command_ping(self, sock, routing, data):
self.sendmsg(sock, { CC_HEADER_TYPE : CC_COMMAND_PONG }, data)
@@ -644,13 +693,25 @@ class MsgQ:
if group == None or instance == None:
return # ignore invalid packets entirely
self.subs.subscribe(group, instance, sock)
+ lname = self.fd_to_lname[sock.fileno()]
+ self.members_notify('subscribed',
+ {
+ 'client': lname,
+ 'group': group
+ })
def process_command_unsubscribe(self, sock, routing, data):
group = routing[CC_HEADER_GROUP]
instance = routing[CC_HEADER_INSTANCE]
if group == None or instance == None:
return # ignore invalid packets entirely
- self.subs.unsubscribe(group, instance, sock)
+ if self.subs.unsubscribe(group, instance, sock):
+ lname = self.fd_to_lname[sock.fileno()]
+ self.members_notify('unsubscribed',
+ {
+ 'client': lname,
+ 'group': group
+ })
def run(self):
"""Process messages. Forever. Mostly."""
@@ -795,16 +856,27 @@ class MsgQ:
return isc.config.create_answer(0)
def command_handler(self, command, args):
- """The command handler (run in a separate thread).
- Not tested, currently effectively empty.
- """
+ """The command handler (run in a separate thread)."""
config_logger.debug(TRACE_DETAIL, MSGQ_COMMAND, command, args)
with self.__lock:
if not self.running:
return
- # TODO: Any commands go here
+ # TODO: Who does validation? The ModuleCCSession or must we?
+
+ if command == 'members':
+ # List all members of MsgQ or of a group.
+ if args is None:
+ args = {}
+ group = args.get('group')
+ if group:
+ return isc.config.create_answer(0,
+ list(map(lambda sock: self.fd_to_lname[sock.fileno()],
+ self.subs.find(group, ''))))
+ else:
+ return isc.config.create_answer(0,
+ list(self.lnames.keys()))
config_logger.error(MSGQ_COMMAND_UNKNOWN, command)
return isc.config.create_answer(1, 'unknown command: ' + command)
@@ -819,7 +891,8 @@ if __name__ == "__main__":
a valid port number. Used by OptionParser() on startup."""
intval = int(value)
if (intval < 0) or (intval > 65535):
- raise OptionValueError("%s requires a port number (0-65535)" % opt_str)
+ raise OptionValueError("%s requires a port number (0-65535)" %
+ opt_str)
parser.values.msgq_port = intval
# Parse any command-line options.
@@ -861,13 +934,23 @@ if __name__ == "__main__":
msgq.command_handler,
None, True,
msgq.socket_file)
+ msgq._session = session
session.start()
# And we create a thread that'll just wait for commands and
# handle them. We don't terminate the thread, we set it to
# daemon. Once the main thread terminates, it'll just die.
def run_session():
while True:
- session.check_command(False)
+ # As the check_command has internal mutex that is shared
+ # with sending part (which includes notify). So we don't
+ # want to hold it long-term and block using select.
+ fileno = session.get_socket().fileno()
+ try:
+ (reads, _, _) = select.select([fileno], [], [])
+ except select.error as se:
+ if se.args[0] != errno.EINTR:
+ raise
+ session.check_command(True)
background_thread = threading.Thread(target=run_session)
background_thread.daemon = True
background_thread.start()
diff --git a/src/bin/msgq/msgq.spec b/src/bin/msgq/msgq.spec
index 93204fa..4b388c5 100644
--- a/src/bin/msgq/msgq.spec
+++ b/src/bin/msgq/msgq.spec
@@ -3,6 +3,18 @@
"module_name": "Msgq",
"module_description": "The message queue",
"config_data": [],
- "commands": []
+ "commands": [
+ {
+ "command_name": "members",
+ "command_description": "Provide the list of members of a group or of the whole MsgQ if no group is given.",
+ "command_args": [
+ {
+ "item_name": "group",
+ "item_optional": true,
+ "item_type": "string"
+ }
+ ]
+ }
+ ]
}
}
diff --git a/src/bin/msgq/tests/msgq_run_test.py b/src/bin/msgq/tests/msgq_run_test.py
index 95173e0..9cf6da6 100644
--- a/src/bin/msgq/tests/msgq_run_test.py
+++ b/src/bin/msgq/tests/msgq_run_test.py
@@ -272,6 +272,62 @@ class MsgqRunTest(unittest.TestCase):
conn.close()
conn = new
+ def test_notifications(self):
+ """
+ Check that the MsgQ is actually sending notifications about events.
+ We create a socket, subscribe the socket itself and see it receives
+ it's own notification.
+
+ Testing all the places where notifications happen is task for the
+ common unit tests in msgq_test.py.
+
+ The test is here, because there might be some trouble with multiple
+ threads in msgq (see the note about locking on the module CC session
+ when sending message from one thread and listening for commands in the
+ other) which would be hard to test using pure unit tests. Testing
+ runnig whole msgq tests that implicitly.
+ """
+ conn = self.__get_connection()
+ # Activate the session, pretend to be the config manager.
+ conn.group_subscribe('ConfigManager')
+ # Answer request for logging config
+ (msg, env) = conn.group_recvmsg(nonblock=False)
+ self.assertEqual({'command': ['get_config',
+ {'module_name': 'Logging'}]},
+ msg)
+ conn.group_reply(env, {'result': [0, {}]})
+ # It sends its spec.
+ (msg, env) = conn.group_recvmsg(nonblock=False)
+ self.assertEqual('module_spec', msg['command'][0])
+ conn.group_reply(env, {'result': [0]})
+ # It asks for its own config
+ (msg, env) = conn.group_recvmsg(nonblock=False)
+ self.assertEqual({'command': ['get_config',
+ {'module_name': 'Msgq'}]},
+ msg)
+ conn.group_reply(env, {'result': [0, {}]})
+ # Synchronization - make sure the session is running before
+ # we continue, so we get the notification. Similar synchronisation
+ # as in b10-init, but we don't have full ccsession here, so we
+ # do so manually.
+ synchronised = False
+ attempts = 100
+ while not synchronised and attempts > 0:
+ time.sleep(0.1)
+ seq = conn.group_sendmsg({'command': ['Are you running?']},
+ 'Msgq', want_answer=True)
+ msg = conn.group_recvmsg(nonblock=False, seq=seq)
+ synchronised = msg[0] != -1
+ attempts -= 1
+ self.assertTrue(synchronised)
+ # The actual test
+ conn.group_subscribe('notifications/cc_members')
+ (msg, env) = conn.group_recvmsg(nonblock=False)
+ self.assertEqual({'notification': ['subscribed', {
+ 'client': conn.lname,
+ 'group': 'notifications/cc_members'
+ }]}, msg)
+
if __name__ == '__main__':
isc.log.init("msgq-tests")
isc.log.resetUnitTestRootLogger()
diff --git a/src/bin/msgq/tests/msgq_test.py b/src/bin/msgq/tests/msgq_test.py
index e5a5656..3d0cbf9 100644
--- a/src/bin/msgq/tests/msgq_test.py
+++ b/src/bin/msgq/tests/msgq_test.py
@@ -19,6 +19,7 @@ from msgq import SubscriptionManager, MsgQ
import unittest
import os
import socket
+import select # needed only for #3014. can be removed once it's solved
import signal
import sys
import time
@@ -63,8 +64,11 @@ class TestSubscriptionManager(unittest.TestCase):
socks = [ 's1', 's2', 's3', 's4', 's5' ]
for s in socks:
self.sm.subscribe("a", "*", s)
- self.sm.unsubscribe("a", "*", 's3')
- self.assertEqual(self.sm.find_sub("a", "*"), [ 's1', 's2', 's4', 's5' ])
+ self.assertTrue(self.sm.unsubscribe("a", "*", 's3'))
+ # Unsubscribe from group it is not in
+ self.assertFalse(self.sm.unsubscribe("a", "*", 's42'))
+ self.assertEqual(self.sm.find_sub("a", "*"),
+ [ 's1', 's2', 's4', 's5' ])
def test_unsubscribe_all(self):
self.sm.subscribe('g1', 'i1', 's1')
@@ -75,7 +79,9 @@ class TestSubscriptionManager(unittest.TestCase):
self.sm.subscribe('g2', 'i1', 's2')
self.sm.subscribe('g2', 'i2', 's1')
self.sm.subscribe('g2', 'i2', 's2')
- self.sm.unsubscribe_all('s1')
+ self.assertEqual(set([('g1', 'i1'), ('g1', 'i2'), ('g2', 'i1'),
+ ('g2', 'i2')]),
+ set(self.sm.unsubscribe_all('s1')))
self.assertEqual(self.sm.find_sub("g1", "i1"), [ 's2' ])
self.assertEqual(self.sm.find_sub("g1", "i2"), [ 's2' ])
self.assertEqual(self.sm.find_sub("g2", "i1"), [ 's2' ])
@@ -178,6 +184,161 @@ class MsgQTest(unittest.TestCase):
data = json.loads(msg[6 + header_len:].decode('utf-8'))
return (header, data)
+ def test_unknown_command(self):
+ """
+ Test the command handler returns error when the command is unknown.
+ """
+ # Fake we are running, to disable test workarounds
+ self.__msgq.running = True
+ self.assertEqual({'result': [1, "unknown command: unknown"]},
+ self.__msgq.command_handler('unknown', {}))
+
+ def test_get_members(self):
+ """
+ Test getting members of a group or of all connected clients.
+ """
+ # Push two dummy "clients" into msgq (the ugly way, by directly
+ # tweaking relevant data structures).
+ class Sock:
+ def __init__(self, fileno):
+ self.fileno = lambda: fileno
+ self.__msgq.lnames['first'] = Sock(1)
+ self.__msgq.lnames['second'] = Sock(2)
+ self.__msgq.fd_to_lname[1] = 'first'
+ self.__msgq.fd_to_lname[2] = 'second'
+ # Subscribe them to some groups
+ self.__msgq.process_command_subscribe(self.__msgq.lnames['first'],
+ {'group': 'G1', 'instance': '*'},
+ None)
+ self.__msgq.process_command_subscribe(self.__msgq.lnames['second'],
+ {'group': 'G1', 'instance': '*'},
+ None)
+ self.__msgq.process_command_subscribe(self.__msgq.lnames['second'],
+ {'group': 'G2', 'instance': '*'},
+ None)
+ # Now query content of some groups through the command handler.
+ self.__msgq.running = True # Enable the command handler
+ def check_both(result):
+ """
+ Check the result is successful one and it contains both lnames (in
+ any order).
+ """
+ array = result['result'][1]
+ self.assertEqual(set(['first', 'second']), set(array))
+ self.assertEqual({'result': [0, array]}, result)
+ # Make sure the result can be encoded as JSON
+ # (there seems to be types that look like a list but JSON choks
+ # on them)
+ json.dumps(result)
+ # Members of the G1 and G2
+ self.assertEqual({'result': [0, ['second']]},
+ self.__msgq.command_handler('members',
+ {'group': 'G2'}))
+ check_both(self.__msgq.command_handler('members', {'group': 'G1'}))
+ # We pretend that all the possible groups exist, just that most
+ # of them are empty. So requesting for Empty is request for an empty
+ # group and should not fail.
+ self.assertEqual({'result': [0, []]},
+ self.__msgq.command_handler('members',
+ {'group': 'Empty'}))
+ # Without the name of the group, we just get all the clients.
+ check_both(self.__msgq.command_handler('members', {}))
+ # Omitting the parameters completely in such case is OK
+ check_both(self.__msgq.command_handler('members', None))
+
+ def notifications_setup(self):
+ """
+ Common setup of some notifications tests. Mock several things.
+ """
+ # Mock the method to send notifications (we don't really want
+ # to send them now, just see they'd be sent).
+ # Mock the poller, as we don't need it at all (and we don't have
+ # real socket to give it now).
+ notifications = []
+ def send_notification(event, params):
+ notifications.append((event, params))
+ class FakePoller:
+ def register(self, socket, mode):
+ pass
+ def unregister(self, sock):
+ pass
+ self.__msgq.members_notify = send_notification
+ self.__msgq.poller = FakePoller()
+
+ # Create a socket
+ class Sock:
+ def __init__(self, fileno):
+ self.fileno = lambda: fileno
+ def close(self):
+ pass
+ sock = Sock(1)
+ return notifications, sock
+
+ @unittest.skipUnless('POLLIN' in select.__dict__,
+ 'cannot perform tests requiring select.poll')
+ def test_notifies(self):
+ """
+ Test the message queue sends notifications about connecting,
+ disconnecting and subscription changes.
+ """
+ notifications, sock = self.notifications_setup()
+
+ # We should notify about new cliend when we register it
+ self.__msgq.register_socket(sock)
+ lname = self.__msgq.fd_to_lname[1] # Steal the lname
+ self.assertEqual([('connected', {'client': lname})], notifications)
+ del notifications[:]
+
+ # A notification should happen for a subscription to a group
+ self.__msgq.process_command_subscribe(sock, {'group': 'G',
+ 'instance': '*'},
+ None)
+ self.assertEqual([('subscribed', {'client': lname, 'group': 'G'})],
+ notifications)
+ del notifications[:]
+
+ # As well for unsubscription
+ self.__msgq.process_command_unsubscribe(sock, {'group': 'G',
+ 'instance': '*'},
+ None)
+ self.assertEqual([('unsubscribed', {'client': lname, 'group': 'G'})],
+ notifications)
+ del notifications[:]
+
+ # Unsubscription from a group it isn't subscribed to
+ self.__msgq.process_command_unsubscribe(sock, {'group': 'H',
+ 'instance': '*'},
+ None)
+ self.assertEqual([], notifications)
+
+ # And, finally, for removal of client
+ self.__msgq.kill_socket(sock.fileno(), sock)
+ self.assertEqual([('disconnected', {'client': lname})], notifications)
+
+ @unittest.skipUnless('POLLIN' in select.__dict__,
+ 'cannot perform tests requiring select.poll')
+ def test_notifies_implicit_kill(self):
+ """
+ Test that the unsubscription notifications are sent before the socket
+ is dropped, even in case it does not unsubscribe explicitly.
+ """
+ notifications, sock = self.notifications_setup()
+
+ # Register and subscribe. Notifications for these are in above test.
+ self.__msgq.register_socket(sock)
+ lname = self.__msgq.fd_to_lname[1] # Steal the lname
+ self.__msgq.process_command_subscribe(sock, {'group': 'G',
+ 'instance': '*'},
+ None)
+ del notifications[:]
+
+ self.__msgq.kill_socket(sock.fileno(), sock)
+ # Now, the notification for unsubscribe should be first, second for
+ # the disconnection.
+ self.assertEqual([('unsubscribed', {'client': lname, 'group': 'G'}),
+ ('disconnected', {'client': lname})
+ ], notifications)
+
def test_undeliverable_errors(self):
"""
Send several packets through the MsgQ and check it generates
@@ -412,12 +573,17 @@ class SendNonblock(unittest.TestCase):
The write end is put into the message queue, so we can check it.
It returns (msgq, read_end, write_end). It is expected the sockets
are closed by the caller afterwards.
+
+ Also check the sockets are registered correctly (eg. internal data
+ structures are there for them).
'''
msgq = MsgQ()
# We do only partial setup, so we don't create the listening socket
msgq.setup_poller()
(read, write) = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)
msgq.register_socket(write)
+ self.assertEqual(1, len(msgq.lnames))
+ self.assertEqual(write, msgq.lnames[msgq.fd_to_lname[write.fileno()]])
return (msgq, read, write)
def infinite_sender(self, sender):
@@ -437,8 +603,15 @@ class SendNonblock(unittest.TestCase):
# Explicitly close temporary socket pair as the Python
# interpreter expects it. It may not be 100% exception safe,
# but since this is only for tests we prefer brevity.
+ # Actually, the write end is often closed by the sender.
+ if write.fileno() != -1:
+ # Some of the senders passed here kill the socket internally.
+ # So kill it only if not yet done so. If the socket is closed,
+ # it gets -1 as fileno().
+ msgq.kill_socket(write.fileno(), write)
+ self.assertFalse(msgq.lnames)
+ self.assertFalse(msgq.fd_to_lname)
read.close()
- write.close()
def test_infinite_sendmsg(self):
"""
@@ -640,9 +813,11 @@ class SendNonblock(unittest.TestCase):
send_exception is raised by BadSocket.
"""
(write, read) = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)
- (control_write, control_read) = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)
+ (control_write, control_read) = socket.socketpair(socket.AF_UNIX,
+ socket.SOCK_STREAM)
badwrite = BadSocket(write, raise_on_send, send_exception)
- self.do_send(badwrite, read, control_write, control_read, expect_answer, expect_send_exception)
+ self.do_send(badwrite, read, control_write, control_read,
+ expect_answer, expect_send_exception)
write.close()
read.close()
control_write.close()
diff --git a/src/bin/resolver/resolver.cc b/src/bin/resolver/resolver.cc
index a3de340..41ed7af 100644
--- a/src/bin/resolver/resolver.cc
+++ b/src/bin/resolver/resolver.cc
@@ -524,7 +524,8 @@ ResolverImpl::processNormalQuery(const IOMessage& io_message,
{
const ConstQuestionPtr question = *query_message->beginQuestion();
const RRType qtype = question->getType();
- const RRClass qclass = question->getClass();
+ // Make cppcheck happy with the reference.
+ const RRClass& qclass = question->getClass();
// Apply query ACL
const Client client(io_message);
diff --git a/src/bin/zonemgr/zonemgr.py.in b/src/bin/zonemgr/zonemgr.py.in
index 879b3ef..5b60b18 100755
--- a/src/bin/zonemgr/zonemgr.py.in
+++ b/src/bin/zonemgr/zonemgr.py.in
@@ -777,6 +777,7 @@ class Zonemgr:
try:
while not self._shutdown_event.is_set():
fileno = self._module_cc.get_socket().fileno()
+ reads = []
# Wait with select() until there is something to read,
# and then read it using a non-blocking read
# This may or may not be relevant data for this loop,
diff --git a/src/lib/asiodns/io_fetch.cc b/src/lib/asiodns/io_fetch.cc
index eed5fdf..a09d8df 100644
--- a/src/lib/asiodns/io_fetch.cc
+++ b/src/lib/asiodns/io_fetch.cc
@@ -410,10 +410,9 @@ void IOFetch::logIOFailure(asio::error_code ec) {
(data_->origin == ASIODNS_READ_DATA) ||
(data_->origin == ASIODNS_UNKNOWN_ORIGIN));
- static const char* PROTOCOL[2] = {"TCP", "UDP"};
LOG_ERROR(logger, data_->origin).arg(ec.value()).
arg((data_->remote_snd->getProtocol() == IPPROTO_TCP) ?
- PROTOCOL[0] : PROTOCOL[1]).
+ "TCP" : "UDP").
arg(data_->remote_snd->getAddress().toText()).
arg(data_->remote_snd->getPort());
}
diff --git a/src/lib/cc/data.cc b/src/lib/cc/data.cc
index af3602a..9b0ed21 100644
--- a/src/lib/cc/data.cc
+++ b/src/lib/cc/data.cc
@@ -24,6 +24,8 @@
#include <iostream>
#include <string>
#include <sstream>
+#include <cerrno>
+#include <climits>
#include <boost/algorithm/string.hpp> // for iequals
@@ -398,19 +400,22 @@ fromStringstreamNumber(std::istream& in, int& pos) {
std::string number = numberFromStringstream(in, pos);
+ errno = 0;
i = strtol(number.c_str(), &endptr, 10);
if (*endptr != '\0') {
- d = strtod(number.c_str(), &endptr);
+ const char* ptr;
+ errno = 0;
+ d = strtod(ptr = number.c_str(), &endptr);
is_double = true;
- if (*endptr != '\0') {
+ if (*endptr != '\0' || ptr == endptr) {
isc_throw(JSONError, std::string("Bad number: ") + number);
} else {
- if (d == HUGE_VAL || d == -HUGE_VAL) {
+ if (errno != 0) {
isc_throw(JSONError, std::string("Number overflow: ") + number);
}
}
} else {
- if (i == LONG_MAX || i == LONG_MIN) {
+ if ((i == LONG_MAX || i == LONG_MIN) && errno != 0) {
isc_throw(JSONError, std::string("Number overflow: ") + number);
}
}
@@ -688,10 +693,9 @@ NullElement::toJSON(std::ostream& ss) const {
void
StringElement::toJSON(std::ostream& ss) const {
ss << "\"";
- char c;
const std::string& str = stringValue();
for (size_t i = 0; i < str.size(); ++i) {
- c = str[i];
+ const char c = str[i];
// Escape characters as defined in JSON spec
// Note that we do not escape forward slash; this
// is allowed, but not mandatory.
diff --git a/src/lib/cc/tests/data_unittests.cc b/src/lib/cc/tests/data_unittests.cc
index 9f015d2..409c951 100644
--- a/src/lib/cc/tests/data_unittests.cc
+++ b/src/lib/cc/tests/data_unittests.cc
@@ -15,6 +15,7 @@
#include <gtest/gtest.h>
#include <boost/foreach.hpp>
#include <boost/assign/std/vector.hpp>
+#include <climits>
#include <cc/data.h>
@@ -146,6 +147,22 @@ TEST(Element, from_and_to_json) {
EXPECT_EQ("100", Element::fromJSON("1e2")->str());
EXPECT_EQ("100", Element::fromJSON("+1e2")->str());
EXPECT_EQ("-100", Element::fromJSON("-1e2")->str());
+
+ // LONG_MAX, -LONG_MAX, LONG_MIN test
+ std::ostringstream longmax, minus_longmax, longmin;
+ longmax << LONG_MAX;
+ minus_longmax << -LONG_MAX;
+ longmin << LONG_MIN;
+ EXPECT_NO_THROW( {
+ EXPECT_EQ(longmax.str(), Element::fromJSON(longmax.str())->str());
+ });
+ EXPECT_NO_THROW( {
+ EXPECT_EQ(minus_longmax.str(), Element::fromJSON(minus_longmax.str())->str());
+ });
+ EXPECT_NO_THROW( {
+ EXPECT_EQ(longmin.str(), Element::fromJSON(longmin.str())->str());
+ });
+
EXPECT_EQ("0.01", Element::fromJSON("1e-2")->str());
EXPECT_EQ("0.01", Element::fromJSON(".01")->str());
EXPECT_EQ("-0.01", Element::fromJSON("-1e-2")->str());
@@ -173,6 +190,8 @@ TEST(Element, from_and_to_json) {
EXPECT_THROW(Element::fromJSON("-1.1e12345678901234567890")->str(), JSONError);
EXPECT_THROW(Element::fromJSON("1e12345678901234567890")->str(), JSONError);
EXPECT_THROW(Element::fromJSON("1e50000")->str(), JSONError);
+ // number underflow
+ EXPECT_THROW(Element::fromJSON("1.1e-12345678901234567890")->str(), JSONError);
}
diff --git a/src/lib/config/config_data.h b/src/lib/config/config_data.h
index e40600d..bcc97de 100644
--- a/src/lib/config/config_data.h
+++ b/src/lib/config/config_data.h
@@ -29,7 +29,7 @@ namespace config {
/// point to anything defined in the .spec file)
class DataNotFoundError : public isc::Exception {
public:
- DataNotFoundError(const char* file, size_t line, const std::string& what) :
+ DataNotFoundError(const char* file, size_t line, const char* what) :
isc::Exception(file, line, what) {}
};
diff --git a/src/lib/datasrc/client_list.cc b/src/lib/datasrc/client_list.cc
index 01f59f4..4fe1eb3 100644
--- a/src/lib/datasrc/client_list.cc
+++ b/src/lib/datasrc/client_list.cc
@@ -110,12 +110,20 @@ ConfigurableClientList::configure(const ConstElementPtr& config,
<< datasrc_name);
}
- // Create a client for the underling data source via factory.
- // If it's our internal type of data source, this is essentially
- // no-op. In the latter case, it's of no use unless cache is
- // allowed; we simply skip building it in that case.
- const DataSourcePair dsrc_pair = getDataSourceClient(type,
- param_conf);
+ DataSourcePair dsrc_pair;
+ try {
+ // Create a client for the underling data source via
+ // factory. If it's our internal type of data source,
+ // this is essentially no-op. In the latter case, it's
+ // of no use unless cache is allowed; we simply skip
+ // building it in that case.
+ dsrc_pair = getDataSourceClient(type, param_conf);
+ } catch (const DataSourceLibraryError& ex) {
+ LOG_ERROR(logger, DATASRC_LIBRARY_ERROR).
+ arg(datasrc_name).arg(rrclass_).arg(ex.what());
+ continue;
+ }
+
if (!allow_cache && !dsrc_pair.first) {
LOG_WARN(logger, DATASRC_LIST_NOT_CACHED).
arg(datasrc_name).arg(rrclass_);
@@ -339,6 +347,7 @@ ConfigurableClientList::resetMemorySegment
ConfigurableClientList::ZoneWriterPair
ConfigurableClientList::getCachedZoneWriter(const Name& name,
+ bool catch_load_error,
const std::string& datasrc_name)
{
if (!allow_cache_) {
@@ -377,7 +386,8 @@ ConfigurableClientList::getCachedZoneWriter(const Name& name,
ZoneWriterPtr(
new memory::ZoneWriter(
*info.ztable_segment_,
- load_action, name, rrclass_, false))));
+ load_action, name, rrclass_,
+ catch_load_error))));
}
// We can't find the specified zone. If a specific data source was
@@ -410,11 +420,15 @@ vector<DataSourceStatus>
ConfigurableClientList::getStatus() const {
vector<DataSourceStatus> result;
BOOST_FOREACH(const DataSourceInfo& info, data_sources_) {
- // TODO: Once we support mapped cache, decide when we need the
- // SEGMENT_WAITING.
- result.push_back(DataSourceStatus(info.name_, info.cache_ ?
- SEGMENT_INUSE : SEGMENT_UNUSED,
- "local"));
+ if (info.ztable_segment_) {
+ result.push_back(DataSourceStatus(
+ info.name_,
+ (info.ztable_segment_->isUsable() ?
+ SEGMENT_INUSE : SEGMENT_WAITING),
+ info.ztable_segment_->getImplType()));
+ } else {
+ result.push_back(DataSourceStatus(info.name_));
+ }
}
return (result);
}
diff --git a/src/lib/datasrc/client_list.h b/src/lib/datasrc/client_list.h
index 318cf5c..e4df415 100644
--- a/src/lib/datasrc/client_list.h
+++ b/src/lib/datasrc/client_list.h
@@ -81,13 +81,26 @@ class DataSourceStatus {
public:
/// \brief Constructor
///
- /// Sets initial values. It doesn't matter what is provided for the type
- /// if state is SEGMENT_UNUSED, the value is effectively ignored.
+ /// Sets initial values. If you want to use \c SEGMENT_UNUSED as the
+ /// state, please use the other constructor.
DataSourceStatus(const std::string& name, MemorySegmentState state,
const std::string& type) :
name_(name),
type_(type),
state_(state)
+ {
+ assert (state != SEGMENT_UNUSED);
+ assert (!type.empty());
+ }
+
+ /// \brief Constructor
+ ///
+ /// Sets initial values. The state is set as \c SEGMENT_UNUSED and
+ /// the type is effectively unspecified.
+ DataSourceStatus(const std::string& name) :
+ name_(name),
+ type_(""),
+ state_(SEGMENT_UNUSED)
{}
/// \brief Get the segment state
@@ -377,10 +390,9 @@ public:
memory::ZoneTableSegment::MemorySegmentOpenMode mode,
isc::data::ConstElementPtr config_params);
-private:
/// \brief Convenience type shortcut
typedef boost::shared_ptr<memory::ZoneWriter> ZoneWriterPtr;
-public:
+
/// \brief Codes indicating in-memory cache status for a given zone name.
///
/// This is used as a result of the getCachedZoneWriter() method.
@@ -420,9 +432,11 @@ public:
/// of the pair.
///
/// \param zone The origin of the zone to load.
+ /// \param catch_load_errors Whether to make the zone writer catch
+ /// load errors (see \c ZoneWriter constructor documentation).
/// \param datasrc_name If not empty, the name of the data source
/// to be used for loading the zone (see above).
- /// \return The result has two parts. The first one is a status describing
+ /// \return The result has two parts. The first one is a status indicating
/// if it worked or not (and in case it didn't, also why). If the
/// status is ZONE_SUCCESS, the second part contains a shared pointer
/// to the writer. If the status is anything else, the second part is
@@ -430,6 +444,7 @@ public:
/// \throw DataSourceError or anything else that the data source
/// containing the zone might throw is propagated.
ZoneWriterPair getCachedZoneWriter(const dns::Name& zone,
+ bool catch_load_error,
const std::string& datasrc_name = "");
/// \brief Implementation of the ClientList::find.
diff --git a/src/lib/datasrc/datasrc_messages.mes b/src/lib/datasrc/datasrc_messages.mes
index 6667349..f9a76ed 100644
--- a/src/lib/datasrc/datasrc_messages.mes
+++ b/src/lib/datasrc/datasrc_messages.mes
@@ -370,6 +370,11 @@ Therefore, the entire data source will not be available for this process. If
this is a problem, you should configure the zones of that data source to some
database backend (sqlite3, for example) and use it from there.
+% DATASRC_LIBRARY_ERROR failure loading %1 datasource library for class %2: %3
+There was a problem loading the dynamic library for a data source. This
+backend is hence not available, and any data sources that use this
+backend will not be available.
+
% DATASRC_LOAD_ZONE_ERROR Error loading zone %1/%2 on data source '%3': %4
During data source configuration, an error was found in the zone data
when it was being loaded in to memory on the shown data source. This
diff --git a/src/lib/datasrc/factory.cc b/src/lib/datasrc/factory.cc
index 73f7e4a..26b31dd 100644
--- a/src/lib/datasrc/factory.cc
+++ b/src/lib/datasrc/factory.cc
@@ -83,8 +83,8 @@ LibraryContainer::LibraryContainer(const std::string& name) {
if (ds_lib_ == NULL) {
// This may cause the filename to appear twice in the actual
// error, but the output of dlerror is implementation-dependent
- isc_throw(DataSourceLibraryError, "dlopen failed for " << name <<
- ": " << dlerror());
+ isc_throw(DataSourceLibraryOpenError,
+ "dlopen failed for " << name << ": " << dlerror());
}
}
diff --git a/src/lib/datasrc/factory.h b/src/lib/datasrc/factory.h
index 4e669ec..b9b6578 100644
--- a/src/lib/datasrc/factory.h
+++ b/src/lib/datasrc/factory.h
@@ -27,7 +27,7 @@ namespace isc {
namespace datasrc {
-/// \brief Raised if there is an error loading the datasource implementation
+/// \brief Raised if there is an error in the datasource implementation
/// library
class DataSourceLibraryError : public DataSourceError {
public:
@@ -35,13 +35,22 @@ public:
DataSourceError(file, line, what) {}
};
+/// \brief Raised if there is an error opening the the datasource
+/// implementation library
+class DataSourceLibraryOpenError : public DataSourceLibraryError {
+public:
+ DataSourceLibraryOpenError(const char* file, size_t line,
+ const char* what) :
+ DataSourceLibraryError(file, line, what) {}
+};
+
/// \brief Raised if there is an error reading a symbol from the datasource
/// implementation library
-class DataSourceLibrarySymbolError : public DataSourceError {
+class DataSourceLibrarySymbolError : public DataSourceLibraryError {
public:
DataSourceLibrarySymbolError(const char* file, size_t line,
const char* what) :
- DataSourceError(file, line, what) {}
+ DataSourceLibraryError(file, line, what) {}
};
typedef DataSourceClient* ds_creator(isc::data::ConstElementPtr config,
diff --git a/src/lib/datasrc/memory/domaintree.h b/src/lib/datasrc/memory/domaintree.h
index 4c6199a..36e1bc9 100644
--- a/src/lib/datasrc/memory/domaintree.h
+++ b/src/lib/datasrc/memory/domaintree.h
@@ -705,7 +705,13 @@ public:
// XXX: meaningless initial values:
last_comparison_(0, 0,
isc::dns::NameComparisonResult::EQUAL)
- {}
+ {
+ // To silence cppcheck. We don't really use the values before
+ // initialization, but this is cleaner anyway.
+ for (size_t i = 0; i < RBT_MAX_LEVEL; ++i) {
+ nodes_[i] = NULL;
+ }
+ }
/// \brief Copy constructor.
///
@@ -828,6 +834,7 @@ private:
/// the top node
///
/// \exception None
+ // cppcheck-suppress unusedPrivateFunction (false positive, it is used)
void pop() {
assert(!isEmpty());
--level_count_;
@@ -840,6 +847,7 @@ private:
/// otherwise the node should be the root node of DomainTree.
///
/// \exception None
+ // cppcheck-suppress unusedPrivateFunction (false positive, it is used)
void push(const DomainTreeNode<T>* node) {
assert(level_count_ < RBT_MAX_LEVEL);
nodes_[level_count_++] = node;
diff --git a/src/lib/datasrc/memory/zone_table.cc b/src/lib/datasrc/memory/zone_table.cc
index 32938a6..454a0aa 100644
--- a/src/lib/datasrc/memory/zone_table.cc
+++ b/src/lib/datasrc/memory/zone_table.cc
@@ -83,7 +83,7 @@ ZoneTable::destroy(util::MemorySegment& mem_sgmt, ZoneTable* ztable, int)
}
ZoneTable::AddResult
-ZoneTable::addZone(util::MemorySegment& mem_sgmt, RRClass zone_class,
+ZoneTable::addZone(util::MemorySegment& mem_sgmt,
const Name& zone_name, ZoneData* content)
{
LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_MEMORY_MEM_ADD_ZONE).
@@ -94,13 +94,8 @@ ZoneTable::addZone(util::MemorySegment& mem_sgmt, RRClass zone_class,
(content ? "empty data" : "NULL") <<
" is passed to Zone::addZone");
}
- SegmentObjectHolder<ZoneData, RRClass> holder(mem_sgmt, zone_class);
- holder.set(content);
- const AddResult result =
- addZoneInternal(mem_sgmt, zone_name, holder.get());
- holder.release();
- return (result);
+ return (addZoneInternal(mem_sgmt, zone_name, content));
}
ZoneTable::AddResult
diff --git a/src/lib/datasrc/memory/zone_table.h b/src/lib/datasrc/memory/zone_table.h
index 81f1a3c..6bad516 100644
--- a/src/lib/datasrc/memory/zone_table.h
+++ b/src/lib/datasrc/memory/zone_table.h
@@ -165,12 +165,22 @@ public:
///
/// This method adds a given zone data to the internal table.
///
- /// This method ensures there'll be no memory leak on exception.
- /// But addresses allocated from \c mem_sgmt could be relocated if
- /// \c util::MemorySegmentGrown is thrown; the caller or its upper layer
- /// must be aware of that possibility and update any such addresses
- /// accordingly. On successful return, this method ensures there's no
- /// address relocation.
+ /// On successful completion (i.e., the method returns without an
+ /// exception), the ownership of \c content will be transferred to
+ /// the \c ZoneTable: the caller should not use the \c content hereafter;
+ /// the \c ZoneTable will be responsible to destroy it when the table
+ /// itself is destroyed.
+ ///
+ /// If this method throws, the caller is responsible to take care of
+ /// the passed \c content, whether to destroy it or use for different
+ /// purposes. Note that addresses allocated from \c mem_sgmt could be
+ /// relocated if \c util::MemorySegmentGrown is thrown; the caller or its
+ /// upper layer must be aware of that possibility and update any such
+ /// addresses accordingly. This applies to \c content, as it's expected
+ /// to be created using \c mem_sgmt.
+ ///
+ /// On successful return, this method ensures there's no address
+ /// relocation.
///
/// \throw InvalidParameter content is NULL or empty.
/// \throw util::MemorySegmentGrown The memory segment has grown, possibly
@@ -181,8 +191,6 @@ public:
/// created. It must be the same segment that was used to create
/// the zone table at the time of create().
/// \param zone_name The name of the zone to be added.
- /// \param zone_class The RR class of the zone. It must be the RR class
- /// that is supposed to be associated to the zone table.
/// \param content This one should hold the zone content (the ZoneData).
/// The ownership is passed onto the zone table. Must not be null or
/// empty. Must correspond to the name and class and must be allocated
@@ -194,7 +202,6 @@ public:
/// inside the result unless it's empty; if the zone was previously
/// added by \c addEmptyZone(), the data returned is NULL.
AddResult addZone(util::MemorySegment& mem_sgmt,
- dns::RRClass zone_class,
const dns::Name& zone_name,
ZoneData* content);
diff --git a/src/lib/datasrc/memory/zone_writer.cc b/src/lib/datasrc/memory/zone_writer.cc
index ebe6151..ea70bb9 100644
--- a/src/lib/datasrc/memory/zone_writer.cc
+++ b/src/lib/datasrc/memory/zone_writer.cc
@@ -134,12 +134,10 @@ ZoneWriter::install() {
// zone data or we've allowed load error to create an empty zone.
assert(impl_->data_holder_.get() || impl_->catch_load_error_);
- // FIXME: This retry is currently untested, as there seems to be no
- // reasonable way to create a zone table segment with non-local memory
- // segment. Once there is, we should provide the test.
while (impl_->state_ != Impl::ZW_INSTALLED) {
try {
- ZoneTable* table(impl_->segment_.getHeader().getTable());
+ ZoneTableHeader& header = impl_->segment_.getHeader();
+ ZoneTable* table(header.getTable());
if (!table) {
isc_throw(isc::Unexpected, "No zone table present");
}
@@ -151,8 +149,7 @@ ZoneWriter::install() {
const ZoneTable::AddResult result(
impl_->data_holder_->get() ?
table->addZone(impl_->segment_.getMemorySegment(),
- impl_->rrclass_, impl_->origin_,
- impl_->data_holder_->get()) :
+ impl_->origin_, impl_->data_holder_->get()) :
table->addEmptyZone(impl_->segment_.getMemorySegment(),
impl_->origin_));
impl_->data_holder_->set(result.zone_data);
diff --git a/src/lib/datasrc/memory/zone_writer.h b/src/lib/datasrc/memory/zone_writer.h
index 12a75c6..bdd350c 100644
--- a/src/lib/datasrc/memory/zone_writer.h
+++ b/src/lib/datasrc/memory/zone_writer.h
@@ -91,7 +91,7 @@ public:
/// later.
/// \throw isc::InvalidOperation if called second time.
/// \throw DataSourceError load related error (not thrown if constructed
- /// with catch_load_error being false).
+ /// with catch_load_error being \c true).
///
/// \param error_msg If non NULL, used as a placeholder to store load error
/// messages.
diff --git a/src/lib/datasrc/tests/client_list_unittest.cc b/src/lib/datasrc/tests/client_list_unittest.cc
index a686aee..eb69556 100644
--- a/src/lib/datasrc/tests/client_list_unittest.cc
+++ b/src/lib/datasrc/tests/client_list_unittest.cc
@@ -16,6 +16,7 @@
#include <datasrc/client_list.h>
#include <datasrc/client.h>
+#include <datasrc/factory.h>
#include <datasrc/cache_config.h>
#include <datasrc/zone_iterator.h>
#include <datasrc/exceptions.h>
@@ -71,6 +72,10 @@ public:
if (type == "error") {
isc_throw(DataSourceError, "The error data source type");
}
+ if (type == "library_error") {
+ isc_throw(DataSourceLibraryError,
+ "The library error data source type");
+ }
if (type == "MasterFiles") {
return (DataSourcePair(0, DataSourceClientContainerPtr()));
}
@@ -116,6 +121,7 @@ public:
const std::string& datasrc_name,
ZoneTableSegment::MemorySegmentOpenMode mode,
ConstElementPtr config_params) = 0;
+ virtual std::string getType() = 0;
};
class ListTest : public ::testing::TestWithParam<SegmentType*> {
@@ -124,6 +130,7 @@ public:
rrclass_(RRClass::IN()),
// The empty list corresponds to a list with no elements inside
list_(new TestedList(rrclass_)),
+ negative_result_(),
config_elem_(Element::fromJSON("["
"{"
" \"type\": \"test_type\","
@@ -202,14 +209,14 @@ public:
memory::ZoneTableSegment::CREATE,
config_ztable_segment);
- boost::scoped_ptr<memory::ZoneWriter> writer(
- new memory::ZoneWriter(
- *dsrc_info.ztable_segment_,
- cache_conf->getLoadAction(rrclass_, zone),
- zone, rrclass_, false));
- writer->load();
- writer->install();
- writer->cleanup(); // not absolutely necessary, but just in case
+ const ConfigurableClientList::ZoneWriterPair result =
+ list_->getCachedZoneWriter(zone, false, dsrc_info.name_);
+
+ ASSERT_EQ(ConfigurableClientList::ZONE_SUCCESS, result.first);
+ result.second->load();
+ result.second->install();
+ // not absolutely necessary, but just in case
+ result.second->cleanup();
GetParam()->reset(*list_, dsrc_info.name_,
memory::ZoneTableSegment::READ_WRITE,
@@ -332,6 +339,9 @@ public:
ConstElementPtr) {
// We must not call reset on local ZoneTableSegments.
}
+ virtual std::string getType() {
+ return ("local");
+ }
};
LocalSegmentType local_segment_type;
@@ -360,6 +370,9 @@ public:
ConstElementPtr config_params) {
list.resetMemorySegment(datasrc_name, mode, config_params);
}
+ virtual std::string getType() {
+ return ("mapped");
+ }
};
MappedSegmentType mapped_segment_type;
@@ -697,6 +710,35 @@ TEST_P(ListTest, dataSrcError) {
checkDS(0, "test_type", "{}", false);
}
+// In case of library errors, the rest of the data sources should be
+// unaffected.
+TEST_P(ListTest, dataSrcLibraryError) {
+ EXPECT_EQ(0, list_->getDataSources().size());
+ const ConstElementPtr elem(Element::fromJSON("["
+ "{"
+ " \"type\": \"type1\","
+ " \"cache-enable\": false,"
+ " \"params\": {}"
+ "},"
+ "{"
+ " \"type\": \"library_error\","
+ " \"cache-enable\": false,"
+ " \"params\": {}"
+ "},"
+ "{"
+ " \"type\": \"type2\","
+ " \"cache-enable\": false,"
+ " \"params\": {}"
+ "}]"
+ ));
+ list_->configure(elem, true);
+ EXPECT_EQ(2, list_->getDataSources().size());
+ checkDS(0, "type1", "{}", false);
+ checkDS(1, "type2", "{}", false);
+ // Check the exact configuration is preserved
+ EXPECT_EQ(elem, list_->getConfiguration());
+}
+
// Check we can get the cache
TEST_P(ListTest, configureCacheEmpty) {
const ConstElementPtr elem(Element::fromJSON("["
@@ -981,7 +1023,7 @@ TEST_P(ListTest, BadMasterFile) {
ConfigurableClientList::CacheStatus
ListTest::doReload(const Name& origin, const string& datasrc_name) {
ConfigurableClientList::ZoneWriterPair
- result(list_->getCachedZoneWriter(origin, datasrc_name));
+ result(list_->getCachedZoneWriter(origin, false, datasrc_name));
if (result.first == ConfigurableClientList::ZONE_SUCCESS) {
// Can't use ASSERT_NE here, it would want to return(), which
// it can't in non-void function.
@@ -999,9 +1041,69 @@ ListTest::doReload(const Name& origin, const string& datasrc_name) {
return (result.first);
}
+// Check that ZoneWriter doesn't throw when asked not to
+TEST_P(ListTest, checkZoneWriterCatchesExceptions) {
+ const ConstElementPtr config_elem_zones_(Element::fromJSON("["
+ "{"
+ " \"type\": \"MasterFiles\","
+ " \"params\": {"
+ " \"example.edu\": \"" TEST_DATA_DIR "example.edu-broken\""
+ " },"
+ " \"cache-enable\": true"
+ "}]"));
+
+ list_->configure(config_elem_zones_, true);
+ ConfigurableClientList::ZoneWriterPair
+ result(list_->getCachedZoneWriter(Name("example.edu"), true));
+ ASSERT_EQ(ConfigurableClientList::ZONE_SUCCESS, result.first);
+ ASSERT_TRUE(result.second);
+
+ std::string error_msg;
+ // Because of the way we called getCachedZoneWriter() with
+ // catch_load_error=true, the following should not throw and must
+ // return an error message in error_msg.
+ EXPECT_NO_THROW(result.second->load(&error_msg));
+ EXPECT_FALSE(error_msg.empty());
+ result.second->cleanup();
+}
+
+// Check that ZoneWriter throws when asked to
+TEST_P(ListTest, checkZoneWriterThrows) {
+ const ConstElementPtr config_elem_zones_(Element::fromJSON("["
+ "{"
+ " \"type\": \"MasterFiles\","
+ " \"params\": {"
+ " \"example.edu\": \"" TEST_DATA_DIR "example.edu-broken\""
+ " },"
+ " \"cache-enable\": true"
+ "}]"));
+
+ list_->configure(config_elem_zones_, true);
+ ConfigurableClientList::ZoneWriterPair
+ result(list_->getCachedZoneWriter(Name("example.edu"), false));
+ ASSERT_EQ(ConfigurableClientList::ZONE_SUCCESS, result.first);
+ ASSERT_TRUE(result.second);
+
+ std::string error_msg;
+ // Because of the way we called getCachedZoneWriter() with
+ // catch_load_error=false, the following should throw and must not
+ // modify error_msg.
+ EXPECT_THROW(result.second->load(&error_msg),
+ isc::datasrc::ZoneLoaderException);
+ EXPECT_TRUE(error_msg.empty());
+ result.second->cleanup();
+}
+
// Test we can reload a zone
TEST_P(ListTest, reloadSuccess) {
list_->configure(config_elem_zones_, true);
+
+ const vector<DataSourceStatus> statii_before(list_->getStatus());
+ ASSERT_EQ(1, statii_before.size());
+ EXPECT_EQ("test_type", statii_before[0].getName());
+ EXPECT_EQ(SEGMENT_UNUSED, statii_before[0].getSegmentState());
+ EXPECT_THROW(statii_before[0].getSegmentType(), isc::InvalidOperation);
+
const Name name("example.org");
prepareCache(0, name);
// The cache currently contains a tweaked version of zone, which
@@ -1017,10 +1119,19 @@ TEST_P(ListTest, reloadSuccess) {
list_->find(name).finder_->
find(Name("tstzonedata").concatenate(name),
RRType::A())->code);
+
+ const vector<DataSourceStatus> statii_after(list_->getStatus());
+ ASSERT_EQ(1, statii_after.size());
+ EXPECT_EQ("test_type", statii_after[0].getName());
+ EXPECT_EQ(SEGMENT_INUSE, statii_after[0].getSegmentState());
+ EXPECT_EQ(GetParam()->getType(), statii_after[0].getSegmentType());
}
// The cache is not enabled. The load should be rejected.
-TEST_P(ListTest, reloadNotAllowed) {
+//
+// FIXME: This test is broken by #2853 and needs to be fixed or
+// removed. Please see #2991 for details.
+TEST_P(ListTest, DISABLED_reloadNotAllowed) {
list_->configure(config_elem_zones_, false);
const Name name("example.org");
// We put the cache in even when not enabled. This won't confuse the thing.
@@ -1257,9 +1368,9 @@ ListTest::accessorIterate(const ConstZoneTableAccessorPtr& accessor,
bool found = false;
int i;
for (i = 0; !it->isLast(); ++i, it->next()) {
- if (Name(zoneName) == it->getCurrent().origin) {
- found = true;
- }
+ if (Name(zoneName) == it->getCurrent().origin) {
+ found = true;
+ }
}
EXPECT_EQ(i, numZones);
if (numZones > 0) {
@@ -1334,7 +1445,7 @@ TEST(DataSourceStatus, status) {
EXPECT_EQ("Test", status.getName());
EXPECT_EQ(SEGMENT_INUSE, status.getSegmentState());
EXPECT_EQ("local", status.getSegmentType());
- const DataSourceStatus status_unused("Unused", SEGMENT_UNUSED, "");
+ const DataSourceStatus status_unused("Unused");
EXPECT_EQ("Unused", status_unused.getName());
EXPECT_EQ(SEGMENT_UNUSED, status_unused.getSegmentState());
EXPECT_THROW(status_unused.getSegmentType(), isc::InvalidOperation);
diff --git a/src/lib/datasrc/tests/database_unittest.cc b/src/lib/datasrc/tests/database_unittest.cc
index 79cc2c3..e6a27b4 100644
--- a/src/lib/datasrc/tests/database_unittest.cc
+++ b/src/lib/datasrc/tests/database_unittest.cc
@@ -1137,6 +1137,9 @@ const char* TEST_NSEC3_RECORDS[][5] = {
};
DatabaseClientTest::DatabaseClientTest() :
+ // We need to initialize to something, and not being mock is safer
+ // until we know for sure.
+ is_mock_(false),
zname_("example.org"), qname_("www.example.org"),
qclass_(dns::RRClass::IN()),
qtype_(dns::RRType::A()),
diff --git a/src/lib/datasrc/tests/factory_unittest.cc b/src/lib/datasrc/tests/factory_unittest.cc
index 5a01a27..2708e0e 100644
--- a/src/lib/datasrc/tests/factory_unittest.cc
+++ b/src/lib/datasrc/tests/factory_unittest.cc
@@ -39,6 +39,8 @@ void
pathtestHelper(const std::string& file, const std::string& expected_error) {
std::string error;
try {
+ // cppcheck-suppress unusedScopedObject We just check if it throws
+ // to create, not use it. That's OK.
DataSourceClientContainer(file, ElementPtr());
} catch (const DataSourceLibraryError& dsle) {
error = dsle.what();
diff --git a/src/lib/datasrc/tests/memory/testdata/Makefile.am b/src/lib/datasrc/tests/memory/testdata/Makefile.am
index b076837..da27482 100644
--- a/src/lib/datasrc/tests/memory/testdata/Makefile.am
+++ b/src/lib/datasrc/tests/memory/testdata/Makefile.am
@@ -29,6 +29,7 @@ EXTRA_DIST += example.org-rrsigs.zone
EXTRA_DIST += example.org-wildcard-dname.zone
EXTRA_DIST += example.org-wildcard-ns.zone
EXTRA_DIST += example.org-wildcard-nsec3.zone
+EXTRA_DIST += template.zone
EXTRA_DIST += rrset-collection.zone
EXTRA_DIST += 2503-test.zone
diff --git a/src/lib/datasrc/tests/memory/testdata/template.zone b/src/lib/datasrc/tests/memory/testdata/template.zone
new file mode 100644
index 0000000..d8ae6d8
--- /dev/null
+++ b/src/lib/datasrc/tests/memory/testdata/template.zone
@@ -0,0 +1,4 @@
+; a zone file that can be used with any origin.
+
+@ 3600 IN SOA . . 1 0 0 0 0
+@ 3600 IN NS ns1
diff --git a/src/lib/datasrc/tests/memory/zone_data_loader_unittest.cc b/src/lib/datasrc/tests/memory/zone_data_loader_unittest.cc
index ad20d43..0e5677c 100644
--- a/src/lib/datasrc/tests/memory/zone_data_loader_unittest.cc
+++ b/src/lib/datasrc/tests/memory/zone_data_loader_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 <config.h>
+
#include <datasrc/memory/zone_data_loader.h>
#include <datasrc/memory/rdataset.h>
#include <datasrc/memory/zone_data.h>
diff --git a/src/lib/datasrc/tests/memory/zone_table_unittest.cc b/src/lib/datasrc/tests/memory/zone_table_unittest.cc
index 328274f..7357322 100644
--- a/src/lib/datasrc/tests/memory/zone_table_unittest.cc
+++ b/src/lib/datasrc/tests/memory/zone_table_unittest.cc
@@ -74,7 +74,7 @@ TEST_F(ZoneTableTest, addZone) {
EXPECT_EQ(0, zone_table->getZoneCount());
// It doesn't accept NULL as zone data
- EXPECT_THROW(zone_table->addZone(mem_sgmt_, zclass_, zname1, NULL),
+ EXPECT_THROW(zone_table->addZone(mem_sgmt_, zname1, NULL),
isc::InvalidParameter);
EXPECT_EQ(0, zone_table->getZoneCount()); // count is still 0
@@ -82,8 +82,7 @@ TEST_F(ZoneTableTest, addZone) {
SegmentObjectHolder<ZoneData, RRClass> holder_empty(
mem_sgmt_, zclass_);
holder_empty.set(ZoneData::create(mem_sgmt_));
- EXPECT_THROW(zone_table->addZone(mem_sgmt_, zclass_, zname1,
- holder_empty.get()),
+ EXPECT_THROW(zone_table->addZone(mem_sgmt_, zname1, holder_empty.get()),
isc::InvalidParameter);
SegmentObjectHolder<ZoneData, RRClass> holder1(
@@ -91,8 +90,7 @@ TEST_F(ZoneTableTest, addZone) {
holder1.set(ZoneData::create(mem_sgmt_, zname1));
const ZoneData* data1(holder1.get());
// Normal successful case.
- const ZoneTable::AddResult result1(zone_table->addZone(mem_sgmt_, zclass_,
- zname1,
+ const ZoneTable::AddResult result1(zone_table->addZone(mem_sgmt_, zname1,
holder1.release()));
EXPECT_EQ(result::SUCCESS, result1.code);
EXPECT_EQ(static_cast<const ZoneData*>(NULL), result1.zone_data);
@@ -103,8 +101,7 @@ TEST_F(ZoneTableTest, addZone) {
// Duplicate add replaces the existing data wit the newly added one.
SegmentObjectHolder<ZoneData, RRClass> holder2(mem_sgmt_, zclass_);
holder2.set(ZoneData::create(mem_sgmt_, zname1));
- const ZoneTable::AddResult result2(zone_table->addZone(mem_sgmt_, zclass_,
- zname1,
+ const ZoneTable::AddResult result2(zone_table->addZone(mem_sgmt_, zname1,
holder2.release()));
EXPECT_EQ(result::EXIST, result2.code);
// The old one gets out
@@ -119,7 +116,7 @@ TEST_F(ZoneTableTest, addZone) {
mem_sgmt_, zclass_);
holder3.set(ZoneData::create(mem_sgmt_, Name("EXAMPLE.COM")));
// names are compared in a case insensitive manner.
- const ZoneTable::AddResult result3(zone_table->addZone(mem_sgmt_, zclass_,
+ const ZoneTable::AddResult result3(zone_table->addZone(mem_sgmt_,
Name("EXAMPLE.COM"),
holder3.release()));
EXPECT_EQ(result::EXIST, result3.code);
@@ -129,26 +126,23 @@ TEST_F(ZoneTableTest, addZone) {
mem_sgmt_, zclass_);
holder4.set(ZoneData::create(mem_sgmt_, zname2));
EXPECT_EQ(result::SUCCESS,
- zone_table->addZone(mem_sgmt_, zclass_, zname2,
- holder4.release()).code);
+ zone_table->addZone(mem_sgmt_, zname2, holder4.release()).code);
EXPECT_EQ(2, zone_table->getZoneCount());
SegmentObjectHolder<ZoneData, RRClass> holder5(
mem_sgmt_, zclass_);
holder5.set(ZoneData::create(mem_sgmt_, zname3));
EXPECT_EQ(result::SUCCESS,
- zone_table->addZone(mem_sgmt_, zclass_, zname3,
- holder5.release()).code);
+ zone_table->addZone(mem_sgmt_, zname3, holder5.release()).code);
EXPECT_EQ(3, zone_table->getZoneCount());
// Have the memory segment throw an exception in extending the internal
- // tree. It still shouldn't cause memory leak (which would be detected
- // in TearDown()).
+ // tree. We'll destroy it after that via SegmentObjectHolder.
SegmentObjectHolder<ZoneData, RRClass> holder6(
mem_sgmt_, zclass_);
holder6.set(ZoneData::create(mem_sgmt_, Name("example.org")));
mem_sgmt_.setThrowCount(1);
- EXPECT_THROW(zone_table->addZone(mem_sgmt_, zclass_, Name("example.org"),
- holder6.release()),
+ EXPECT_THROW(zone_table->addZone(mem_sgmt_, Name("example.org"),
+ holder6.get()),
std::bad_alloc);
}
@@ -175,8 +169,7 @@ TEST_F(ZoneTableTest, addEmptyZone) {
// internal to the ZoneTable implementation.
SegmentObjectHolder<ZoneData, RRClass> holder2(mem_sgmt_, zclass_);
holder2.set(ZoneData::create(mem_sgmt_, zname1));
- const ZoneTable::AddResult result2(zone_table->addZone(mem_sgmt_, zclass_,
- zname1,
+ const ZoneTable::AddResult result2(zone_table->addZone(mem_sgmt_, zname1,
holder2.release()));
EXPECT_EQ(result::EXIST, result2.code);
EXPECT_EQ(static_cast<const ZoneData*>(NULL), result2.zone_data);
@@ -197,20 +190,18 @@ TEST_F(ZoneTableTest, findZone) {
mem_sgmt_, zclass_);
holder1.set(ZoneData::create(mem_sgmt_, zname1));
ZoneData* zone_data = holder1.get();
- EXPECT_EQ(result::SUCCESS, zone_table->addZone(mem_sgmt_, zclass_, zname1,
+ EXPECT_EQ(result::SUCCESS, zone_table->addZone(mem_sgmt_, zname1,
holder1.release()).code);
SegmentObjectHolder<ZoneData, RRClass> holder2(
mem_sgmt_, zclass_);
holder2.set(ZoneData::create(mem_sgmt_, zname2));
EXPECT_EQ(result::SUCCESS,
- zone_table->addZone(mem_sgmt_, zclass_, zname2,
- holder2.release()).code);
+ zone_table->addZone(mem_sgmt_, zname2, holder2.release()).code);
SegmentObjectHolder<ZoneData, RRClass> holder3(
mem_sgmt_, zclass_);
holder3.set(ZoneData::create(mem_sgmt_, zname3));
EXPECT_EQ(result::SUCCESS,
- zone_table->addZone(mem_sgmt_, zclass_, zname3,
- holder3.release()).code);
+ zone_table->addZone(mem_sgmt_, zname3, holder3.release()).code);
const ZoneTable::FindResult find_result1 =
zone_table->findZone(Name("example.com"));
@@ -234,8 +225,7 @@ TEST_F(ZoneTableTest, findZone) {
SegmentObjectHolder<ZoneData, RRClass> holder4(
mem_sgmt_, zclass_);
holder4.set(ZoneData::create(mem_sgmt_, Name("com")));
- EXPECT_EQ(result::SUCCESS, zone_table->addZone(mem_sgmt_, zclass_,
- Name("com"),
+ EXPECT_EQ(result::SUCCESS, zone_table->addZone(mem_sgmt_, Name("com"),
holder4.release()).code);
EXPECT_EQ(zone_data,
zone_table->findZone(Name("www.example.com")).zone_data);
diff --git a/src/lib/datasrc/tests/memory/zone_writer_unittest.cc b/src/lib/datasrc/tests/memory/zone_writer_unittest.cc
index 97a396f..e1fe672 100644
--- a/src/lib/datasrc/tests/memory/zone_writer_unittest.cc
+++ b/src/lib/datasrc/tests/memory/zone_writer_unittest.cc
@@ -12,10 +12,20 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
+#include <config.h>
+
#include <datasrc/memory/zone_writer.h>
#include <datasrc/memory/zone_table_segment_local.h>
#include <datasrc/memory/zone_data.h>
+#include <datasrc/memory/zone_data_loader.h>
+#include <datasrc/memory/load_action.h>
+#include <datasrc/memory/zone_table.h>
#include <datasrc/exceptions.h>
+#include <datasrc/result.h>
+
+#include <util/memory_segment_mapped.h>
+
+#include <cc/data.h>
#include <dns/rrclass.h>
#include <dns/name.h>
@@ -27,8 +37,10 @@
#include <boost/scoped_ptr.hpp>
#include <boost/bind.hpp>
+#include <boost/format.hpp>
#include <string>
+#include <unistd.h>
using boost::scoped_ptr;
using boost::bind;
@@ -325,4 +337,79 @@ TEST_F(ZoneWriterTest, autoCleanUp) {
EXPECT_NO_THROW(writer_->load());
}
+// Used in the manyWrites test, encapsulating loadZoneData() to avoid
+// its signature ambiguity.
+ZoneData*
+loadZoneDataWrapper(isc::util::MemorySegment& segment, const RRClass& rrclass,
+ const Name& name, const std::string& filename)
+{
+ return (loadZoneData(segment, rrclass, name, filename));
+}
+
+// Check the behavior of creating many small zones. The main purpose of
+// test is to trigger MemorySegmentGrown exception in ZoneWriter::install.
+// There's no easy (if any) way to cause that reliably as it's highly
+// dependent on details of the underlying boost implementation and probably
+// also on the system behavior, but we'll try some promising scenario (it
+// in fact triggered the intended result at least on one environment).
+TEST_F(ZoneWriterTest, manyWrites) {
+#ifdef USE_SHARED_MEMORY
+ // First, make a fresh mapped file of a small size (so it'll be more likely
+ // to grow in the test.
+ const char* const mapped_file = TEST_DATA_BUILDDIR "/test.mapped";
+ unlink(mapped_file);
+ boost::scoped_ptr<isc::util::MemorySegmentMapped> segment(
+ new isc::util::MemorySegmentMapped(
+ mapped_file, isc::util::MemorySegmentMapped::CREATE_ONLY, 4096));
+ segment.reset();
+
+ // Then prepare a ZoneTableSegment of the 'mapped' type specifying the
+ // file we just created.
+ boost::scoped_ptr<ZoneTableSegment> zt_segment(
+ ZoneTableSegment::create(RRClass::IN(), "mapped"));
+ const isc::data::ConstElementPtr params =
+ isc::data::Element::fromJSON(
+ "{\"mapped-file\": \"" + std::string(mapped_file) + "\"}");
+ zt_segment->reset(ZoneTableSegment::READ_WRITE, params);
+#else
+ // Do the same test for the local segment, although there shouldn't be
+ // anything tricky in that case.
+ boost::scoped_ptr<ZoneTableSegment> zt_segment(
+ ZoneTableSegment::create(RRClass::IN(), "local"));
+#endif
+
+ // Now, create many small zones in the zone table with a ZoneWriter.
+ // We use larger origin names so it'll (hopefully) require the memory
+ // segment to grow while adding the name into the internal table.
+ const size_t zone_count = 10000; // arbitrary choice
+ for (size_t i = 0; i < zone_count; ++i) {
+ const Name origin(
+ boost::str(boost::format("%063u.%063u.%063u.example.org")
+ % i % i % i));
+ const LoadAction action = boost::bind(loadZoneDataWrapper, _1,
+ RRClass::IN(), origin,
+ TEST_DATA_DIR
+ "/template.zone");
+ ZoneWriter writer(*zt_segment, action, origin, RRClass::IN(), false);
+ writer.load();
+ writer.install();
+ writer.cleanup();
+
+ // Confirm it's been successfully added and can be actually found.
+ const ZoneTable::FindResult result =
+ zt_segment->getHeader().getTable()->findZone(origin);
+ EXPECT_EQ(isc::datasrc::result::SUCCESS, result.code);
+ EXPECT_NE(static_cast<const ZoneData*>(NULL), result.zone_data) <<
+ "unexpected find result: " + origin.toText();
+ }
+
+ // Make sure to close the segment before (possibly) removing the mapped
+ // file.
+ zt_segment.reset();
+
+#ifdef USE_SHARED_MEMORY
+ unlink(mapped_file);
+#endif
+}
+
}
diff --git a/src/lib/dhcp/option_custom.h b/src/lib/dhcp/option_custom.h
index e0314ae..a7d2b95 100644
--- a/src/lib/dhcp/option_custom.h
+++ b/src/lib/dhcp/option_custom.h
@@ -310,6 +310,7 @@ private:
///
/// @throw isc::dhcp::InvalidDataType if the type is invalid.
template<typename T>
+ // cppcheck-suppress unusedPrivateFunction
void checkDataType(const uint32_t index) const;
/// @brief Check if data field index is valid.
diff --git a/src/lib/dhcp/option_definition.cc b/src/lib/dhcp/option_definition.cc
index 9db99f4..bf2c5fb 100644
--- a/src/lib/dhcp/option_definition.cc
+++ b/src/lib/dhcp/option_definition.cc
@@ -228,14 +228,6 @@ OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
}
void
-OptionDefinition::sanityCheckUniverse(const Option::Universe expected_universe,
- const Option::Universe actual_universe) {
- if (expected_universe != actual_universe) {
- isc_throw(isc::BadValue, "invalid universe specified for the option");
- }
-}
-
-void
OptionDefinition::validate() const {
using namespace boost::algorithm;
diff --git a/src/lib/dhcp/option_definition.h b/src/lib/dhcp/option_definition.h
index 0aa0e17..dcfc3c7 100644
--- a/src/lib/dhcp/option_definition.h
+++ b/src/lib/dhcp/option_definition.h
@@ -501,15 +501,6 @@ private:
void writeToBuffer(const std::string& value, const OptionDataType type,
OptionBuffer& buf) const;
- /// @brief Sanity check universe value.
- ///
- /// @param expected_universe expected universe value.
- /// @param actual_universe actual universe value.
- ///
- /// @throw isc::BadValue if expected universe and actual universe don't match.
- static inline void sanityCheckUniverse(const Option::Universe expected_universe,
- const Option::Universe actual_universe);
-
/// Option name.
std::string name_;
/// Option code.
diff --git a/src/lib/dhcpsrv/dhcp_parsers.cc b/src/lib/dhcpsrv/dhcp_parsers.cc
index d21538b..4d1dc73 100644
--- a/src/lib/dhcpsrv/dhcp_parsers.cc
+++ b/src/lib/dhcpsrv/dhcp_parsers.cc
@@ -79,9 +79,9 @@ DebugParser::DebugParser(const std::string& param_name)
void
DebugParser::build(ConstElementPtr new_config) {
+ value_ = new_config;
std::cout << "Build for token: [" << param_name_ << "] = ["
<< value_->str() << "]" << std::endl;
- value_ = new_config;
}
void
diff --git a/src/lib/dns/master_lexer.h b/src/lib/dns/master_lexer.h
index 31c6443..e33f964 100644
--- a/src/lib/dns/master_lexer.h
+++ b/src/lib/dns/master_lexer.h
@@ -22,6 +22,8 @@
#include <stdint.h>
+#include <boost/noncopyable.hpp>
+
namespace isc {
namespace dns {
namespace master_lexer_internal {
@@ -303,7 +305,7 @@ private:
/// implementation of the exception handling). For these reasons, some of
/// this class does not throw for an error that would be reported as an
/// exception in other classes.
-class MasterLexer {
+class MasterLexer : public boost::noncopyable {
friend class master_lexer_internal::State;
public:
/// \brief Exception thrown when we fail to read from the input
diff --git a/src/lib/dns/message.h b/src/lib/dns/message.h
index 8aaaa48..1c83e1e 100644
--- a/src/lib/dns/message.h
+++ b/src/lib/dns/message.h
@@ -98,10 +98,10 @@ struct SectionIteratorImpl;
template <typename T>
class SectionIterator : public std::iterator<std::input_iterator_tag, T> {
public:
- SectionIterator<T>() : impl_(NULL) {}
- SectionIterator<T>(const SectionIteratorImpl<T>& impl);
- ~SectionIterator<T>();
- SectionIterator<T>(const SectionIterator<T>& source);
+ SectionIterator() : impl_(NULL) {}
+ SectionIterator(const SectionIteratorImpl<T>& impl);
+ ~SectionIterator();
+ SectionIterator(const SectionIterator<T>& source);
void operator=(const SectionIterator<T>& source);
SectionIterator<T>& operator++();
SectionIterator<T> operator++(int);
diff --git a/src/lib/dns/python/pydnspp_common.cc b/src/lib/dns/python/pydnspp_common.cc
index e9d62e0..4250db7 100644
--- a/src/lib/dns/python/pydnspp_common.cc
+++ b/src/lib/dns/python/pydnspp_common.cc
@@ -56,9 +56,8 @@ PyObject* po_DNSMessageBADVERS;
int
readDataFromSequence(uint8_t *data, size_t len, PyObject* sequence) {
- PyObject* el = NULL;
for (size_t i = 0; i < len; i++) {
- el = PySequence_GetItem(sequence, i);
+ PyObject *el = PySequence_GetItem(sequence, i);
if (!el) {
PyErr_SetString(PyExc_TypeError,
"sequence too short");
diff --git a/src/lib/dns/rdata.cc b/src/lib/dns/rdata.cc
index 081f855..f42c349 100644
--- a/src/lib/dns/rdata.cc
+++ b/src/lib/dns/rdata.cc
@@ -310,6 +310,9 @@ Generic::Generic(const Generic& source) :
{}
Generic&
+// Our check is better than the usual if (this == &source),
+// but cppcheck doesn't recognize it.
+// cppcheck-suppress operatorEqToSelf
Generic::operator=(const Generic& source) {
if (impl_ == source.impl_) {
return (*this);
diff --git a/src/lib/dns/rdata/any_255/tsig_250.cc b/src/lib/dns/rdata/any_255/tsig_250.cc
index 4e86cf9..796e320 100644
--- a/src/lib/dns/rdata/any_255/tsig_250.cc
+++ b/src/lib/dns/rdata/any_255/tsig_250.cc
@@ -338,7 +338,7 @@ TSIG::TSIG(const TSIG& source) : Rdata(), impl_(new TSIGImpl(*source.impl_))
TSIG&
TSIG::operator=(const TSIG& source) {
- if (impl_ == source.impl_) {
+ if (this == &source) {
return (*this);
}
@@ -370,13 +370,13 @@ TSIG::toText() const {
lexical_cast<string>(impl_->time_signed_) + " " +
lexical_cast<string>(impl_->fudge_) + " " +
lexical_cast<string>(impl_->mac_.size()) + " ";
- if (impl_->mac_.size() > 0) {
+ if (!impl_->mac_.empty()) {
result += encodeBase64(impl_->mac_) + " ";
}
result += lexical_cast<string>(impl_->original_id_) + " ";
result += TSIGError(impl_->error_).toText() + " ";
result += lexical_cast<string>(impl_->other_data_.size());
- if (impl_->other_data_.size() > 0) {
+ if (!impl_->other_data_.empty()) {
result += " " + encodeBase64(impl_->other_data_);
}
@@ -520,7 +520,7 @@ TSIG::getMACSize() const {
const void*
TSIG::getMAC() const {
- if (impl_->mac_.size() > 0) {
+ if (!impl_->mac_.empty()) {
return (&impl_->mac_[0]);
} else {
return (NULL);
@@ -544,7 +544,7 @@ TSIG::getOtherLen() const {
const void*
TSIG::getOtherData() const {
- if (impl_->other_data_.size() > 0) {
+ if (!impl_->other_data_.empty()) {
return (&impl_->other_data_[0]);
} else {
return (NULL);
diff --git a/src/lib/dns/rdata/generic/detail/ds_like.h b/src/lib/dns/rdata/generic/detail/ds_like.h
index a7bebbf..c143cab 100644
--- a/src/lib/dns/rdata/generic/detail/ds_like.h
+++ b/src/lib/dns/rdata/generic/detail/ds_like.h
@@ -188,12 +188,12 @@ public:
/// \brief The copy constructor.
///
/// Trivial for now, we could've used the default one.
- DSLikeImpl(const DSLikeImpl& source) {
- digest_ = source.digest_;
- tag_ = source.tag_;
- algorithm_ = source.algorithm_;
- digest_type_ = source.digest_type_;
- }
+ DSLikeImpl(const DSLikeImpl& source) :
+ tag_(source.tag_),
+ algorithm_(source.algorithm_),
+ digest_type_(source.digest_type_),
+ digest_(source.digest_)
+ {}
/// \brief Convert the DS-like data to a string.
///
diff --git a/src/lib/dns/rdata/generic/dlv_32769.cc b/src/lib/dns/rdata/generic/dlv_32769.cc
index 89a62e1..97b9d1a 100644
--- a/src/lib/dns/rdata/generic/dlv_32769.cc
+++ b/src/lib/dns/rdata/generic/dlv_32769.cc
@@ -62,7 +62,7 @@ DLV::DLV(const DLV& source) :
/// PIMPL-induced logic
DLV&
DLV::operator=(const DLV& source) {
- if (impl_ == source.impl_) {
+ if (this == &source) {
return (*this);
}
diff --git a/src/lib/dns/rdata/generic/dnskey_48.cc b/src/lib/dns/rdata/generic/dnskey_48.cc
index 2e9a9f3..3ef6c72 100644
--- a/src/lib/dns/rdata/generic/dnskey_48.cc
+++ b/src/lib/dns/rdata/generic/dnskey_48.cc
@@ -212,7 +212,7 @@ DNSKEY::DNSKEY(const DNSKEY& source) :
DNSKEY&
DNSKEY::operator=(const DNSKEY& source) {
- if (impl_ == source.impl_) {
+ if (this == &source) {
return (*this);
}
diff --git a/src/lib/dns/rdata/generic/ds_43.cc b/src/lib/dns/rdata/generic/ds_43.cc
index 3492388..21bf8f3 100644
--- a/src/lib/dns/rdata/generic/ds_43.cc
+++ b/src/lib/dns/rdata/generic/ds_43.cc
@@ -50,7 +50,7 @@ DS::DS(const DS& source) :
DS&
DS::operator=(const DS& source) {
- if (impl_ == source.impl_) {
+ if (this == &source) {
return (*this);
}
diff --git a/src/lib/dns/rdata/generic/nsec3_50.cc b/src/lib/dns/rdata/generic/nsec3_50.cc
index fd8f78d..667f4a4 100644
--- a/src/lib/dns/rdata/generic/nsec3_50.cc
+++ b/src/lib/dns/rdata/generic/nsec3_50.cc
@@ -200,7 +200,7 @@ NSEC3::NSEC3(const NSEC3& source) :
NSEC3&
NSEC3::operator=(const NSEC3& source) {
- if (impl_ == source.impl_) {
+ if (this == &source) {
return (*this);
}
diff --git a/src/lib/dns/rdata/generic/nsec3param_51.cc b/src/lib/dns/rdata/generic/nsec3param_51.cc
index 494746d..f07c569 100644
--- a/src/lib/dns/rdata/generic/nsec3param_51.cc
+++ b/src/lib/dns/rdata/generic/nsec3param_51.cc
@@ -139,7 +139,7 @@ NSEC3PARAM::NSEC3PARAM(const NSEC3PARAM& source) :
NSEC3PARAM&
NSEC3PARAM::operator=(const NSEC3PARAM& source) {
- if (impl_ == source.impl_) {
+ if (this == &source) {
return (*this);
}
diff --git a/src/lib/dns/rdata/generic/nsec_47.cc b/src/lib/dns/rdata/generic/nsec_47.cc
index ffa2c97..c2dca32 100644
--- a/src/lib/dns/rdata/generic/nsec_47.cc
+++ b/src/lib/dns/rdata/generic/nsec_47.cc
@@ -155,7 +155,7 @@ NSEC::NSEC(const NSEC& source) :
NSEC&
NSEC::operator=(const NSEC& source) {
- if (impl_ == source.impl_) {
+ if (this == &source) {
return (*this);
}
diff --git a/src/lib/dns/rdata/generic/rrsig_46.cc b/src/lib/dns/rdata/generic/rrsig_46.cc
index 8e826d7..505c388 100644
--- a/src/lib/dns/rdata/generic/rrsig_46.cc
+++ b/src/lib/dns/rdata/generic/rrsig_46.cc
@@ -230,7 +230,7 @@ RRSIG::RRSIG(const RRSIG& source) :
RRSIG&
RRSIG::operator=(const RRSIG& source) {
- if (impl_ == source.impl_) {
+ if (this == &source) {
return (*this);
}
diff --git a/src/lib/dns/rdata/generic/spf_99.cc b/src/lib/dns/rdata/generic/spf_99.cc
index 4bf24e9..17c4e3c 100644
--- a/src/lib/dns/rdata/generic/spf_99.cc
+++ b/src/lib/dns/rdata/generic/spf_99.cc
@@ -42,7 +42,7 @@ using namespace isc::util;
/// This method never throws an exception otherwise.
SPF&
SPF::operator=(const SPF& source) {
- if (impl_ == source.impl_) {
+ if (this == &source) {
return (*this);
}
diff --git a/src/lib/dns/rdata/generic/sshfp_44.cc b/src/lib/dns/rdata/generic/sshfp_44.cc
index c7199f3..2865ed1 100644
--- a/src/lib/dns/rdata/generic/sshfp_44.cc
+++ b/src/lib/dns/rdata/generic/sshfp_44.cc
@@ -199,7 +199,7 @@ SSHFP::SSHFP(const SSHFP& other) :
SSHFP&
SSHFP::operator=(const SSHFP& source) {
- if (impl_ == source.impl_) {
+ if (this == &source) {
return (*this);
}
diff --git a/src/lib/dns/rdata/generic/txt_16.cc b/src/lib/dns/rdata/generic/txt_16.cc
index 1bd2eb1..ff5d0e1 100644
--- a/src/lib/dns/rdata/generic/txt_16.cc
+++ b/src/lib/dns/rdata/generic/txt_16.cc
@@ -33,7 +33,7 @@ using namespace isc::util;
TXT&
TXT::operator=(const TXT& source) {
- if (impl_ == source.impl_) {
+ if (this == &source) {
return (*this);
}
diff --git a/src/lib/dns/rdata/in_1/srv_33.cc b/src/lib/dns/rdata/in_1/srv_33.cc
index ac62071..fdb8f22 100644
--- a/src/lib/dns/rdata/in_1/srv_33.cc
+++ b/src/lib/dns/rdata/in_1/srv_33.cc
@@ -190,7 +190,7 @@ SRV::SRV(const SRV& source) :
SRV&
SRV::operator=(const SRV& source) {
- if (impl_ == source.impl_) {
+ if (this == &source) {
return (*this);
}
diff --git a/src/lib/dns/rrttl.cc b/src/lib/dns/rrttl.cc
index e7f8441..f4b02c0 100644
--- a/src/lib/dns/rrttl.cc
+++ b/src/lib/dns/rrttl.cc
@@ -74,10 +74,10 @@ parseTTLString(const string& ttlstr, uint32_t& ttlval, string* error_txt) {
const string::const_iterator end = ttlstr.end();
string::const_iterator pos = ttlstr.begin();
- // When we detect we have some units
- bool units_mode = false;
-
try {
+ // When we detect we have some units
+ bool units_mode = false;
+
while (pos != end) {
// Find the first unit, if there's any.
const string::const_iterator unit = find_if(pos, end, myIsalpha);
diff --git a/src/lib/dns/serial.h b/src/lib/dns/serial.h
index 678fb22..0cd6833 100644
--- a/src/lib/dns/serial.h
+++ b/src/lib/dns/serial.h
@@ -59,7 +59,10 @@ public:
/// \brief Direct assignment from other Serial
///
/// \param other The Serial to assign the value from
- void operator=(const Serial& other) { value_ = other.getValue(); }
+ Serial& operator=(const Serial& other) {
+ value_ = other.getValue();
+ return (*this);
+ }
/// \brief Direct assignment from value
///
diff --git a/src/lib/dns/tsigkey.cc b/src/lib/dns/tsigkey.cc
index e55cce3..7075203 100644
--- a/src/lib/dns/tsigkey.cc
+++ b/src/lib/dns/tsigkey.cc
@@ -148,7 +148,7 @@ TSIGKey::TSIGKey(const TSIGKey& source) : impl_(new TSIGKeyImpl(*source.impl_))
TSIGKey&
TSIGKey::operator=(const TSIGKey& source) {
- if (impl_ == source.impl_) {
+ if (this == &source) {
return (*this);
}
diff --git a/src/lib/log/logger_impl.h b/src/lib/log/logger_impl.h
index d8dea26..60f4c58 100644
--- a/src/lib/log/logger_impl.h
+++ b/src/lib/log/logger_impl.h
@@ -23,6 +23,7 @@
#include <string>
#include <map>
#include <utility>
+#include <boost/noncopyable.hpp>
// log4cplus logger header file
@@ -61,7 +62,7 @@ namespace log {
/// b) The idea of debug levels is implemented. See logger_level.h and
/// logger_level_impl.h for more details on this.
-class LoggerImpl {
+class LoggerImpl : public boost::noncopyable {
public:
/// \brief Constructor
diff --git a/src/lib/log/logger_manager.h b/src/lib/log/logger_manager.h
index b09383e..0c49757 100644
--- a/src/lib/log/logger_manager.h
+++ b/src/lib/log/logger_manager.h
@@ -19,6 +19,8 @@
#include <util/threads/sync.h>
#include <log/logger_specification.h>
+#include <boost/noncopyable.hpp>
+
// Generated if, when updating the logging specification, an unknown
// destination is encountered.
class UnknownLoggingDestination : public isc::Exception {
@@ -41,7 +43,7 @@ class LoggerManagerImpl;
/// To isolate the underlying implementation from basic processing, the
/// LoggerManager is implemented using the "pimpl" idiom.
-class LoggerManager {
+class LoggerManager : public boost::noncopyable {
public:
/// \brief Constructor
LoggerManager();
diff --git a/src/lib/log/message_exception.h b/src/lib/log/message_exception.h
index 5f1ad12..7133cd8 100644
--- a/src/lib/log/message_exception.h
+++ b/src/lib/log/message_exception.h
@@ -62,7 +62,7 @@ public:
/// \param lineno Line number on which error occurred (if > 0).
MessageException(const char* file, size_t line, const char* what,
MessageID id, const std::string& arg1, int lineno)
- : isc::Exception(file, line, what), id_(id)
+ : isc::Exception(file, line, what), id_(id), lineno_(lineno)
{
if (lineno > 0) {
args_.push_back(boost::lexical_cast<std::string>(lineno));
@@ -82,7 +82,7 @@ public:
MessageException(const char* file, size_t line, const char *what,
MessageID id, const std::string& arg1,
const std::string& arg2, int lineno)
- : isc::Exception(file, line, what), id_(id)
+ : isc::Exception(file, line, what), id_(id), lineno_(lineno)
{
if (lineno > 0) {
args_.push_back(boost::lexical_cast<std::string>(lineno));
diff --git a/src/lib/log/message_reader.cc b/src/lib/log/message_reader.cc
index b5a4d35..d54c41d 100644
--- a/src/lib/log/message_reader.cc
+++ b/src/lib/log/message_reader.cc
@@ -127,7 +127,7 @@ void
MessageReader::parsePrefix(const vector<string>& tokens) {
// Should not get here unless there is something in the tokens array.
- assert(tokens.size() > 0);
+ assert(!tokens.empty());
// Process $PREFIX. With no arguments, the prefix is set to the empty
// string. One argument sets the prefix to the to its value and more than
diff --git a/src/lib/python/isc/Makefile.am b/src/lib/python/isc/Makefile.am
index 712843e..7b7d768 100644
--- a/src/lib/python/isc/Makefile.am
+++ b/src/lib/python/isc/Makefile.am
@@ -1,5 +1,5 @@
SUBDIRS = datasrc util cc config dns log net notify testutils acl bind10
-SUBDIRS += xfrin log_messages server_common ddns sysinfo statistics
+SUBDIRS += xfrin log_messages server_common ddns sysinfo statistics memmgr
python_PYTHON = __init__.py
diff --git a/src/lib/python/isc/datasrc/Makefile.am b/src/lib/python/isc/datasrc/Makefile.am
index 8a4b3a7..4278805 100644
--- a/src/lib/python/isc/datasrc/Makefile.am
+++ b/src/lib/python/isc/datasrc/Makefile.am
@@ -10,6 +10,7 @@ python_PYTHON = __init__.py sqlite3_ds.py
AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
AM_CPPFLAGS += $(BOOST_INCLUDES)
AM_CPPFLAGS += $(SQLITE_CFLAGS)
+AM_CXXFLAGS = $(B10_CXXFLAGS)
python_LTLIBRARIES = datasrc.la
datasrc_la_SOURCES = datasrc.cc datasrc.h
@@ -23,6 +24,7 @@ datasrc_la_SOURCES += configurableclientlist_python.h
datasrc_la_SOURCES += zone_loader_python.cc zone_loader_python.h
datasrc_la_SOURCES += zonetable_accessor_python.cc zonetable_accessor_python.h
datasrc_la_SOURCES += zonetable_iterator_python.cc zonetable_iterator_python.h
+datasrc_la_SOURCES += zonewriter_python.cc zonewriter_python.h
datasrc_la_CPPFLAGS = $(AM_CPPFLAGS) $(PYTHON_INCLUDES)
datasrc_la_CXXFLAGS = $(AM_CXXFLAGS) $(PYTHON_CXXFLAGS)
@@ -34,11 +36,13 @@ datasrc_la_LIBADD += $(top_builddir)/src/lib/dns/python/libb10-pydnspp.la
datasrc_la_LIBADD += $(PYTHON_LIB)
EXTRA_DIST = client_inc.cc
+EXTRA_DIST += configurableclientlist_inc.cc
EXTRA_DIST += finder_inc.cc
EXTRA_DIST += iterator_inc.cc
EXTRA_DIST += updater_inc.cc
EXTRA_DIST += journal_reader_inc.cc
EXTRA_DIST += zone_loader_inc.cc
+EXTRA_DIST += zonewriter_inc.cc
CLEANDIRS = __pycache__
diff --git a/src/lib/python/isc/datasrc/configurableclientlist_inc.cc b/src/lib/python/isc/datasrc/configurableclientlist_inc.cc
new file mode 100644
index 0000000..b16a6bb
--- /dev/null
+++ b/src/lib/python/isc/datasrc/configurableclientlist_inc.cc
@@ -0,0 +1,134 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+namespace {
+
+const char* const ConfigurableClientList_doc = "\
+The list of data source clients\n\
+\n\
+The purpose is to have several data source clients of the same class\
+and then be able to search through them to identify the one containing\
+a given zone.\n\
+\n\
+Unlike the C++ version, we don't have the abstract base class. Abstract\
+classes are not needed due to the duck typing nature of python.\
+";
+
+const char* const ConfigurableClientList_configure_doc = "\
+configure(configuration, allow_cache) -> None\n\
+\n\
+Wrapper around C++ ConfigurableClientList::configure\n\
+\n\
+This sets the active configuration. It fills the ConfigurableClientList with\
+corresponding data source clients.\n\
+\n\
+If any error is detected, an exception is raised and the previous\
+configuration preserved.\n\
+\n\
+Parameters:\n\
+ configuration The configuration, as a JSON encoded string.\
+ allow_cache If caching is allowed.\
+";
+
+const char* const ConfigurableClientList_reset_memory_segment_doc = "\
+reset_memory_segment(datasrc_name, mode, config_params) -> None\n\
+\n\
+This method resets the zone table segment for a datasource with a new\n\
+memory segment.\n\
+\n\
+Parameters:\n\
+ datasrc_name The name of the data source whose segment to reset.\n\
+ mode The open mode for the new memory segment.\n\
+ config_params The configuration for the new memory segment, as a JSON encoded string.\n\
+";
+
+const char* const ConfigurableClientList_get_zone_table_accessor_doc = "\
+get_zone_table_accessor(datasrc_name, use_cache) -> \
+isc.datasrc.ZoneTableAccessor\n\
+\n\
+Create a ZoneTableAccessor object for the specified data source.\n\
+\n\
+Parameters:\n\
+ datasrc_name If not empty, the name of the data source\n\
+ use_cache If true, create a zone table for in-memory cache.\n\
+";
+
+const char* const ConfigurableClientList_get_cached_zone_writer_doc = "\
+get_cached_zone_writer(zone, catch_load_error, datasrc_name) -> status, zone_writer\n\
+\n\
+This method returns a ZoneWriter that can be used to (re)load a zone.\n\
+\n\
+By default this method identifies the first data source in the list\n\
+that should serve the zone of the given name, and returns a ZoneWriter\n\
+object that can be used to load the content of the zone, in a specific\n\
+way for that data source.\n\
+\n\
+If the optional datasrc_name parameter is provided with a non empty\n\
+string, this method only tries to load the specified zone into or with\n\
+the data source which has the given name, regardless where in the list\n\
+that data source is placed. Even if the given name of zone doesn't\n\
+exist in the data source, other data sources are not searched and\n\
+this method simply returns ZONE_NOT_FOUND in the first element\n\
+of the pair.\n\
+\n\
+Two elements are returned. The first element is a status\n\
+indicating if it worked or not (and in case it didn't, also why). If the\n\
+status is ZONE_SUCCESS, the second element contains a ZoneWriter object. If\n\
+the status is anything else, the second element is None.\n\
+\n\
+Parameters:\n\
+ zone The origin of the zone to (re)load.\n\
+ catch_load_error Whether to catch load errors, or to raise them as exceptions.\n\
+ datasrc_name The name of the data source where the zone is to be loaded (optional).\n\
+";
+
+const char* const ConfigurableClientList_get_status_doc = "\
+get_status() -> list of tuples\n\
+\n\
+This method returns a list of tuples, with each tuple containing the\n\
+status of a data source client. If there are no data source clients\n\
+present, an empty list is returned.\n\
+\n\
+The tuples contain (name, segment_type, segment_state):\n\
+ name The name of the data source.\n\
+ segment_type A string indicating the type of memory segment in use.\n\
+ segment_state The state of the memory segment.\n\
+\n\
+If segment_state is SEGMENT_UNUSED, None is returned for the segment_type.\n\
+";
+
+const char* const ConfigurableClientList_find_doc = "\
+find(zone, want_exact_match=False, want_finder=True) -> datasrc_client,\
+zone_finder, exact_match\n\
+\n\
+Look for a data source containing the given zone.\n\
+\n\
+It searches through the contained data sources and returns a data source\
+containing the zone, the zone finder of the zone and a boolean if the answer\
+is an exact match.\n\
+\n\
+The first parameter is isc.dns.Name object of a name in the zone. If the\
+want_exact_match is True, only zone with this exact origin is returned.\
+If it is False, the best matching zone is returned.\n\
+\n\
+If the want_finder is False, the returned zone_finder might be None even\
+if the data source is identified (in such case, the datasrc_client is not\
+None). Setting it to false allows the client list some optimisations, if\
+you don't need it, but if you do need it, it is better to set it to True\
+instead of getting it from the datasrc_client later.\n\
+\n\
+If no answer is found, the datasrc_client and zone_finder are None.\
+";
+
+} // unnamed namespace
diff --git a/src/lib/python/isc/datasrc/configurableclientlist_python.cc b/src/lib/python/isc/datasrc/configurableclientlist_python.cc
index cb1f6f4..a8c6d5f 100644
--- a/src/lib/python/isc/datasrc/configurableclientlist_python.cc
+++ b/src/lib/python/isc/datasrc/configurableclientlist_python.cc
@@ -36,12 +36,16 @@
#include "finder_python.h"
#include "client_python.h"
#include "zonetable_accessor_python.h"
+#include "zonewriter_python.h"
+
+#include "configurableclientlist_inc.cc"
using namespace std;
using namespace isc::util::python;
using namespace isc::datasrc;
using namespace isc::datasrc::memory;
using namespace isc::datasrc::python;
+using namespace isc::datasrc::memory::python;
using namespace isc::dns::python;
//
@@ -68,7 +72,8 @@ ConfigurableClientList_init(PyObject* po_self, PyObject* args, PyObject*) {
return (0);
}
} catch (const exception& ex) {
- const string ex_what = "Failed to construct ConfigurableClientList object: " +
+ const string ex_what =
+ "Failed to construct ConfigurableClientList object: " +
string(ex.what());
PyErr_SetString(getDataSourceException("Error"), ex_what.c_str());
return (-1);
@@ -153,6 +158,96 @@ ConfigurableClientList_resetMemorySegment(PyObject* po_self, PyObject* args) {
}
PyObject*
+ConfigurableClientList_getCachedZoneWriter(PyObject* po_self, PyObject* args) {
+ s_ConfigurableClientList* self =
+ static_cast<s_ConfigurableClientList*>(po_self);
+ try {
+ PyObject* name_obj;
+ int catch_load_error;
+ const char* datasrc_name_p = "";
+#if (PY_VERSION_HEX >= 0x030300f0)
+ // The 'p' specifier for predicate (boolean) is available from
+ // Python 3.3 (final) only.
+ if (PyArg_ParseTuple(args, "O!p|s", &isc::dns::python::name_type,
+ &name_obj, &catch_load_error, &datasrc_name_p)) {
+#else
+ if (PyArg_ParseTuple(args, "O!i|s", &isc::dns::python::name_type,
+ &name_obj, &catch_load_error, &datasrc_name_p)) {
+#endif
+ const isc::dns::Name&
+ name(isc::dns::python::PyName_ToName(name_obj));
+ const std::string datasrc_name(datasrc_name_p);
+
+ const ConfigurableClientList::ZoneWriterPair result =
+ self->cppobj->getCachedZoneWriter(name, catch_load_error,
+ datasrc_name);
+
+ PyObjectContainer writer;
+ if (!result.second) {
+ // Use the Py_BuildValue, as it takes care of the
+ // reference counts correctly.
+ writer.reset(Py_BuildValue(""));
+ } else {
+ // Make sure it keeps the writer alive.
+ writer.reset(createZoneWriterObject(result.second,
+ po_self));
+ }
+
+ return (Py_BuildValue("IO", result.first, writer.get()));
+ } else {
+ return (NULL);
+ }
+ } catch (const std::exception& exc) {
+ PyErr_SetString(getDataSourceException("Error"), exc.what());
+ return (NULL);
+ } catch (...) {
+ PyErr_SetString(getDataSourceException("Error"),
+ "Unknown C++ exception");
+ return (NULL);
+ }
+}
+
+PyObject*
+ConfigurableClientList_getStatus(PyObject* po_self, PyObject*) {
+ s_ConfigurableClientList* self =
+ static_cast<s_ConfigurableClientList*>(po_self);
+ try {
+ const std::vector<DataSourceStatus> status = self->cppobj->getStatus();
+
+ PyObjectContainer slist(PyList_New(status.size()));
+
+ for (size_t i = 0; i < status.size(); ++i) {
+ PyObjectContainer segment_type;
+
+ if (status[i].getSegmentState() != SEGMENT_UNUSED) {
+ segment_type.reset(Py_BuildValue(
+ "s", status[i].getSegmentType().c_str()));
+ } else {
+ Py_INCREF(Py_None);
+ segment_type.reset(Py_None);
+ }
+
+ PyObjectContainer tup(Py_BuildValue("(sOI)",
+ status[i].getName().c_str(),
+ segment_type.get(),
+ status[i].getSegmentState()));
+ // The following "steals" our reference on tup, so we must
+ // not decref.
+ PyList_SET_ITEM(slist.get(), i, tup.release());
+ }
+
+ return (slist.release());
+ } catch (const std::exception& exc) {
+ PyErr_SetString(getDataSourceException("Error"), exc.what());
+ return (NULL);
+ } catch (...) {
+ PyErr_SetString(getDataSourceException("Error"),
+ "Unknown C++ exception");
+ return (NULL);
+ }
+}
+
+PyObject*
ConfigurableClientList_find(PyObject* po_self, PyObject* args) {
s_ConfigurableClientList* self =
static_cast<s_ConfigurableClientList*>(po_self);
@@ -162,7 +257,7 @@ ConfigurableClientList_find(PyObject* po_self, PyObject* args) {
int want_finder = 1;
if (PyArg_ParseTuple(args, "O!|ii", &isc::dns::python::name_type,
&name_obj, &want_exact_match, &want_finder)) {
- const isc::dns::Name
+ const isc::dns::Name&
name(isc::dns::python::PyName_ToName(name_obj));
const ClientList::FindResult
result(self->cppobj->find(name, want_exact_match,
@@ -245,78 +340,21 @@ ConfigurableClientList_getZoneTableAccessor(PyObject* po_self, PyObject* args) {
// 3. Argument type
// 4. Documentation
PyMethodDef ConfigurableClientList_methods[] = {
- { "configure", ConfigurableClientList_configure, METH_VARARGS,
- "configure(configuration, allow_cache) -> None\n\
-\n\
-Wrapper around C++ ConfigurableClientList::configure\n\
-\n\
-This sets the active configuration. It fills the ConfigurableClientList with\
-corresponding data source clients.\n\
-\n\
-If any error is detected, an exception is raised and the previous\
-configuration preserved.\n\
-\n\
-Parameters:\n\
- configuration The configuration, as a JSON encoded string.\
- allow_cache If caching is allowed." },
+ { "configure", ConfigurableClientList_configure,
+ METH_VARARGS, ConfigurableClientList_configure_doc },
{ "reset_memory_segment", ConfigurableClientList_resetMemorySegment,
- METH_VARARGS,
- "reset_memory_segment(datasrc_name, mode, config_params) -> None\n\
-\n\
-Wrapper around C++ ConfigurableClientList::resetMemorySegment\n\
-\n\
-This resets the zone table segment for a datasource with a new\n\
-memory segment.\n\
-\n\
-Parameters:\n\
- datasrc_name The name of the data source whose segment to reset.\
- mode The open mode for the new memory segment.\
- config_params The configuration for the new memory segment, as a JSON encoded string." },
- { "find", ConfigurableClientList_find, METH_VARARGS,
-"find(zone, want_exact_match=False, want_finder=True) -> datasrc_client,\
-zone_finder, exact_match\n\
-\n\
-Look for a data source containing the given zone.\n\
-\n\
-It searches through the contained data sources and returns a data source\
-containing the zone, the zone finder of the zone and a boolean if the answer\
-is an exact match.\n\
-\n\
-The first parameter is isc.dns.Name object of a name in the zone. If the\
-want_exact_match is True, only zone with this exact origin is returned.\
-If it is False, the best matching zone is returned.\n\
-\n\
-If the want_finder is False, the returned zone_finder might be None even\
-if the data source is identified (in such case, the datasrc_client is not\
-None). Setting it to false allows the client list some optimisations, if\
-you don't need it, but if you do need it, it is better to set it to True\
-instead of getting it from the datasrc_client later.\n\
-\n\
-If no answer is found, the datasrc_client and zone_finder are None." },
+ METH_VARARGS, ConfigurableClientList_reset_memory_segment_doc },
{ "get_zone_table_accessor", ConfigurableClientList_getZoneTableAccessor,
- METH_VARARGS,
-"get_zone_table_accessor(datasrc_name, use_cache) -> \
-isc.datasrc.ZoneTableAccessor\n\
-\n\
-Create a ZoneTableAccessor object for the specified data source.\n\
-\n\
-Parameters:\n\
- datasrc_name If not empty, the name of the data source\
- use_cache If true, create a zone table for in-memory cache." },
+ METH_VARARGS, ConfigurableClientList_get_zone_table_accessor_doc },
+ { "get_cached_zone_writer", ConfigurableClientList_getCachedZoneWriter,
+ METH_VARARGS, ConfigurableClientList_get_cached_zone_writer_doc },
+ { "get_status", ConfigurableClientList_getStatus,
+ METH_NOARGS, ConfigurableClientList_get_status_doc },
+ { "find", ConfigurableClientList_find,
+ METH_VARARGS, ConfigurableClientList_find_doc },
{ NULL, NULL, 0, NULL }
};
-const char* const ConfigurableClientList_doc = "\
-The list of data source clients\n\
-\n\
-The purpose is to have several data source clients of the same class\
-and then be able to search through them to identify the one containing\
-a given zone.\n\
-\n\
-Unlike the C++ version, we don't have the abstract base class. Abstract\
-classes are not needed due to the duck typing nature of python.\
-";
-
} // end of unnamed namespace
namespace isc {
@@ -391,11 +429,48 @@ initModulePart_ConfigurableClientList(PyObject* mod) {
}
Py_INCREF(&configurableclientlist_type);
- // FIXME: These should eventually be moved to the ZoneTableSegment
- // class when we add Python bindings for the memory data source
- // specific bits. But for now, we add these enums here to support
- // reloading a zone table segment.
try {
+ // ConfigurableClientList::CacheStatus enum
+ installClassVariable
+ (configurableclientlist_type,
+ "CACHE_STATUS_CACHE_DISABLED",
+ Py_BuildValue("I", ConfigurableClientList::CACHE_DISABLED));
+ installClassVariable
+ (configurableclientlist_type,
+ "CACHE_STATUS_ZONE_NOT_CACHED",
+ Py_BuildValue("I", ConfigurableClientList::ZONE_NOT_CACHED));
+ installClassVariable
+ (configurableclientlist_type,
+ "CACHE_STATUS_ZONE_NOT_FOUND",
+ Py_BuildValue("I", ConfigurableClientList::ZONE_NOT_FOUND));
+ installClassVariable
+ (configurableclientlist_type,
+ "CACHE_STATUS_CACHE_NOT_WRITABLE",
+ Py_BuildValue("I", ConfigurableClientList::CACHE_NOT_WRITABLE));
+ installClassVariable
+ (configurableclientlist_type,
+ "CACHE_STATUS_DATASRC_NOT_FOUND",
+ Py_BuildValue("I", ConfigurableClientList::DATASRC_NOT_FOUND));
+ installClassVariable
+ (configurableclientlist_type,
+ "CACHE_STATUS_ZONE_SUCCESS",
+ Py_BuildValue("I", ConfigurableClientList::ZONE_SUCCESS));
+
+ // MemorySegmentState enum
+ installClassVariable(configurableclientlist_type,
+ "SEGMENT_UNUSED",
+ Py_BuildValue("I", SEGMENT_UNUSED));
+ installClassVariable(configurableclientlist_type,
+ "SEGMENT_WAITING",
+ Py_BuildValue("I", SEGMENT_WAITING));
+ installClassVariable(configurableclientlist_type,
+ "SEGMENT_INUSE",
+ Py_BuildValue("I", SEGMENT_INUSE));
+
+ // FIXME: These should eventually be moved to the
+ // ZoneTableSegment class when we add Python bindings for the
+ // memory data source specific bits. But for now, we add these
+ // enums here to support reloading a zone table segment.
installClassVariable(configurableclientlist_type, "CREATE",
Py_BuildValue("I", ZoneTableSegment::CREATE));
installClassVariable(configurableclientlist_type, "READ_WRITE",
diff --git a/src/lib/python/isc/datasrc/datasrc.cc b/src/lib/python/isc/datasrc/datasrc.cc
index 117e409..11c3e7c 100644
--- a/src/lib/python/isc/datasrc/datasrc.cc
+++ b/src/lib/python/isc/datasrc/datasrc.cc
@@ -35,6 +35,7 @@
#include "zone_loader_python.h"
#include "zonetable_accessor_python.h"
#include "zonetable_iterator_python.h"
+#include "zonewriter_python.h"
#include <util/python/pycppwrapper_util.h>
#include <dns/python/pydnspp_common.h>
@@ -44,6 +45,7 @@
using namespace isc::datasrc;
using namespace isc::datasrc::python;
+using namespace isc::datasrc::memory::python;
using namespace isc::util::python;
using namespace isc::dns::python;
@@ -388,5 +390,10 @@ PyInit_datasrc(void) {
return (NULL);
}
+ if (!initModulePart_ZoneWriter(mod)) {
+ Py_DECREF(mod);
+ return (NULL);
+ }
+
return (mod);
}
diff --git a/src/lib/python/isc/datasrc/iterator_python.cc b/src/lib/python/isc/datasrc/iterator_python.cc
index 9757a3b..0b80f20 100644
--- a/src/lib/python/isc/datasrc/iterator_python.cc
+++ b/src/lib/python/isc/datasrc/iterator_python.cc
@@ -61,7 +61,7 @@ typedef CPPPyObjectContainer<s_ZoneIterator, ZoneIterator>
// General creation and destruction
int
-ZoneIterator_init(s_ZoneIterator* self, PyObject* args) {
+ZoneIterator_init(s_ZoneIterator*, PyObject*) {
// can't be called directly
PyErr_SetString(PyExc_TypeError,
"ZoneIterator cannot be constructed directly");
diff --git a/src/lib/python/isc/datasrc/tests/Makefile.am b/src/lib/python/isc/datasrc/tests/Makefile.am
index a86c2b4..256ca62 100644
--- a/src/lib/python/isc/datasrc/tests/Makefile.am
+++ b/src/lib/python/isc/datasrc/tests/Makefile.am
@@ -1,17 +1,10 @@
+SUBDIRS = testdata
+
PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
PYTESTS = datasrc_test.py sqlite3_ds_test.py
PYTESTS += clientlist_test.py zone_loader_test.py
EXTRA_DIST = $(PYTESTS)
-EXTRA_DIST += testdata/brokendb.sqlite3
-EXTRA_DIST += testdata/example.com.sqlite3
-EXTRA_DIST += testdata/example.com.source.sqlite3
-EXTRA_DIST += testdata/newschema.sqlite3
-EXTRA_DIST += testdata/oldschema.sqlite3
-EXTRA_DIST += testdata/new_minor_schema.sqlite3
-EXTRA_DIST += testdata/example.com
-EXTRA_DIST += testdata/example.com.ch
-EXTRA_DIST += testdata/static.zone
CLEANFILES = $(abs_builddir)/rwtest.sqlite3.copied
CLEANFILES += $(abs_builddir)/zoneloadertest.sqlite3
@@ -35,13 +28,18 @@ if ENABLE_PYTHON_COVERAGE
rm -f .coverage
${LN_S} $(abs_top_srcdir)/.coverage .coverage
endif
+if USE_SHARED_MEMORY
+HAVE_SHARED_MEMORY=yes
+else
+HAVE_SHARED_MEMORY=no
+endif
for pytest in $(PYTESTS) ; do \
echo Running test: $$pytest ; \
$(LIBRARY_PATH_PLACEHOLDER) \
PYTHONPATH=:$(COMMON_PYTHON_PATH):$(abs_top_builddir)/src/lib/python/isc/log:$(abs_top_builddir)/src/lib/python/isc/datasrc/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs \
TESTDATA_PATH=$(abs_srcdir)/testdata \
TESTDATA_WRITE_PATH=$(abs_builddir) \
- GLOBAL_TESTDATA_PATH=$(abs_top_srcdir)/src/lib/testutils/testdata \
+ HAVE_SHARED_MEMORY=$(HAVE_SHARED_MEMORY) \
B10_FROM_BUILD=$(abs_top_builddir) \
$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
done
diff --git a/src/lib/python/isc/datasrc/tests/clientlist_test.py b/src/lib/python/isc/datasrc/tests/clientlist_test.py
index 2609b6b..e8056a5 100644
--- a/src/lib/python/isc/datasrc/tests/clientlist_test.py
+++ b/src/lib/python/isc/datasrc/tests/clientlist_test.py
@@ -20,7 +20,8 @@ import unittest
import os
import sys
-TESTDATA_PATH = os.environ['GLOBAL_TESTDATA_PATH'] + os.sep
+TESTDATA_PATH = os.environ['TESTDATA_PATH'] + os.sep
+MAPFILE_PATH = os.environ['TESTDATA_WRITE_PATH'] + os.sep + 'test.mapped'
class ClientListTest(unittest.TestCase):
"""
@@ -36,8 +37,18 @@ class ClientListTest(unittest.TestCase):
# last.
self.dsrc = None
self.finder = None
+
+ # If a test created a ZoneWriter with a mapped memory segment,
+ # the writer will hold a reference to the client list which will
+ # need the mapfile to exist until it's destroyed. So we'll make
+ # sure to destroy the writer (by resetting it) before removing
+ # the mapfile below.
+ self.__zone_writer = None
self.clist = None
+ if os.path.exists(MAPFILE_PATH):
+ os.unlink(MAPFILE_PATH)
+
def test_constructors(self):
"""
Test the constructor. It should accept an RRClass. Check it
@@ -54,6 +65,15 @@ class ClientListTest(unittest.TestCase):
self.assertRaises(TypeError, isc.datasrc.ConfigurableClientList,
isc.dns.RRClass.IN, isc.dns.RRClass.IN)
+ def configure_helper(self):
+ self.clist.configure('''[{
+ "type": "MasterFiles",
+ "params": {
+ "example.com": "''' + TESTDATA_PATH + '''example.com"
+ },
+ "cache-enable": true
+ }]''', True)
+
def test_configure(self):
"""
Test we can configure the client list. This tests if the valid
@@ -64,22 +84,16 @@ class ClientListTest(unittest.TestCase):
# This should be NOP now
self.clist.configure("[]", True)
# Check the zone is not there yet
- dsrc, finder, exact = self.clist.find(isc.dns.Name("example.org"))
+ dsrc, finder, exact = self.clist.find(isc.dns.Name("example.com"))
self.assertIsNone(dsrc)
self.assertIsNone(finder)
self.assertFalse(exact)
# We can use this type, as it is not loaded dynamically.
- self.clist.configure('''[{
- "type": "MasterFiles",
- "params": {
- "example.org": "''' + TESTDATA_PATH + '''example.org.zone"
- },
- "cache-enable": true
- }]''', True)
+ self.configure_helper()
# Check the zone is there now. Proper tests of find are in other
# test methods.
self.dsrc, self.finder, exact = \
- self.clist.find(isc.dns.Name("example.org"))
+ self.clist.find(isc.dns.Name("example.com"))
self.assertIsNotNone(self.dsrc)
self.assertTrue(isinstance(self.dsrc, isc.datasrc.DataSourceClient))
self.assertIsNotNone(self.finder)
@@ -88,29 +102,14 @@ class ClientListTest(unittest.TestCase):
self.assertRaises(isc.datasrc.Error, self.clist.configure,
'"bad type"', True)
self.assertRaises(isc.datasrc.Error, self.clist.configure, '''[{
- "type": "bad type"
- }]''', True)
- self.assertRaises(isc.datasrc.Error, self.clist.configure, '''[{
bad JSON,
}]''', True)
self.assertRaises(TypeError, self.clist.configure, [], True)
self.assertRaises(TypeError, self.clist.configure, "[]")
self.assertRaises(TypeError, self.clist.configure, "[]", "true")
- def test_find(self):
- """
- Test the find accepts the right arguments, some of them can be omitted,
- etc.
- """
- self.clist = isc.datasrc.ConfigurableClientList(isc.dns.RRClass.IN)
- self.clist.configure('''[{
- "type": "MasterFiles",
- "params": {
- "example.org": "''' + TESTDATA_PATH + '''example.org.zone"
- },
- "cache-enable": true
- }]''', True)
- dsrc, finder, exact = self.clist.find(isc.dns.Name("sub.example.org"))
+ def find_helper(self):
+ dsrc, finder, exact = self.clist.find(isc.dns.Name("sub.example.com"))
self.assertIsNotNone(dsrc)
self.assertTrue(isinstance(dsrc, isc.datasrc.DataSourceClient))
self.assertIsNotNone(finder)
@@ -124,31 +123,31 @@ class ClientListTest(unittest.TestCase):
# We check an exact match in test_configure already
self.assertFalse(exact)
self.dsrc, self.finder, exact = \
- self.clist.find(isc.dns.Name("sub.example.org"), False)
+ self.clist.find(isc.dns.Name("sub.example.com"), False)
self.assertIsNotNone(self.dsrc)
self.assertTrue(isinstance(self.dsrc, isc.datasrc.DataSourceClient))
self.assertIsNotNone(self.finder)
self.assertTrue(isinstance(self.finder, isc.datasrc.ZoneFinder))
self.assertFalse(exact)
self.dsrc, self.finder, exact = \
- self.clist.find(isc.dns.Name("sub.example.org"), True)
+ self.clist.find(isc.dns.Name("sub.example.com"), True)
self.assertIsNone(self.dsrc)
self.assertIsNone(self.finder)
self.assertFalse(exact)
self.dsrc, self.finder, exact = \
- self.clist.find(isc.dns.Name("sub.example.org"), False, False)
+ self.clist.find(isc.dns.Name("sub.example.com"), False, False)
self.assertIsNotNone(self.dsrc)
self.assertTrue(isinstance(self.dsrc, isc.datasrc.DataSourceClient))
self.assertIsNotNone(self.finder)
self.assertTrue(isinstance(self.finder, isc.datasrc.ZoneFinder))
self.assertFalse(exact)
self.dsrc, self.finder, exact = \
- self.clist.find(isc.dns.Name("sub.example.org"), True, False)
+ self.clist.find(isc.dns.Name("sub.example.com"), True, False)
self.assertIsNone(self.dsrc)
self.assertIsNone(self.finder)
self.assertFalse(exact)
# Some invalid inputs
- self.assertRaises(TypeError, self.clist.find, "example.org")
+ self.assertRaises(TypeError, self.clist.find, "example.com")
self.assertRaises(TypeError, self.clist.find)
def test_get_zone_table_accessor(self):
@@ -178,13 +177,7 @@ class ClientListTest(unittest.TestCase):
self.assertEqual(0, len(list(iterator)))
# normal configuration
- self.clist.configure('''[{
- "type": "MasterFiles",
- "params": {
- "example.org": "''' + TESTDATA_PATH + '''example.org.zone"
- },
- "cache-enable": true
- }]''', True)
+ self.configure_helper()
# !use_cache => NotImplemented
self.assertRaises(isc.datasrc.Error,
self.clist.get_zone_table_accessor, None, False)
@@ -196,7 +189,7 @@ class ClientListTest(unittest.TestCase):
self.assertIsNotNone(table)
zonelist = list(table)
self.assertEqual(1, len(zonelist))
- self.assertEqual(zonelist[0][1], isc.dns.Name("example.org"))
+ self.assertEqual(zonelist[0][1], isc.dns.Name("example.com"))
# named datasrc
table = self.clist.get_zone_table_accessor("MasterFiles", True)
@@ -231,6 +224,195 @@ class ClientListTest(unittest.TestCase):
zonelist.remove(zone)
self.assertEqual(0, len(zonelist))
+ def test_find(self):
+ """
+ Test the find accepts the right arguments, some of them can be omitted,
+ etc.
+ """
+ self.clist = isc.datasrc.ConfigurableClientList(isc.dns.RRClass.IN)
+ self.configure_helper()
+ self.find_helper()
+
+ @unittest.skipIf(os.environ['HAVE_SHARED_MEMORY'] != 'yes',
+ 'shared memory is not available')
+ def test_find_mapped(self):
+ """
+ Test find on a mapped segment.
+ """
+ self.clist = isc.datasrc.ConfigurableClientList(isc.dns.RRClass.IN)
+ self.clist.configure('''[{
+ "type": "MasterFiles",
+ "params": {
+ "example.com": "''' + TESTDATA_PATH + '''example.com"
+ },
+ "cache-enable": true,
+ "cache-type": "mapped"
+ }]''', True)
+
+ map_params = '{"mapped-file": "' + MAPFILE_PATH + '"}'
+ self.clist.reset_memory_segment("MasterFiles",
+ isc.datasrc.ConfigurableClientList.CREATE,
+ map_params)
+ result, self.__zone_writer = \
+ self.clist.get_cached_zone_writer(isc.dns.Name("example.com"),
+ False)
+ self.assertEqual(isc.datasrc.ConfigurableClientList.CACHE_STATUS_ZONE_SUCCESS,
+ result)
+ err_msg = self.__zone_writer.load()
+ self.assertIsNone(err_msg)
+ self.__zone_writer.install()
+ self.__zone_writer.cleanup()
+
+ self.clist.reset_memory_segment("MasterFiles",
+ isc.datasrc.ConfigurableClientList.READ_ONLY,
+ map_params)
+ result, self.__zone_writer = \
+ self.clist.get_cached_zone_writer(isc.dns.Name("example.com"),
+ False)
+ self.assertEqual(isc.datasrc.ConfigurableClientList.CACHE_STATUS_CACHE_NOT_WRITABLE,
+ result)
+
+ # The segment is still in READ_ONLY mode.
+ self.find_helper()
+
+ def test_zone_writer_load_twice(self):
+ """
+ Test that the zone writer throws when load() is called more than
+ once.
+ """
+
+ self.clist = isc.datasrc.ConfigurableClientList(isc.dns.RRClass.IN)
+ self.configure_helper()
+
+ result, self.__zone_writer = \
+ self.clist.get_cached_zone_writer(isc.dns.Name("example.com"),
+ False)
+ self.assertEqual(isc.datasrc.ConfigurableClientList.CACHE_STATUS_ZONE_SUCCESS,
+ result)
+ err_msg = self.__zone_writer.load()
+ self.assertIsNone(err_msg)
+ self.assertRaises(isc.datasrc.Error, self.__zone_writer.load)
+ self.__zone_writer.cleanup()
+
+ def test_zone_writer_load_without_raise(self):
+ """
+ Test that the zone writer does not throw when asked not to.
+ """
+
+ self.clist = isc.datasrc.ConfigurableClientList(isc.dns.RRClass.IN)
+ self.clist.configure('''[{
+ "type": "MasterFiles",
+ "params": {
+ "example.com": "''' + TESTDATA_PATH + '''example.com-broken.zone"
+ },
+ "cache-enable": true
+ }]''', True)
+
+ result, self.__zone_writer = \
+ self.clist.get_cached_zone_writer(isc.dns.Name("example.com"),
+ True)
+ self.assertEqual(isc.datasrc.ConfigurableClientList.CACHE_STATUS_ZONE_SUCCESS,
+ result)
+ err_msg = self.__zone_writer.load()
+ self.assertIsNotNone(err_msg)
+ self.assertTrue('Errors found when validating zone' in err_msg)
+ self.__zone_writer.cleanup()
+
+ def test_zone_writer_install_without_load(self):
+ """
+ Test that the zone writer throws when install() is called
+ without calling load() first.
+ """
+
+ self.clist = isc.datasrc.ConfigurableClientList(isc.dns.RRClass.IN)
+ self.configure_helper()
+
+ result, self.__zone_writer = \
+ self.clist.get_cached_zone_writer(isc.dns.Name("example.com"),
+ False)
+ self.assertEqual(isc.datasrc.ConfigurableClientList.CACHE_STATUS_ZONE_SUCCESS,
+ result)
+ self.assertRaises(isc.datasrc.Error, self.__zone_writer.install)
+ self.__zone_writer.cleanup()
+
+ def test_get_status(self):
+ """
+ Test getting status of various data sources.
+ """
+
+ self.clist = isc.datasrc.ConfigurableClientList(isc.dns.RRClass.IN)
+
+ status = self.clist.get_status()
+ self.assertIsNotNone(status)
+ self.assertTrue(isinstance(status, list))
+ self.assertEqual(0, len(status))
+
+ self.configure_helper()
+
+ status = self.clist.get_status()
+ self.assertIsNotNone(status)
+ self.assertTrue(isinstance(status, list))
+ self.assertEqual(1, len(status))
+ self.assertTrue(isinstance(status[0], tuple))
+ self.assertTupleEqual(('MasterFiles', 'local',
+ isc.datasrc.ConfigurableClientList.SEGMENT_INUSE),
+ status[0])
+
+ @unittest.skipIf(os.environ['HAVE_SHARED_MEMORY'] != 'yes',
+ 'shared memory is not available')
+ def test_get_status_unused(self):
+ """
+ Test getting status when segment type is mapped, but the cache
+ is disabled.
+ """
+
+ self.clist = isc.datasrc.ConfigurableClientList(isc.dns.RRClass.IN)
+ self.clist.configure('''[{
+ "type": "sqlite3",
+ "params": {
+ "database_file": "''' + TESTDATA_PATH + '''example.com.sqlite3"
+ },
+ "cache-zones" : ["example.com"],
+ "cache-type": "mapped",
+ "cache-enable": false
+ }]''', True)
+
+ status = self.clist.get_status()
+ self.assertIsNotNone(status)
+ self.assertTrue(isinstance(status, list))
+ self.assertEqual(1, len(status))
+ self.assertTrue(isinstance(status[0], tuple))
+ self.assertTupleEqual(('sqlite3', None,
+ isc.datasrc.ConfigurableClientList.SEGMENT_UNUSED),
+ status[0])
+
+ @unittest.skipIf(os.environ['HAVE_SHARED_MEMORY'] != 'yes',
+ 'shared memory is not available')
+ def test_get_status_waiting(self):
+ """
+ Test getting status when segment type is mapped and it has not
+ been reset yet.
+ """
+
+ self.clist = isc.datasrc.ConfigurableClientList(isc.dns.RRClass.IN)
+ self.clist.configure('''[{
+ "type": "MasterFiles",
+ "params": {
+ "example.com": "''' + TESTDATA_PATH + '''example.com"
+ },
+ "cache-enable": true,
+ "cache-type": "mapped"
+ }]''', True)
+
+ status = self.clist.get_status()
+ self.assertIsNotNone(status)
+ self.assertTrue(isinstance(status, list))
+ self.assertEqual(1, len(status))
+ self.assertTrue(isinstance(status[0], tuple))
+ self.assertTupleEqual(('MasterFiles', 'mapped',
+ isc.datasrc.ConfigurableClientList.SEGMENT_WAITING),
+ status[0])
+
if __name__ == "__main__":
isc.log.init("bind10")
isc.log.resetUnitTestRootLogger()
diff --git a/src/lib/python/isc/datasrc/tests/testdata/Makefile.am b/src/lib/python/isc/datasrc/tests/testdata/Makefile.am
new file mode 100644
index 0000000..8365a24
--- /dev/null
+++ b/src/lib/python/isc/datasrc/tests/testdata/Makefile.am
@@ -0,0 +1,12 @@
+EXTRA_DIST = \
+ brokendb.sqlite3 \
+ example.com \
+ example.com-broken.zone \
+ example.com.ch \
+ example.com.source.sqlite3 \
+ example.com.sqlite3 \
+ Makefile.am \
+ new_minor_schema.sqlite3 \
+ newschema.sqlite3 \
+ oldschema.sqlite3 \
+ static.zone
diff --git a/src/lib/python/isc/datasrc/tests/testdata/example.com-broken.zone b/src/lib/python/isc/datasrc/tests/testdata/example.com-broken.zone
new file mode 100644
index 0000000..079b400
--- /dev/null
+++ b/src/lib/python/isc/datasrc/tests/testdata/example.com-broken.zone
@@ -0,0 +1,6 @@
+; This zone is broken (contains no NS records).
+example.com. 1000 IN SOA a.dns.example.com. mail.example.com. 2 1 1 1 1
+a.dns.example.com. 1000 IN A 1.1.1.1
+b.dns.example.com. 1000 IN A 3.3.3.3
+b.dns.example.com. 1000 IN AAAA 4:4::4:4
+b.dns.example.com. 1000 IN AAAA 5:5::5:5
diff --git a/src/lib/python/isc/datasrc/zonetable_accessor_python.cc b/src/lib/python/isc/datasrc/zonetable_accessor_python.cc
index 353f7b5..d0583b9 100644
--- a/src/lib/python/isc/datasrc/zonetable_accessor_python.cc
+++ b/src/lib/python/isc/datasrc/zonetable_accessor_python.cc
@@ -34,7 +34,11 @@ namespace {
// The s_* Class simply covers one instantiation of the object
class s_ZoneTableAccessor : public PyObject {
public:
- s_ZoneTableAccessor() : cppobj(ConstZoneTableAccessorPtr()) {};
+ s_ZoneTableAccessor() :
+ cppobj(ConstZoneTableAccessorPtr()),
+ base_obj(NULL)
+ {}
+
ConstZoneTableAccessorPtr cppobj;
// This is a reference to a base object; if the object of this class
// depends on another object to be in scope during its lifetime,
diff --git a/src/lib/python/isc/datasrc/zonetable_iterator_python.cc b/src/lib/python/isc/datasrc/zonetable_iterator_python.cc
index 9ac7cd9..fbf1ebf 100644
--- a/src/lib/python/isc/datasrc/zonetable_iterator_python.cc
+++ b/src/lib/python/isc/datasrc/zonetable_iterator_python.cc
@@ -49,7 +49,7 @@ public:
// General creation and destruction
int
-ZoneTableIterator_init(s_ZoneTableIterator* self, PyObject* args) {
+ZoneTableIterator_init(s_ZoneTableIterator*, PyObject*) {
// can't be called directly
PyErr_SetString(PyExc_TypeError,
"ZoneTableIterator cannot be constructed directly");
diff --git a/src/lib/python/isc/datasrc/zonewriter_inc.cc b/src/lib/python/isc/datasrc/zonewriter_inc.cc
new file mode 100644
index 0000000..1f10a9a
--- /dev/null
+++ b/src/lib/python/isc/datasrc/zonewriter_inc.cc
@@ -0,0 +1,103 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+namespace {
+
+const char* const ZoneWriter_doc = "\
+Does an update to a zone.\n\
+\n\
+This represents the work of a (re)load of a zone. The work is divided\n\
+into three stages load(), install() and cleanup(). They should be\n\
+called in this order for the effect to take place.\n\
+\n\
+We divide them so the update of zone data can be done asynchronously,\n\
+in a different thread. The install() operation is the only one that\n\
+needs to be done in a critical section.\n\
+\n\
+This class provides strong exception guarantee for each public method.\n\
+That is, when any of the methods throws, the entire state stays the\n\
+same as before the call.\n\
+\n\
+ZoneWriter objects cannot be constructed directly. They have to be\n\
+obtained by using get_cached_zone_writer() on a ConfigurableClientList.\n\
+\n\
+";
+
+const char* const ZoneWriter_load_doc = "\
+load() -> err_msg\n\
+\n\
+Get the zone data into memory.\n\
+\n\
+This is the part that does the time-consuming loading into the memory.\n\
+This can be run in a separate thread, for example. It has no effect on\n\
+the data actually served, it only prepares them for future use.\n\
+\n\
+This is the first method you should call on the object. Never call it\n\
+multiple times.\n\
+\n\
+If the zone loads successfully, this method returns None. In the case of\n\
+a load error, if the ZoneWriter was constructed with the\n\
+catch_load_error option (see\n\
+ConfigurableClientList.get_cached_zone_writer()), this method will\n\
+return an error message string; if it was created without the\n\
+catch_load_error option, this method will raise a DataSourceError\n\
+exception.\n\
+\n\
+Exceptions:\n\
+ isc.InvalidOperation if called second time.\n\
+ DataSourceError load related error (not thrown if constructed not to).\n\
+\n\
+";
+
+const char* const ZoneWriter_install_doc = "\
+install() -> void\n\
+\n\
+Put the changes to effect.\n\
+\n\
+This replaces the old version of zone with the one previously prepared\n\
+by load(). It takes ownership of the old zone data, if any.\n\
+\n\
+You may call it only after successful load() and at most once. It\n\
+includes the case the writer is constructed to allow load errors,\n\
+and load() encountered and caught a DataSourceError exception.\n\
+In this case this method installs a special empty zone to\n\
+the table.\n\
+\n\
+The operation is expected to be fast and is meant to be used inside a\n\
+critical section.\n\
+\n\
+This may throw in rare cases. If it throws, you still need to call\n\
+cleanup().\n\
+\n\
+Exceptions:\n\
+ isc.InvalidOperation if called without previous load() or for the\n\
+ second time or cleanup() was called already.\n\
+\n\
+";
+
+const char* const ZoneWriter_cleanup_doc = "\
+cleanup() -> void\n\
+\n\
+Clean up resources.\n\
+\n\
+This releases all resources held by owned zone data. That means the\n\
+one loaded by load() in case install() was not called or was not\n\
+successful, or the one replaced in install().\n\
+\n\
+Exceptions:\n\
+ none\n\
+\n\
+";
+
+} // unnamed namespace
diff --git a/src/lib/python/isc/datasrc/zonewriter_python.cc b/src/lib/python/isc/datasrc/zonewriter_python.cc
new file mode 100644
index 0000000..b3783e8
--- /dev/null
+++ b/src/lib/python/isc/datasrc/zonewriter_python.cc
@@ -0,0 +1,253 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// Enable this if you use s# variants with PyArg_ParseTuple(), see
+// http://docs.python.org/py3k/c-api/arg.html#strings-and-buffers
+//#define PY_SSIZE_T_CLEAN
+
+// Python.h needs to be placed at the head of the program file, see:
+// http://docs.python.org/py3k/extending/extending.html#a-simple-example
+#include <Python.h>
+
+#include <string>
+#include <stdexcept>
+
+#include <util/python/pycppwrapper_util.h>
+
+#include <datasrc/memory/zone_writer.h>
+
+#include "zonewriter_python.h"
+#include "datasrc.h"
+
+#include "zonewriter_inc.cc"
+
+using namespace std;
+using namespace isc::util::python;
+using namespace isc::datasrc;
+using namespace isc::datasrc::memory;
+using namespace isc::datasrc::python;
+using namespace isc::datasrc::memory::python;
+
+//
+// ZoneWriter
+//
+
+namespace {
+
+// The s_* Class simply covers one instantiation of the object
+class s_ZoneWriter : public PyObject {
+public:
+ s_ZoneWriter() :
+ cppobj(ConfigurableClientList::ZoneWriterPtr()),
+ base_obj(NULL)
+ {}
+
+ ConfigurableClientList::ZoneWriterPtr cppobj;
+ // This is a reference to a base object; if the object of this class
+ // depends on another object to be in scope during its lifetime,
+ // we use INCREF the base object upon creation, and DECREF it at
+ // the end of the destructor
+ // This is an optional argument to createXXX(). If NULL, it is ignored.
+ PyObject* base_obj;
+};
+
+int
+ZoneWriter_init(PyObject*, PyObject*, PyObject*) {
+ // can't be called directly
+ PyErr_SetString(PyExc_TypeError,
+ "ZoneWriter cannot be constructed directly");
+
+ return (-1);
+}
+
+void
+ZoneWriter_destroy(PyObject* po_self) {
+ s_ZoneWriter* self = static_cast<s_ZoneWriter*>(po_self);
+ // cppobj is a shared ptr, but to make sure things are not destroyed in
+ // the wrong order, we reset it here.
+ self->cppobj.reset();
+ if (self->base_obj != NULL) {
+ Py_DECREF(self->base_obj);
+ }
+ Py_TYPE(self)->tp_free(self);
+}
+
+PyObject*
+ZoneWriter_load(PyObject* po_self, PyObject*) {
+ s_ZoneWriter* self = static_cast<s_ZoneWriter*>(po_self);
+ try {
+ std::string error_msg;
+ self->cppobj->load(&error_msg);
+ if (!error_msg.empty()) {
+ return (Py_BuildValue("s", error_msg.c_str()));
+ }
+ } catch (const std::exception& exc) {
+ PyErr_SetString(getDataSourceException("Error"), exc.what());
+ return (NULL);
+ } catch (...) {
+ PyErr_SetString(getDataSourceException("Error"),
+ "Unknown C++ exception");
+ return (NULL);
+ }
+
+ Py_RETURN_NONE;
+}
+
+PyObject*
+ZoneWriter_install(PyObject* po_self, PyObject*) {
+ s_ZoneWriter* self = static_cast<s_ZoneWriter*>(po_self);
+ try {
+ self->cppobj->install();
+ } catch (const std::exception& exc) {
+ PyErr_SetString(getDataSourceException("Error"), exc.what());
+ return (NULL);
+ } catch (...) {
+ PyErr_SetString(getDataSourceException("Error"),
+ "Unknown C++ exception");
+ return (NULL);
+ }
+
+ Py_RETURN_NONE;
+}
+
+PyObject*
+ZoneWriter_cleanup(PyObject* po_self, PyObject*) {
+ s_ZoneWriter* self = static_cast<s_ZoneWriter*>(po_self);
+ try {
+ self->cppobj->cleanup();
+ } catch (const std::exception& exc) {
+ PyErr_SetString(getDataSourceException("Error"), exc.what());
+ return (NULL);
+ } catch (...) {
+ PyErr_SetString(getDataSourceException("Error"),
+ "Unknown C++ exception");
+ return (NULL);
+ }
+
+ Py_RETURN_NONE;
+}
+
+// This list contains the actual set of functions we have in
+// python. Each entry has
+// 1. Python method name
+// 2. Our static function here
+// 3. Argument type
+// 4. Documentation
+PyMethodDef ZoneWriter_methods[] = {
+ { "load", ZoneWriter_load, METH_NOARGS,
+ ZoneWriter_load_doc },
+ { "install", ZoneWriter_install, METH_NOARGS,
+ ZoneWriter_install_doc },
+ { "cleanup", ZoneWriter_cleanup, METH_NOARGS,
+ ZoneWriter_cleanup_doc },
+ { NULL, NULL, 0, NULL }
+};
+
+} // end of unnamed namespace
+
+namespace isc {
+namespace datasrc {
+namespace memory {
+namespace python {
+// This defines the complete type for reflection in python and
+// parsing of PyObject* to s_ZoneWriter
+// Most of the functions are not actually implemented and NULL here.
+PyTypeObject zonewriter_type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "datasrc.ZoneWriter",
+ sizeof(s_ZoneWriter), // tp_basicsize
+ 0, // tp_itemsize
+ ZoneWriter_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
+ NULL, // tp_str
+ NULL, // tp_getattro
+ NULL, // tp_setattro
+ NULL, // tp_as_buffer
+ Py_TPFLAGS_DEFAULT, // tp_flags
+ ZoneWriter_doc,
+ NULL, // tp_traverse
+ NULL, // tp_clear
+ NULL, // tp_richcompare
+ 0, // tp_weaklistoffset
+ NULL, // tp_iter
+ NULL, // tp_iternext
+ ZoneWriter_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
+ ZoneWriter_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
+};
+
+// Module Initialization, all statics are initialized here
+bool
+initModulePart_ZoneWriter(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(&zonewriter_type) < 0) {
+ return (false);
+ }
+ void* p = &zonewriter_type;
+ if (PyModule_AddObject(mod, "ZoneWriter", static_cast<PyObject*>(p)) < 0) {
+ return (false);
+ }
+ Py_INCREF(&zonewriter_type);
+
+ return (true);
+}
+
+PyObject*
+createZoneWriterObject(ConfigurableClientList::ZoneWriterPtr source,
+ PyObject* base_obj)
+{
+ s_ZoneWriter* py_zf = static_cast<s_ZoneWriter*>(
+ zonewriter_type.tp_alloc(&zonewriter_type, 0));
+ if (py_zf != NULL) {
+ py_zf->cppobj = source;
+ py_zf->base_obj = base_obj;
+ if (base_obj != NULL) {
+ Py_INCREF(base_obj);
+ }
+ }
+ return (py_zf);
+}
+
+} // namespace python
+} // namespace memory
+} // namespace datasrc
+} // namespace isc
diff --git a/src/lib/python/isc/datasrc/zonewriter_python.h b/src/lib/python/isc/datasrc/zonewriter_python.h
new file mode 100644
index 0000000..a7c97b3
--- /dev/null
+++ b/src/lib/python/isc/datasrc/zonewriter_python.h
@@ -0,0 +1,50 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef PYTHON_ZONEWRITER_H
+#define PYTHON_ZONEWRITER_H 1
+
+#include <Python.h>
+#include <datasrc/client_list.h>
+
+namespace isc {
+namespace datasrc {
+namespace memory {
+namespace python {
+
+extern PyTypeObject zonewriter_type;
+
+bool initModulePart_ZoneWriter(PyObject* mod);
+
+/// \brief Create a ZoneWriter python object
+///
+/// \param source The zone writer pointer to wrap
+/// \param base_obj An optional PyObject that this ZoneWriter depends on
+/// Its refcount is increased, and will be decreased when
+/// this zone iterator is destroyed, making sure that the
+/// base object is never destroyed before this ZoneWriter.
+PyObject* createZoneWriterObject(
+ isc::datasrc::ConfigurableClientList::ZoneWriterPtr source,
+ PyObject* base_obj = NULL);
+
+} // namespace python
+} // namespace memory
+} // namespace datasrc
+} // namespace isc
+
+#endif // PYTHON_ZONEWRITER_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/python/isc/ddns/libddns_messages.mes b/src/lib/python/isc/ddns/libddns_messages.mes
index ab982ad..86af4c3 100644
--- a/src/lib/python/isc/ddns/libddns_messages.mes
+++ b/src/lib/python/isc/ddns/libddns_messages.mes
@@ -212,3 +212,13 @@ To make sure DDNS service is not interrupted, this problem is caught instead
of reraised; The update is aborted, and a SERVFAIL is sent back to the client.
This is most probably a bug in the DDNS code, but *could* be caused by
the data source.
+
+% LIBDDNS_ZONE_INVALID_ERROR Newly received zone data %1/%2 fails validation: %3
+The zone data was received successfully, but the zone when updated with
+this zone data fails validation.
+
+% LIBDDNS_ZONE_INVALID_WARN Newly received zone data %1/%2 has a problem: %3
+The zone data was received successfully, but when checking the zone when
+updated with this zone data, it was discovered there's some issue with
+it. It might be correct, but it should be checked and possibly
+fixed. The problem does not stop the zone from being used.
diff --git a/src/lib/python/isc/ddns/session.py b/src/lib/python/isc/ddns/session.py
index febdfdc..6f2b7cf 100644
--- a/src/lib/python/isc/ddns/session.py
+++ b/src/lib/python/isc/ddns/session.py
@@ -812,6 +812,20 @@ class UpdateSession:
self.__diff.delete_data(old_soa)
self.__diff.add_data(new_soa)
+ def __validate_error(self, reason):
+ '''
+ Used as error callback below.
+ '''
+ logger.error(LIBDDNS_ZONE_INVALID_ERROR, self.__zname, self.__zclass,
+ reason)
+
+ def __validate_warning(self, reason):
+ '''
+ Used as warning callback below.
+ '''
+ logger.warn(LIBDDNS_ZONE_INVALID_WARN, self.__zname, self.__zclass,
+ reason)
+
def __do_update(self):
'''Scan, check, and execute the Update section in the
DDNS Update message.
@@ -849,8 +863,17 @@ class UpdateSession:
elif rrset.get_class() == RRClass.NONE:
self.__do_update_delete_rrs_from_rrset(rrset)
+ if not check_zone(self.__zname, self.__zclass,
+ self.__diff.get_rrset_collection(),
+ (self.__validate_error, self.__validate_warning)):
+ raise UpdateError('Validation of the new zone failed',
+ self.__zname, self.__zclass, Rcode.REFUSED)
self.__diff.commit()
return Rcode.NOERROR
+ except UpdateError:
+ # Propagate UpdateError exceptions (don't catch them in the
+ # blocks below)
+ raise
except isc.datasrc.Error as dse:
logger.info(LIBDDNS_UPDATE_DATASRC_COMMIT_FAILED, dse)
return Rcode.SERVFAIL
diff --git a/src/lib/python/isc/ddns/tests/session_tests.py b/src/lib/python/isc/ddns/tests/session_tests.py
index 4880d72..49bf672 100644
--- a/src/lib/python/isc/ddns/tests/session_tests.py
+++ b/src/lib/python/isc/ddns/tests/session_tests.py
@@ -1481,6 +1481,16 @@ class SessionTest(SessionTestBase):
self.assertEqual(Rcode.SERVFAIL.to_text(),
self._session._UpdateSession__do_update().to_text())
+ def test_check_zone_failure(self):
+ # ns3.example.org. is an NS and should not have a CNAME
+ # record. This would cause check_zone() to fail.
+ self.__initialize_update_rrsets()
+ new_cname = create_rrset("ns3.example.org", TEST_RRCLASS,
+ RRType.CNAME, 3600,
+ [ "cname.example.org." ])
+
+ self.check_full_handle_result(Rcode.REFUSED, [ new_cname ])
+
class SessionACLTest(SessionTestBase):
'''ACL related tests for update session.'''
def test_update_acl_check(self):
diff --git a/src/lib/python/isc/log_messages/Makefile.am b/src/lib/python/isc/log_messages/Makefile.am
index c8b9c7a..caf1e3b 100644
--- a/src/lib/python/isc/log_messages/Makefile.am
+++ b/src/lib/python/isc/log_messages/Makefile.am
@@ -4,6 +4,7 @@ EXTRA_DIST = __init__.py
EXTRA_DIST += init_messages.py
EXTRA_DIST += cmdctl_messages.py
EXTRA_DIST += ddns_messages.py
+EXTRA_DIST += memmgr_messages.py
EXTRA_DIST += stats_messages.py
EXTRA_DIST += stats_httpd_messages.py
EXTRA_DIST += xfrin_messages.py
@@ -24,6 +25,7 @@ CLEANFILES = __init__.pyc
CLEANFILES += init_messages.pyc
CLEANFILES += cmdctl_messages.pyc
CLEANFILES += ddns_messages.pyc
+CLEANFILES += memmgr_messages.pyc
CLEANFILES += stats_messages.pyc
CLEANFILES += stats_httpd_messages.pyc
CLEANFILES += xfrin_messages.pyc
diff --git a/src/lib/python/isc/log_messages/memmgr_messages.py b/src/lib/python/isc/log_messages/memmgr_messages.py
new file mode 100644
index 0000000..8c59cc9
--- /dev/null
+++ b/src/lib/python/isc/log_messages/memmgr_messages.py
@@ -0,0 +1 @@
+from work.memmgr_messages import *
diff --git a/src/lib/python/isc/memmgr/Makefile.am b/src/lib/python/isc/memmgr/Makefile.am
new file mode 100644
index 0000000..f00dba6
--- /dev/null
+++ b/src/lib/python/isc/memmgr/Makefile.am
@@ -0,0 +1,10 @@
+SUBDIRS = . tests
+
+python_PYTHON = __init__.py builder.py datasrc_info.py
+
+pythondir = $(pyexecdir)/isc/memmgr
+
+CLEANDIRS = __pycache__
+
+clean-local:
+ rm -rf $(CLEANDIRS)
diff --git a/src/lib/python/isc/memmgr/__init__.py b/src/lib/python/isc/memmgr/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/lib/python/isc/memmgr/builder.py b/src/lib/python/isc/memmgr/builder.py
new file mode 100644
index 0000000..5b4eed9
--- /dev/null
+++ b/src/lib/python/isc/memmgr/builder.py
@@ -0,0 +1,99 @@
+# Copyright (C) 2013 Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+class MemorySegmentBuilder:
+ """The builder runs in a different thread in the memory manager. It
+ waits for commands from the memory manager, and then executes them
+ in the given order sequentially.
+ """
+
+ def __init__(self, sock, cv, command_queue, response_queue):
+ """ The constructor takes the following arguments:
+
+ sock: A socket using which this builder object notifies the
+ main thread that it has a response waiting for it.
+
+ cv: A condition variable object that is used by the main
+ thread to tell this builder object that new commands are
+ available to it. Note that this is also used for
+ synchronizing access to the queues, so code that uses
+ MemorySegmentBuilder must use this condition variable's
+ lock object to synchronize its access to the queues.
+
+ command_queue: A list of commands sent by the main thread to
+ this object. Commands should be executed
+ sequentially in the given order by this
+ object.
+
+ response_queue: A list of responses sent by this object to
+ the main thread. The format of this is
+ currently not strictly defined. Future
+ tickets will be able to define it based on
+ how it's used.
+ """
+
+ self._sock = sock
+ self._cv = cv
+ self._command_queue = command_queue
+ self._response_queue = response_queue
+ self._shutdown = False
+
+ def run(self):
+ """ This is the method invoked when the builder thread is
+ started. In this thread, be careful when modifying
+ variables passed-by-reference in the constructor. If they
+ are reassigned, they will not refer to the main thread's
+ objects any longer. Any use of command_queue and
+ response_queue must be synchronized by acquiring the lock in
+ the condition variable. This method must normally terminate
+ only when the 'shutdown' command is sent to it.
+ """
+
+ # Acquire the condition variable while running the loop.
+ with self._cv:
+ while not self._shutdown:
+ while len(self._command_queue) == 0:
+ self._cv.wait()
+ # Move the queue content to a local queue. Be careful of
+ # not making assignments to reference variables.
+ local_command_queue = self._command_queue[:]
+ del self._command_queue[:]
+
+ # Run commands passed in the command queue sequentially
+ # in the given order. For now, it only supports the
+ # "shutdown" command, which just exits the thread.
+ for command in local_command_queue:
+ if command == 'shutdown':
+ self._shutdown = True
+ # When the shutdown command is received, we do
+ # not process any further commands.
+ break
+ else:
+ # A bad command was received. Raising an
+ # exception is not useful in this case as we are
+ # likely running in a different thread from the
+ # main thread which would need to be
+ # notified. Instead return this in the response
+ # queue.
+ self._response_queue.append(('bad_command',))
+ self._shutdown = True
+ break
+
+ # Notify (any main thread) on the socket about a
+ # response. Otherwise, the main thread may wait in its
+ # loop without knowing there was a problem.
+ if len(self._response_queue) > 0:
+ while self._sock.send(b'x') != 1:
+ continue
diff --git a/src/lib/python/isc/memmgr/datasrc_info.py b/src/lib/python/isc/memmgr/datasrc_info.py
new file mode 100644
index 0000000..86f2d6a
--- /dev/null
+++ b/src/lib/python/isc/memmgr/datasrc_info.py
@@ -0,0 +1,220 @@
+# Copyright (C) 2013 Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+import os
+
+class SegmentInfoError(Exception):
+ """An exception raised for general errors in the SegmentInfo class."""
+ pass
+
+class SegmentInfo:
+ """A base class to maintain information about memory segments.
+
+ An instance of this class corresponds to the memory segment used
+ for in-memory cache of a specific single data source. It manages
+ information to set/reset the latest effective segment (such as
+ path to a memory mapped file) and sets of other modules using the
+ segment.
+
+ Since there can be several different types of memory segments,
+ the top level class provides abstract interfaces independent from
+ segment-type specific details. Such details are expected to be
+ delegated to subclasses corresponding to specific types of segments.
+
+ The implementation is still incomplete. It will have more attributes
+ such as a set of current readers, methods for adding or deleting
+ the readers. These will probably be implemented in this base class
+ as they will be independent from segment-type specific details.
+
+ """
+ # Common constants of user type: reader or writer
+ READER = 0
+ WRITER = 1
+
+ def create(type, genid, rrclass, datasrc_name, mgr_config):
+ """Factory of specific SegmentInfo subclass instance based on the
+ segment type.
+
+ This is specifically for the memmgr, and segments that are not of
+ its interest will be ignored. This method returns None in these
+ cases. At least 'local' type segments will be ignored this way.
+
+ If an unknown type of segment is specified, this method throws an
+ SegmentInfoError exception. The assumption is that this method
+ is called after the corresponding data source configuration has been
+ validated, at which point such unknown segments should have been
+ rejected.
+
+ Parameters:
+ type (str or None): The type of memory segment; None if the segment
+ isn't used.
+ genid (int): The generation ID of the corresponding data source
+ configuration.
+ rrclass (isc.dns.RRClass): The RR class of the data source.
+ datasrc_name (str): The name of the data source.
+ mgr_config (dict): memmgr configuration related to memory segment
+ information. The content of the dict is type specific; each
+ subclass is expected to know which key is necessary and the
+ semantics of its value.
+
+ """
+ if type == 'mapped':
+ return MappedSegmentInfo(genid, rrclass, datasrc_name, mgr_config)
+ elif type is None or type == 'local':
+ return None
+ raise SegmentInfoError('unknown segment type to create info: ' + type)
+
+ def get_reset_param(self, user_type):
+ """Return parameters to reset the zone table memory segment.
+
+ It returns a dict object that consists of parameter mappings
+ (string to parameter value) for the specified type of user to
+ reset a zone table segment with
+ isc.datasrc.ConfigurableClientList.reset_memory_segment(). It
+ can also be passed to the user module as part of command
+ parameters. Note that reset_memory_segment() takes a json
+ expression encoded as a string, so the return value of this method
+ will have to be converted with json.dumps().
+
+ Each subclass must implement this method.
+
+ Parameter:
+ user_type (READER or WRITER): specifies the type of user to reset
+ the segment.
+
+ """
+ raise SegmentInfoError('get_reset_param is not implemented')
+
+ def switch_versions(self):
+ """Switch internal information for the reader segment and writer
+ segment.
+
+ This method is expected to be called when the writer on one version
+ of memory segment completes updates and the memmgr is going to
+ have readers switch to the updated version. Details of the
+ information to be switched would depend on the segment type, and
+ are delegated to the specific subclass.
+
+ Each subclass must implement this method.
+
+ """
+ raise SegmentInfoError('switch_versions is not implemented')
+
+class MappedSegmentInfo(SegmentInfo):
+ """SegmentInfo implementation of 'mapped' type memory segments.
+
+ It maintains paths to mapped files both readers and the writer.
+
+ While objets of this class are expected to be shared by multiple
+ threads, it assumes operations are serialized through message passing,
+ so access to this class itself is not protected by any explicit
+ synchronization mechanism.
+
+ """
+ def __init__(self, genid, rrclass, datasrc_name, mgr_config):
+ super().__init__()
+
+ # Something like "/var/bind10/zone-IN-1-sqlite3-mapped"
+ self.__mapped_file_base = mgr_config['mapped_file_dir'] + os.sep + \
+ 'zone-' + str(rrclass) + '-' + str(genid) + '-' + datasrc_name + \
+ '-mapped'
+
+ # Current versions (suffix of the mapped files) for readers and the
+ # writer. In this initial implementation we assume that all possible
+ # readers are waiting for a new version (not using pre-existing one),
+ # and the writer is expected to build a new segment as version "0".
+ self.__reader_ver = None # => 0 => 1 => 0 => 1 ...
+ self.__writer_ver = 0 # => 1 => 0 => 1 => 0 ...
+
+ def get_reset_param(self, user_type):
+ ver = self.__reader_ver if user_type == self.READER else \
+ self.__writer_ver
+ if ver is None:
+ return None
+ mapped_file = self.__mapped_file_base + '.' + str(ver)
+ return {'mapped-file': mapped_file}
+
+ def switch_versions(self):
+ # Swith the versions as noted in the constructor.
+ self.__writer_ver = 1 - self.__writer_ver
+
+ if self.__reader_ver is None:
+ self.__reader_ver = 0
+ else:
+ self.__reader_ver = 1 - self.__reader_ver
+
+ # Versions should be different
+ assert(self.__reader_ver != self.__writer_ver)
+
+class DataSrcInfo:
+ """A container for datasrc.ConfigurableClientLists and associated
+ in-memory segment information corresponding to a given geration of
+ configuration.
+
+ This class maintains all datasrc.ConfigurableClientLists in a form
+ of dict from RR classes corresponding to a given single generation
+ of data source configuration, along with sets of memory segment
+ information that needs to be used by memmgr.
+
+ Once constructed, mappings do not change (different generation of
+ configuration will result in another DataSrcInfo objects). Status
+ of SegmentInfo objects stored in this class object may change over time.
+
+ Attributes: these are all constant and read only. For dict objects,
+ mapping shouldn't be modified either.
+ gen_id (int): The corresponding configuration generation ID.
+ clients_map (dict, isc.dns.RRClass=>isc.datasrc.ConfigurableClientList):
+ The configured client lists for all RR classes of the generation.
+ segment_info_map (dict, (isc.dns.RRClass, str)=>SegmentInfo):
+ SegmentInfo objects managed in the DataSrcInfo objects. Can be
+ retrieved by (RRClass, <data source name>).
+
+ """
+ def __init__(self, genid, clients_map, mgr_config):
+ """Constructor.
+
+ As long as given parameters are of valid type and have been
+ validated, this constructor shouldn't raise an exception.
+
+ Parameters:
+ genid (int): see gen_id attribute
+ clients_map (dict): see clients_map attribute
+ mgr_config (dict, str=>key-dependent-value): A copy of the current
+ memmgr configuration, in case it's needed to construct a specific
+ type of SegmentInfo. The specific SegmentInfo class is expected
+ to know the key-value mappings that it needs.
+
+ """
+ self.__gen_id = genid
+ self.__clients_map = clients_map
+ self.__segment_info_map = {}
+ for (rrclass, client_list) in clients_map.items():
+ for (name, sgmt_type, _) in client_list.get_status():
+ sgmt_info = SegmentInfo.create(sgmt_type, genid, rrclass, name,
+ mgr_config)
+ if sgmt_info is not None:
+ self.__segment_info_map[(rrclass, name)] = sgmt_info
+
+ @property
+ def gen_id(self):
+ return self.__gen_id
+
+ @property
+ def clients_map(self):
+ return self.__clients_map
+
+ @property
+ def segment_info_map(self):
+ return self.__segment_info_map
diff --git a/src/lib/python/isc/memmgr/tests/Makefile.am b/src/lib/python/isc/memmgr/tests/Makefile.am
new file mode 100644
index 0000000..7a85083
--- /dev/null
+++ b/src/lib/python/isc/memmgr/tests/Makefile.am
@@ -0,0 +1,34 @@
+PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
+PYTESTS = builder_tests.py datasrc_info_tests.py
+EXTRA_DIST = $(PYTESTS)
+
+# If necessary (rare cases), explicitly specify paths to dynamic libraries
+# required by loadable python modules.
+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/dns/python/.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/util/threads/.libs:$(abs_top_builddir)/src/lib/acl/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+endif
+
+# Some tests require backend shared memory support
+if USE_SHARED_MEMORY
+HAVE_SHARED_MEMORY=yes
+else
+HAVE_SHARED_MEMORY=no
+endif
+
+# test using command-line arguments, so use check-local target instead of TESTS
+# B10_FROM_BUILD is necessary to load data source backend from the build tree.
+check-local:
+if ENABLE_PYTHON_COVERAGE
+ touch $(abs_top_srcdir)/.coverage
+ rm -f .coverage
+ ${LN_S} $(abs_top_srcdir)/.coverage .coverage
+endif
+ for pytest in $(PYTESTS) ; do \
+ echo Running test: $$pytest ; \
+ $(LIBRARY_PATH_PLACEHOLDER) \
+ TESTDATA_PATH=$(builddir) \
+ B10_FROM_BUILD=$(abs_top_builddir) \
+ HAVE_SHARED_MEMORY=$(HAVE_SHARED_MEMORY) \
+ PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_builddir)/src/lib/dns/python/.libs \
+ $(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
+ done
diff --git a/src/lib/python/isc/memmgr/tests/builder_tests.py b/src/lib/python/isc/memmgr/tests/builder_tests.py
new file mode 100644
index 0000000..328fd74
--- /dev/null
+++ b/src/lib/python/isc/memmgr/tests/builder_tests.py
@@ -0,0 +1,121 @@
+# Copyright (C) 2013 Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+import unittest
+import socket
+import select
+import threading
+
+import isc.log
+from isc.memmgr.builder import *
+
+class TestMemorySegmentBuilder(unittest.TestCase):
+ def _create_builder_thread(self):
+ (self._master_sock, self._builder_sock) = \
+ socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)
+
+ self._builder_command_queue = []
+ self._builder_response_queue = []
+
+ self._builder_cv = threading.Condition()
+
+ self._builder = MemorySegmentBuilder(self._builder_sock,
+ self._builder_cv,
+ self._builder_command_queue,
+ self._builder_response_queue)
+ self._builder_thread = threading.Thread(target=self._builder.run)
+
+ def setUp(self):
+ self._create_builder_thread()
+
+ def tearDown(self):
+ # It's the tests' responsibility to stop and join the builder
+ # thread if they start it.
+ self.assertFalse(self._builder_thread.isAlive())
+
+ self._master_sock.close()
+ self._builder_sock.close()
+
+ def test_bad_command(self):
+ """Tests what happens when a bad command is passed to the
+ MemorySegmentBuilder.
+ """
+
+ self._builder_thread.start()
+
+ # Now that the builder thread is running, send it a bad
+ # command. The thread should exit its main loop and be joinable.
+ with self._builder_cv:
+ self._builder_command_queue.append('bad_command')
+ self._builder_cv.notify_all()
+
+ # Wait 5 seconds to receive a notification on the socket from
+ # the builder.
+ (reads, _, _) = select.select([self._master_sock], [], [], 5)
+ self.assertTrue(self._master_sock in reads)
+
+ # Reading 1 byte should not block us here, especially as the
+ # socket is ready to read. It's a hack, but this is just a
+ # testcase.
+ got = self._master_sock.recv(1)
+ self.assertEqual(got, b'x')
+
+ # Wait 5 seconds at most for the main loop of the builder to
+ # exit.
+ self._builder_thread.join(5)
+ self.assertFalse(self._builder_thread.isAlive())
+
+ # The command queue must be cleared, and the response queue must
+ # contain a response that a bad command was sent. The thread is
+ # no longer running, so we can use the queues without a lock.
+ self.assertEqual(len(self._builder_command_queue), 0)
+ self.assertEqual(len(self._builder_response_queue), 1)
+
+ response = self._builder_response_queue[0]
+ self.assertTrue(isinstance(response, tuple))
+ self.assertTupleEqual(response, ('bad_command',))
+ del self._builder_response_queue[:]
+
+ def test_shutdown(self):
+ """Tests that shutdown command exits the MemorySegmentBuilder
+ loop.
+ """
+
+ self._builder_thread.start()
+
+ # Now that the builder thread is running, send it the shutdown
+ # command. The thread should exit its main loop and be joinable.
+ with self._builder_cv:
+ self._builder_command_queue.append('shutdown')
+ # Commands after 'shutdown' must be ignored.
+ self._builder_command_queue.append('bad_command_1')
+ self._builder_command_queue.append('bad_command_2')
+ self._builder_cv.notify_all()
+
+ # Wait 5 seconds at most for the main loop of the builder to
+ # exit.
+ self._builder_thread.join(5)
+ self.assertFalse(self._builder_thread.isAlive())
+
+ # The command queue must be cleared, and the response queue must
+ # be untouched (we don't use it in this test). The thread is no
+ # longer running, so we can use the queues without a lock.
+ self.assertEqual(len(self._builder_command_queue), 0)
+ self.assertEqual(len(self._builder_response_queue), 0)
+
+if __name__ == "__main__":
+ isc.log.init("bind10-test")
+ isc.log.resetUnitTestRootLogger()
+ unittest.main()
diff --git a/src/lib/python/isc/memmgr/tests/datasrc_info_tests.py b/src/lib/python/isc/memmgr/tests/datasrc_info_tests.py
new file mode 100644
index 0000000..cc6307f
--- /dev/null
+++ b/src/lib/python/isc/memmgr/tests/datasrc_info_tests.py
@@ -0,0 +1,192 @@
+# Copyright (C) 2013 Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+import os
+import unittest
+
+from isc.dns import *
+import isc.config
+import isc.datasrc
+import isc.log
+from isc.server_common.datasrc_clients_mgr import DataSrcClientsMgr
+from isc.memmgr.datasrc_info import *
+
+# Defined for easier tests with DataSrcClientsMgr.reconfigure(), which
+# only needs get_value() method
+class MockConfigData:
+ def __init__(self, data):
+ self.__data = data
+
+ def get_value(self, identifier):
+ return self.__data[identifier], False
+
+class TestSegmentInfo(unittest.TestCase):
+ def setUp(self):
+ self.__mapped_file_dir = os.environ['TESTDATA_PATH']
+ self.__sgmt_info = SegmentInfo.create('mapped', 0, RRClass.IN,
+ 'sqlite3',
+ {'mapped_file_dir':
+ self.__mapped_file_dir})
+
+ def __check_sgmt_reset_param(self, user_type, expected_ver):
+ """Common check on the return value of get_reset_param() for
+ MappedSegmentInfo.
+
+ Unless it's expected to return None, it should be a map that
+ maps "mapped-file" to the expected version of mapped-file.
+
+ """
+ if expected_ver is None:
+ self.assertIsNone(self.__sgmt_info.get_reset_param(user_type))
+ return
+ param = self.__sgmt_info.get_reset_param(user_type)
+ self.assertEqual(self.__mapped_file_dir +
+ '/zone-IN-0-sqlite3-mapped.' + str(expected_ver),
+ param['mapped-file'])
+
+ def test_initial_params(self):
+ self.__check_sgmt_reset_param(SegmentInfo.WRITER, 0)
+ self.__check_sgmt_reset_param(SegmentInfo.READER, None)
+
+ def test_swtich_versions(self):
+ self.__sgmt_info.switch_versions()
+ self.__check_sgmt_reset_param(SegmentInfo.WRITER, 1)
+ self.__check_sgmt_reset_param(SegmentInfo.READER, 0)
+
+ self.__sgmt_info.switch_versions()
+ self.__check_sgmt_reset_param(SegmentInfo.WRITER, 0)
+ self.__check_sgmt_reset_param(SegmentInfo.READER, 1)
+
+ def test_init_others(self):
+ # For local type of segment, information isn't needed and won't be
+ # created.
+ self.assertIsNone(SegmentInfo.create('local', 0, RRClass.IN,
+ 'sqlite3', {}))
+
+ # Unknown type of segment will result in an exception.
+ self.assertRaises(SegmentInfoError, SegmentInfo.create, 'unknown', 0,
+ RRClass.IN, 'sqlite3', {})
+
+ def test_missing_methods(self):
+ # Bad subclass of SegmentInfo that doesn't implement mandatory methods.
+ class TestSegmentInfo(SegmentInfo):
+ pass
+
+ self.assertRaises(SegmentInfoError,
+ TestSegmentInfo().get_reset_param,
+ SegmentInfo.WRITER)
+ self.assertRaises(SegmentInfoError, TestSegmentInfo().switch_versions)
+
+class MockClientList:
+ """A mock ConfigurableClientList class.
+
+ Just providing minimal shortcut interfaces needed for DataSrcInfo class.
+
+ """
+ def __init__(self, status_list):
+ self.__status_list = status_list
+
+ def get_status(self):
+ return self.__status_list
+
+class TestDataSrcInfo(unittest.TestCase):
+ def setUp(self):
+ self.__mapped_file_dir = os.environ['TESTDATA_PATH']
+ self.__mgr_config = {'mapped_file_dir': self.__mapped_file_dir}
+ self.__sqlite3_dbfile = os.environ['TESTDATA_PATH'] + '/' + 'zone.db'
+ self.__clients_map = {
+ # mixture of 'local' and 'mapped' and 'unused' (type =None)
+ # segments
+ RRClass.IN: MockClientList([('datasrc1', 'local', None),
+ ('datasrc2', 'mapped', None),
+ ('datasrc3', None, None)]),
+ RRClass.CH: MockClientList([('datasrc2', 'mapped', None),
+ ('datasrc1', 'local', None)]) }
+
+ def tearDown(self):
+ if os.path.exists(self.__sqlite3_dbfile):
+ os.unlink(self.__sqlite3_dbfile)
+
+ def __check_sgmt_reset_param(self, sgmt_info, writer_file):
+ # Check if the initial state of (mapped) segment info object has
+ # expected values.
+ self.assertIsNone(sgmt_info.get_reset_param(SegmentInfo.READER))
+ param = sgmt_info.get_reset_param(SegmentInfo.WRITER)
+ self.assertEqual(writer_file, param['mapped-file'])
+
+ def test_init(self):
+ """Check basic scenarios of constructing DataSrcInfo."""
+
+ # This checks that all data sources of all RR classes are covered,
+ # "local" segments are ignored, info objects for "mapped" segments
+ # are created and stored in segment_info_map.
+ datasrc_info = DataSrcInfo(42, self.__clients_map, self.__mgr_config)
+ self.assertEqual(42, datasrc_info.gen_id)
+ self.assertEqual(self.__clients_map, datasrc_info.clients_map)
+ self.assertEqual(2, len(datasrc_info.segment_info_map))
+ sgmt_info = datasrc_info.segment_info_map[(RRClass.IN, 'datasrc2')]
+ self.__check_sgmt_reset_param(sgmt_info, self.__mapped_file_dir +
+ '/zone-IN-42-datasrc2-mapped.0')
+ sgmt_info = datasrc_info.segment_info_map[(RRClass.CH, 'datasrc2')]
+ self.__check_sgmt_reset_param(sgmt_info, self.__mapped_file_dir +
+ '/zone-CH-42-datasrc2-mapped.0')
+
+ # A case where clist.get_status() returns an empty list; shouldn't
+ # cause disruption
+ self.__clients_map = { RRClass.IN: MockClientList([])}
+ datasrc_info = DataSrcInfo(42, self.__clients_map, self.__mgr_config)
+ self.assertEqual(42, datasrc_info.gen_id)
+ self.assertEqual(0, len(datasrc_info.segment_info_map))
+
+ # A case where clients_map is empty; shouldn't cause disruption
+ self.__clients_map = {}
+ datasrc_info = DataSrcInfo(42, self.__clients_map, self.__mgr_config)
+ self.assertEqual(42, datasrc_info.gen_id)
+ self.assertEqual(0, len(datasrc_info.segment_info_map))
+
+ # This test uses real "mmaped" segment and doesn't work without shared
+ # memory support.
+ @unittest.skipIf(os.environ['HAVE_SHARED_MEMORY'] != 'yes',
+ 'shared memory support is not available')
+ def test_production(self):
+ """Check the behavior closer to a production environment.
+
+ Instead of using a mock classes, just for confirming we didn't miss
+ something.
+
+ """
+ cfg_data = MockConfigData(
+ {"classes":
+ {"IN": [{"type": "sqlite3", "cache-enable": True,
+ "cache-type": "mapped", "cache-zones": [],
+ "params": {"database_file": self.__sqlite3_dbfile}}]
+ }
+ })
+ cmgr = DataSrcClientsMgr(use_cache=True)
+ cmgr.reconfigure({}, cfg_data)
+
+ genid, clients_map = cmgr.get_clients_map()
+ datasrc_info = DataSrcInfo(genid, clients_map, self.__mgr_config)
+
+ self.assertEqual(1, datasrc_info.gen_id)
+ self.assertEqual(clients_map, datasrc_info.clients_map)
+ self.assertEqual(1, len(datasrc_info.segment_info_map))
+ sgmt_info = datasrc_info.segment_info_map[(RRClass.IN, 'sqlite3')]
+ self.assertIsNone(sgmt_info.get_reset_param(SegmentInfo.READER))
+
+if __name__ == "__main__":
+ isc.log.init("bind10-test")
+ isc.log.resetUnitTestRootLogger()
+ unittest.main()
diff --git a/src/lib/python/isc/server_common/Makefile.am b/src/lib/python/isc/server_common/Makefile.am
index 596d6cd..54b2885 100644
--- a/src/lib/python/isc/server_common/Makefile.am
+++ b/src/lib/python/isc/server_common/Makefile.am
@@ -1,12 +1,14 @@
SUBDIRS = tests
python_PYTHON = __init__.py tsig_keyring.py auth_command.py dns_tcp.py
-python_PYTHON += datasrc_clients_mgr.py
+python_PYTHON += datasrc_clients_mgr.py bind10_server.py
python_PYTHON += logger.py
pythondir = $(pyexecdir)/isc/server_common
BUILT_SOURCES = $(PYTHON_LOGMSGPKG_DIR)/work/server_common_messages.py
+BUILT_SOURCES += bind10_server.py
+
nodist_pylogmessage_PYTHON = $(PYTHON_LOGMSGPKG_DIR)/work/server_common_messages.py
pylogmessagedir = $(pyexecdir)/isc/log_messages/
diff --git a/src/lib/python/isc/server_common/bind10_server.py.in b/src/lib/python/isc/server_common/bind10_server.py.in
new file mode 100644
index 0000000..5bbd341
--- /dev/null
+++ b/src/lib/python/isc/server_common/bind10_server.py.in
@@ -0,0 +1,275 @@
+# Copyright (C) 2013 Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+import errno
+import os
+import select
+import signal
+
+import isc.log
+import isc.config
+from isc.server_common.logger import logger
+from isc.log_messages.server_common_messages import *
+
+class BIND10ServerFatal(Exception):
+ """Exception raised when the server program encounters a fatal error."""
+ pass
+
+class BIND10Server:
+ """A mixin class for common BIND 10 server implementations.
+
+ It takes care of common initialization such as setting up a module CC
+ session, and running main event loop. It also handles the "shutdown"
+ command for its normal behavior. If a specific server class wants to
+ handle this command differently or if it does not support the command,
+ it should override the _command_handler method.
+
+ Specific modules can define module-specific class inheriting this class,
+ instantiate it, and call run() with the module name.
+
+ Methods to be implemented in the actual class:
+ _config_handler: config handler method as specified in ModuleCCSession.
+ must be exception free; errors should be signaled by
+ the return value.
+ _mod_command_handler: can be optionally defined to handle
+ module-specific commands. should conform to
+ command handlers as specified in ModuleCCSession.
+ must be exception free; errors should be signaled
+ by the return value.
+ _setup_module: can be optionally defined for module-specific
+ initialization. This is called after the module CC
+ session has started, and can be used for registering
+ interest on remote modules, etc. If it raises an
+ exception, the server will be immediately stopped.
+ Parameter: None, Return: None
+ _shutdown_module: can be optionally defined for module-specific
+ finalization. This is called right before the
+ module CC session is stopped. If it raises an
+ exception, the server will be immediately
+ stopped.
+ Parameter: None, Return: None
+
+ """
+ # Will be set to True when the server should stop and shut down.
+ # Can be read via accessor method 'shutdown', mainly for testing.
+ __shutdown = False
+
+ # ModuleCCSession used in the server. Defined as 'protectd' so tests
+ # can refer to it directly; others should access it via the
+ # 'mod_ccsession' accessor.
+ _mod_cc = None
+
+ # Will be set in run(). Define a tentative value so other methods can
+ # be tested directly.
+ __module_name = ''
+
+ # Basically constant, but allow tests to override it.
+ _select_fn = select.select
+
+ def __init__(self):
+ self._read_callbacks = {}
+ self._write_callbacks = {}
+ self._error_callbacks = {}
+
+ @property
+ def shutdown(self):
+ return self.__shutdown
+
+ @property
+ def mod_ccsession(self):
+ return self._mod_cc
+
+ def _setup_ccsession(self):
+ """Create and start module CC session.
+
+ This is essentially private, but allows tests to override it.
+
+ """
+ self._mod_cc = isc.config.ModuleCCSession(
+ self._get_specfile_location(), self._config_handler,
+ self._command_handler)
+ self._mod_cc.start()
+
+ def _get_specfile_location(self):
+ """Return the path to the module spec file following common convetion.
+
+ This method generates the path commonly used by most BIND 10 modules,
+ determined by a well known prefix and the module name.
+
+ A specific module can override this method if it uses a different
+ path for the spec file.
+
+ """
+ # First check if it's running under an 'in-source' environment,
+ # then try commonly used paths and file names. If found, use it.
+ for ev in ['B10_FROM_SOURCE', 'B10_FROM_BUILD']:
+ if ev in os.environ:
+ specfile = os.environ[ev] + '/src/bin/' + self.__module_name +\
+ '/' + self.__module_name + '.spec'
+ if os.path.exists(specfile):
+ return specfile
+ # Otherwise, just use the installed path, whether or not it really
+ # exists; leave error handling to the caller.
+ specfile_path = '${datarootdir}/bind10'\
+ .replace('${datarootdir}', '${prefix}/share')\
+ .replace('${prefix}', '/Users/jinmei/opt')
+ return specfile_path + '/' + self.__module_name + '.spec'
+
+ def _trigger_shutdown(self):
+ """Initiate a shutdown sequence.
+
+ This method is expected to be called in various ways including
+ in the middle of a signal handler, and is designed to be as simple
+ as possible to minimize side effects. Actual shutdown will take
+ place in a normal control flow.
+
+ This method is defined as 'protected'. User classes can use it
+ to shut down the server.
+
+ """
+ self.__shutdown = True
+
+ def _run_internal(self):
+ """Main event loop.
+
+ This method is essentially private, but allows tests to override it.
+
+ """
+
+ logger.info(PYSERVER_COMMON_SERVER_STARTED, self.__module_name)
+ cc_fileno = self._mod_cc.get_socket().fileno()
+ while not self.__shutdown:
+ try:
+ read_fds = list(self._read_callbacks.keys())
+ read_fds.append(cc_fileno)
+ write_fds = list(self._write_callbacks.keys())
+ error_fds = list(self._error_callbacks.keys())
+
+ (reads, writes, errors) = \
+ self._select_fn(read_fds, write_fds, error_fds)
+ except select.error as ex:
+ # ignore intterruption by signal; regard other select errors
+ # fatal.
+ if ex.args[0] == errno.EINTR:
+ continue
+ else:
+ raise
+
+ for fileno in reads:
+ if fileno in self._read_callbacks:
+ for callback in self._read_callbacks[fileno]:
+ callback()
+
+ for fileno in writes:
+ if fileno in self._write_callbacks:
+ for callback in self._write_callbacks[fileno]:
+ callback()
+
+ for fileno in errors:
+ if fileno in self._error_callbacks:
+ for callback in self._error_callbacks[fileno]:
+ callback()
+
+ if cc_fileno in reads:
+ # this shouldn't raise an exception (if it does, we'll
+ # propagate it)
+ self._mod_cc.check_command(True)
+
+ self._shutdown_module()
+ self._mod_cc.send_stopping()
+
+ def _command_handler(self, cmd, args):
+ logger.debug(logger.DBGLVL_TRACE_BASIC, PYSERVER_COMMON_COMMAND,
+ self.__module_name, cmd)
+ if cmd == 'shutdown':
+ self._trigger_shutdown()
+ answer = isc.config.create_answer(0)
+ else:
+ answer = self._mod_command_handler(cmd, args)
+
+ return answer
+
+ def _mod_command_handler(self, cmd, args):
+ """The default implementation of the module specific command handler"""
+ return isc.config.create_answer(1, "Unknown command: " + str(cmd))
+
+ def _setup_module(self):
+ """The default implementation of the module specific initialization"""
+ pass
+
+ def _shutdown_module(self):
+ """The default implementation of the module specific finalization"""
+ pass
+
+ def watch_fileno(self, fileno, rcallback=None, wcallback=None, \
+ xcallback=None):
+ """Register the fileno for the internal select() call.
+
+ *callback's are callable objects which would be called when
+ read, write, error events occur on the specified fileno.
+ """
+ if rcallback is not None:
+ if fileno in self._read_callbacks:
+ self._read_callbacks[fileno].append(rcallback)
+ else:
+ self._read_callbacks[fileno] = [rcallback]
+
+ if wcallback is not None:
+ if fileno in self._write_callbacks:
+ self._write_callbacks[fileno].append(wcallback)
+ else:
+ self._write_callbacks[fileno] = [wcallback]
+
+ if xcallback is not None:
+ if fileno in self._error_callbacks:
+ self._error_callbacks[fileno].append(xcallback)
+ else:
+ self._error_callbacks[fileno] = [xcallback]
+
+ def run(self, module_name):
+ """Start the server and let it run until it's told to stop.
+
+ Usually this must be the first method of this class that is called
+ from its user.
+
+ Parameter:
+ module_name (str): the Python module name for the actual server
+ implementation. Often identical to the directory name in which
+ the implementation files are placed.
+
+ Returns: values expected to be used as program's exit code.
+ 0: server has run and finished successfully.
+ 1: some error happens
+
+ """
+ try:
+ self.__module_name = module_name
+ shutdown_sighandler = \
+ lambda signal, frame: self._trigger_shutdown()
+ signal.signal(signal.SIGTERM, shutdown_sighandler)
+ signal.signal(signal.SIGINT, shutdown_sighandler)
+ self._setup_ccsession()
+ self._setup_module()
+ self._run_internal()
+ logger.info(PYSERVER_COMMON_SERVER_STOPPED, self.__module_name)
+ return 0
+ except BIND10ServerFatal as ex:
+ logger.error(PYSERVER_COMMON_SERVER_FATAL, self.__module_name,
+ ex)
+ except Exception as ex:
+ logger.error(PYSERVER_COMMON_UNCAUGHT_EXCEPTION, type(ex).__name__,
+ ex)
+
+ return 1
diff --git a/src/lib/python/isc/server_common/server_common_messages.mes b/src/lib/python/isc/server_common/server_common_messages.mes
index f22ce65..54602f9 100644
--- a/src/lib/python/isc/server_common/server_common_messages.mes
+++ b/src/lib/python/isc/server_common/server_common_messages.mes
@@ -21,6 +21,9 @@
# have that at this moment. So when adding a message, make sure that
# the name is not already used in src/lib/config/config_messages.mes
+% PYSERVER_COMMON_COMMAND %1 server has received '%2' command
+The server process received the shown name of command from other module.
+
% PYSERVER_COMMON_DNS_TCP_SEND_DONE completed sending TCP message to %1 (%2 bytes in total)
Debug message. A complete DNS message has been successfully
transmitted over a TCP connection, possibly after multiple send
@@ -44,6 +47,18 @@ The destination address and the total size of the message that has
been transmitted so far (including the 2-byte length field) are shown
in the log message.
+% PYSERVER_COMMON_SERVER_FATAL %1 server has encountered a fatal error: %2
+The BIND 10 server process encountered a fatal error (normally specific to
+the particular program), and is forcing itself to shut down.
+
+% PYSERVER_COMMON_SERVER_STARTED %1 server has started
+The server process has successfully started and is now ready to receive
+commands and configuration updates.
+
+% PYSERVER_COMMON_SERVER_STOPPED %1 server has started
+The server process has successfully stopped and is no longer listening for or
+handling commands. Normally the process will soon exit.
+
% PYSERVER_COMMON_TSIG_KEYRING_DEINIT Deinitializing global TSIG keyring
A debug message noting that the global TSIG keyring is being removed from
memory. Most programs don't do that, they just exit, which is OK.
@@ -57,3 +72,8 @@ to be loaded from configuration.
A debug message. The TSIG keyring is being (re)loaded from configuration.
This happens at startup or when the configuration changes. The old keyring
is removed and new one created with all the keys.
+
+% PYSERVER_COMMON_UNCAUGHT_EXCEPTION uncaught exception of type %1: %2
+The BIND 10 server process encountered an uncaught exception and will now shut
+down. This is indicative of a programming error and should not happen under
+normal circumstances. The exception type and message are printed.
diff --git a/src/lib/python/isc/server_common/tests/.gitignore b/src/lib/python/isc/server_common/tests/.gitignore
new file mode 100644
index 0000000..dfeb2e5
--- /dev/null
+++ b/src/lib/python/isc/server_common/tests/.gitignore
@@ -0,0 +1,2 @@
+/datasrc.spec
+/zone.sqlite3
diff --git a/src/lib/python/isc/server_common/tests/Makefile.am b/src/lib/python/isc/server_common/tests/Makefile.am
index 86006d5..bf6b26c 100644
--- a/src/lib/python/isc/server_common/tests/Makefile.am
+++ b/src/lib/python/isc/server_common/tests/Makefile.am
@@ -1,5 +1,6 @@
PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
PYTESTS = tsig_keyring_test.py dns_tcp_test.py datasrc_clients_mgr_test.py
+PYTESTS += bind10_server_test.py
EXTRA_DIST = $(PYTESTS)
# If necessary (rare cases), explicitly specify paths to dynamic libraries
@@ -29,6 +30,7 @@ endif
$(LIBRARY_PATH_PLACEHOLDER) \
PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_builddir)/src/lib/dns/python/.libs \
B10_LOCKFILE_DIR_FROM_BUILD=$(abs_top_builddir) \
+ B10_FROM_SOURCE=$(abs_top_srcdir) \
B10_FROM_BUILD=$(abs_top_builddir) \
$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
done
diff --git a/src/lib/python/isc/server_common/tests/bind10_server_test.py b/src/lib/python/isc/server_common/tests/bind10_server_test.py
new file mode 100755
index 0000000..f93eed6
--- /dev/null
+++ b/src/lib/python/isc/server_common/tests/bind10_server_test.py
@@ -0,0 +1,294 @@
+# Copyright (C) 2013 Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+import unittest
+import errno
+import os
+import signal
+
+import isc.log
+import isc.config
+from isc.server_common.bind10_server import *
+from isc.testutils.ccsession_mock import MockModuleCCSession
+
+TEST_FILENO = 42 # arbitrarily chosen
+
+class TestException(Exception):
+ """A generic exception class specific in this test module."""
+ pass
+
+class MyCCSession(MockModuleCCSession, isc.config.ConfigData):
+ def __init__(self, specfile, config_handler, command_handler):
+ # record parameter for later inspection
+ self.specfile_param = specfile
+ self.config_handler_param = config_handler
+ self.command_handler_param = command_handler
+
+ self.check_command_param = None # used in check_command()
+
+ # Initialize some local attributes of MockModuleCCSession, including
+ # 'stopped'
+ MockModuleCCSession.__init__(self)
+
+ def start(self):
+ pass
+
+ def check_command(self, nonblock):
+ """Mock check_command(). Just record the param for later inspection."""
+ self.check_command_param = nonblock
+
+ def get_socket(self):
+ return self
+
+ def fileno(self):
+ """Pretending get_socket().fileno()
+
+ Returing an arbitrarily chosen constant.
+
+ """
+ return TEST_FILENO
+
+class MockServer(BIND10Server):
+ def __init__(self):
+ BIND10Server.__init__(self)
+ self._select_fn = self.select_wrapper
+
+ def _setup_ccsession(self):
+ orig_cls = isc.config.ModuleCCSession
+ isc.config.ModuleCCSession = MyCCSession
+ try:
+ super()._setup_ccsession()
+ except Exception:
+ raise
+ finally:
+ isc.config.ModuleCCSession = orig_cls
+
+ def _config_handler(self):
+ pass
+
+ def mod_command_handler(self, cmd, args):
+ """A sample _mod_command_handler implementation."""
+ self.command_handler_params = (cmd, args) # for inspection
+ return isc.config.create_answer(0)
+
+ def select_wrapper(self, reads, writes, errors):
+ self._trigger_shutdown() # make sure the loop will stop
+ self.select_params = (reads, writes, errors) # record for inspection
+ return [], [], []
+
+class TestBIND10Server(unittest.TestCase):
+ def setUp(self):
+ self.__server = MockServer()
+ self.__reads = 0
+ self.__writes = 0
+ self.__errors = 0
+
+ def test_init(self):
+ """Check initial conditions"""
+ self.assertFalse(self.__server.shutdown)
+
+ def test_trigger_shutdown(self):
+ self.__server._trigger_shutdown()
+ self.assertTrue(self.__server.shutdown)
+
+ def test_sigterm_handler(self):
+ """Check the signal handler behavior.
+
+ SIGTERM and SIGINT should be caught and should call memmgr's
+ _trigger_shutdown(). This test also indirectly confirms run() calls
+ run_internal().
+
+ """
+ def checker():
+ self.__shutdown_called = True
+
+ self.__server._run_internal = lambda: os.kill(os.getpid(),
+ signal.SIGTERM)
+ self.__server._trigger_shutdown = lambda: checker()
+ self.assertEqual(0, self.__server.run('test'))
+ self.assertTrue(self.__shutdown_called)
+
+ self.__shutdown_called = False
+ self.__server._run_internal = lambda: os.kill(os.getpid(),
+ signal.SIGINT)
+ self.assertEqual(0, self.__server.run('test'))
+ self.assertTrue(self.__shutdown_called)
+
+ def test_exception(self):
+ """Check exceptions are handled, not leaked."""
+ def exception_raiser(ex_cls):
+ raise ex_cls('test')
+
+ # Test all possible exceptions that are explicitly caught
+ for ex in [TestException, BIND10ServerFatal]:
+ self.__server._run_internal = lambda: exception_raiser(ex)
+ self.assertEqual(1, self.__server.run('test'))
+
+ def test_run(self):
+ """Check other behavior of run()"""
+ self.__server._run_internal = lambda: None # prevent looping
+ self.assertEqual(0, self.__server.run('test'))
+ # module CC session should have been setup.
+ # The exact path to the spec file can vary, so we simply check
+ # it works and it's the expected name stripping the path.
+ self.assertEqual(
+ self.__server.mod_ccsession.specfile_param.split('/')[-1],
+ 'test.spec')
+ self.assertEqual(self.__server.mod_ccsession.config_handler_param,
+ self.__server._config_handler)
+ self.assertEqual(self.__server.mod_ccsession.command_handler_param,
+ self.__server._command_handler)
+
+ def test_run_with_setup_module(self):
+ """Check run() with module specific setup method."""
+ self.setup_called = False
+ def check_called():
+ self.setup_called = True
+ self.__server._run_internal = lambda: None
+ self.__server._setup_module = check_called
+ self.assertEqual(0, self.__server.run('test'))
+ self.assertTrue(self.setup_called)
+
+ def test_shutdown_command(self):
+ answer = self.__server._command_handler('shutdown', None)
+ self.assertTrue(self.__server.shutdown)
+ self.assertEqual((0, None), isc.config.parse_answer(answer))
+
+ def test_run_with_shutdown_module(self):
+ """Check run() with module specific shutdown method."""
+ self.shutdown_called = False
+ def check_called():
+ self.shutdown_called = True
+ self.__server.__shutdown = True
+ self.__server._shutdown_module = check_called
+ self.assertEqual(0, self.__server.run('test'))
+ self.assertTrue(self.shutdown_called)
+
+ def test_other_command(self):
+ self.__server._mod_command_handler = self.__server.mod_command_handler
+ answer = self.__server._command_handler('other command', None)
+ # shouldn't be confused with shutdown
+ self.assertFalse(self.__server.shutdown)
+ self.assertEqual((0, None), isc.config.parse_answer(answer))
+ self.assertEqual(('other command', None),
+ self.__server.command_handler_params)
+
+ def test_other_command_nohandler(self):
+ """Similar to test_other_command, but without explicit handler"""
+ # In this case "unknown command" error should be returned.
+ answer = self.__server._command_handler('other command', None)
+ self.assertEqual(1, isc.config.parse_answer(answer)[0])
+
+ def test_run_internal(self):
+ self.__server._setup_ccsession()
+ self.__server._run_internal()
+ self.assertEqual(([TEST_FILENO], [], []), self.__server.select_params)
+
+ def select_wrapper(self, r, w, e, ex=None, ret=None):
+ """Mock select() function used some of the tests below.
+
+ If ex is not None and it's first call to this method, it raises ex
+ assuming it's an exception.
+
+ If ret is not None, it returns the given value; otherwise it returns
+ all empty lists.
+
+ """
+ self.select_params.append((r, w, e))
+ if ex is not None and len(self.select_params) == 1:
+ raise ex
+ else:
+ self.__server._trigger_shutdown()
+ if ret is not None:
+ return ret
+ return [], [], []
+
+ def test_select_for_command(self):
+ """A normal event iteration, handling one command."""
+ self.select_params = []
+ self.__server._select_fn = \
+ lambda r, w, e: self.select_wrapper(r, w, e,
+ ret=([TEST_FILENO], [], []))
+ self.__server._setup_ccsession()
+ self.__server._run_internal()
+ # select should be called only once.
+ self.assertEqual([([TEST_FILENO], [], [])], self.select_params)
+ # check_command should have been called.
+ self.assertTrue(self.__server.mod_ccsession.check_command_param)
+ # module CC session should have been stopped explicitly.
+ self.assertTrue(self.__server.mod_ccsession.stopped)
+
+ def test_select_interrupted(self):
+ """Emulating case select() raises EINTR."""
+ self.select_params = []
+ self.__server._select_fn = \
+ lambda r, w, e: self.select_wrapper(r, w, e,
+ ex=select.error(errno.EINTR))
+ self.__server._setup_ccsession()
+ self.__server._run_internal()
+ # EINTR will be ignored and select() will be called again.
+ self.assertEqual([([TEST_FILENO], [], []), ([TEST_FILENO], [], [])],
+ self.select_params)
+ # check_command() shouldn't have been called (select_wrapper returns
+ # empty lists by default).
+ self.assertIsNone(self.__server.mod_ccsession.check_command_param)
+ self.assertTrue(self.__server.mod_ccsession.stopped)
+
+ def test_select_other_exception(self):
+ """Emulating case select() raises other select error."""
+ self.select_params = []
+ self.__server._select_fn = \
+ lambda r, w, e: self.select_wrapper(r, w, e,
+ ex=select.error(errno.EBADF))
+ self.__server._setup_ccsession()
+ # the exception will be propagated.
+ self.assertRaises(select.error, self.__server._run_internal)
+ self.assertEqual([([TEST_FILENO], [], [])], self.select_params)
+ # in this case module CC session hasn't been stopped explicitly
+ # others will notice it due to connection reset.
+ self.assertFalse(self.__server.mod_ccsession.stopped)
+
+ def my_read_callback(self):
+ self.__reads += 1
+
+ def my_write_callback(self):
+ self.__writes += 1
+
+ def my_error_callback(self):
+ self.__errors += 1
+
+ def test_watch_fileno(self):
+ """Test watching for fileno."""
+ self.select_params = []
+ self.__server._select_fn = \
+ lambda r, w, e: self.select_wrapper(r, w, e,
+ ret=([10, 20, 42, TEST_FILENO], [], [30]))
+ self.__server._setup_ccsession()
+
+ self.__server.watch_fileno(10, rcallback=self.my_read_callback)
+ self.__server.watch_fileno(20, rcallback=self.my_read_callback, \
+ wcallback=self.my_write_callback)
+ self.__server.watch_fileno(30, xcallback=self.my_error_callback)
+
+ self.__server._run_internal()
+ self.assertEqual([([10, 20, TEST_FILENO], [20], [30])], self.select_params)
+ self.assertEqual(2, self.__reads)
+ self.assertEqual(0, self.__writes)
+ self.assertEqual(1, self.__errors)
+
+if __name__== "__main__":
+ isc.log.init("bind10_server_test")
+ isc.log.resetUnitTestRootLogger()
+ unittest.main()
diff --git a/src/lib/resolve/recursive_query.cc b/src/lib/resolve/recursive_query.cc
index 55dc4da..3c54a78 100644
--- a/src/lib/resolve/recursive_query.cc
+++ b/src/lib/resolve/recursive_query.cc
@@ -245,11 +245,6 @@ private:
// normal query state
- // Update the question that will be sent to the server
- void setQuestion(const Question& new_question) {
- question_ = new_question;
- }
-
// TODO: replace by our wrapper
asio::deadline_timer client_timer;
asio::deadline_timer lookup_timer;
diff --git a/src/lib/server_common/portconfig.cc b/src/lib/server_common/portconfig.cc
index 7bc6876..6120c7d 100644
--- a/src/lib/server_common/portconfig.cc
+++ b/src/lib/server_common/portconfig.cc
@@ -49,6 +49,9 @@ parseAddresses(isc::data::ConstElementPtr addresses,
"address and port");
}
try {
+ // We create an IOAddress object to just check that
+ // construction passes. It is immediately destroyed.
+ // cppcheck-suppress unusedScopedObject
IOAddress(addr->stringValue());
if (port->intValue() < 0 ||
port->intValue() > 0xffff) {
diff --git a/src/lib/statistics/counter.h b/src/lib/statistics/counter.h
index b0d31e9..32d025e 100644
--- a/src/lib/statistics/counter.h
+++ b/src/lib/statistics/counter.h
@@ -55,7 +55,7 @@ public:
/// \param type %Counter item to increment
///
/// \throw isc::OutOfRange \a type is invalid
- void inc(const Counter::Type type) {
+ void inc(const Counter::Type& type) {
if (type >= counters_.size()) {
isc_throw(isc::OutOfRange, "Counter type is out of range");
}
@@ -68,7 +68,7 @@ public:
/// \param type %Counter item to get the value of
///
/// \throw isc::OutOfRange \a type is invalid
- const Counter::Value& get(const Counter::Type type) const {
+ const Counter::Value& get(const Counter::Type& type) const {
if (type >= counters_.size()) {
isc_throw(isc::OutOfRange, "Counter type is out of range");
}
diff --git a/src/lib/testutils/mockups.h b/src/lib/testutils/mockups.h
index 58b39ee..00c2dcd 100644
--- a/src/lib/testutils/mockups.h
+++ b/src/lib/testutils/mockups.h
@@ -115,6 +115,8 @@ private:
// to addServerXXX methods so the test code subsequently checks the parameters.
class MockDNSService : public isc::asiodns::DNSServiceBase {
public:
+ MockDNSService() : tcp_recv_timeout_(0) {}
+
// A helper tuple of parameters passed to addServerUDPFromFD().
struct UDPFdParams {
int fd;
diff --git a/src/lib/util/Makefile.am b/src/lib/util/Makefile.am
index ff5ef40..6d07ac8 100644
--- a/src/lib/util/Makefile.am
+++ b/src/lib/util/Makefile.am
@@ -37,6 +37,10 @@ libb10_util_la_SOURCES += encode/base32hex_from_binary.h
libb10_util_la_SOURCES += encode/base_n.cc encode/hex.h
libb10_util_la_SOURCES += encode/binary_from_base32hex.h
libb10_util_la_SOURCES += encode/binary_from_base16.h
+libb10_util_la_SOURCES += hooks/callout_manager.h hooks/callout_manager.cc
+libb10_util_la_SOURCES += hooks/callout_handle.h hooks/callout_handle.cc
+libb10_util_la_SOURCES += hooks/library_handle.h hooks/library_handle.cc
+libb10_util_la_SOURCES += hooks/server_hooks.h hooks/server_hooks.cc
libb10_util_la_SOURCES += random/qid_gen.h random/qid_gen.cc
libb10_util_la_SOURCES += random/random_number_generator.h
diff --git a/src/lib/util/buffer.h b/src/lib/util/buffer.h
index 4800e99..4aac11e 100644
--- a/src/lib/util/buffer.h
+++ b/src/lib/util/buffer.h
@@ -342,15 +342,17 @@ public:
/// \brief Assignment operator
OutputBuffer& operator =(const OutputBuffer& other) {
- uint8_t* newbuff(static_cast<uint8_t*>(malloc(other.allocated_)));
- if (newbuff == NULL && other.allocated_ != 0) {
- throw std::bad_alloc();
+ if (this != &other) {
+ uint8_t* newbuff(static_cast<uint8_t*>(malloc(other.allocated_)));
+ if (newbuff == NULL && other.allocated_ != 0) {
+ throw std::bad_alloc();
+ }
+ free(buffer_);
+ buffer_ = newbuff;
+ size_ = other.size_;
+ allocated_ = other.allocated_;
+ std::memcpy(buffer_, other.buffer_, size_);
}
- free(buffer_);
- buffer_ = newbuff;
- size_ = other.size_;
- allocated_ = other.allocated_;
- std::memcpy(buffer_, other.buffer_, size_);
return (*this);
}
diff --git a/src/lib/util/hooks/callout_handle.cc b/src/lib/util/hooks/callout_handle.cc
new file mode 100644
index 0000000..8052823
--- /dev/null
+++ b/src/lib/util/hooks/callout_handle.cc
@@ -0,0 +1,121 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <util/hooks/callout_handle.h>
+#include <util/hooks/callout_manager.h>
+#include <util/hooks/library_handle.h>
+#include <util/hooks/server_hooks.h>
+
+#include <string>
+#include <utility>
+#include <vector>
+
+using namespace std;
+using namespace isc::util;
+
+namespace isc {
+namespace util {
+
+// Constructor.
+CalloutHandle::CalloutHandle(const boost::shared_ptr<CalloutManager>& manager)
+ : arguments_(), context_collection_(), manager_(manager), skip_(false) {
+
+ // Call the "context_create" hook. We should be OK doing this - although
+ // the constructor has not finished running, all the member variables
+ // have been created.
+ manager_->callCallouts(ServerHooks::CONTEXT_CREATE, *this);
+}
+
+// Destructor
+CalloutHandle::~CalloutHandle() {
+
+ // Call the "context_destroy" hook. We should be OK doing this - although
+ // the destructor is being called, all the member variables are still in
+ // existence.
+ manager_->callCallouts(ServerHooks::CONTEXT_DESTROY, *this);
+}
+
+// Return the name of all argument items.
+
+vector<string>
+CalloutHandle::getArgumentNames() const {
+
+ vector<string> names;
+ for (ElementCollection::const_iterator i = arguments_.begin();
+ i != arguments_.end(); ++i) {
+ names.push_back(i->first);
+ }
+
+ return (names);
+}
+
+// Return the library handle allowing the callout to access the CalloutManager
+// registration/deregistration functions.
+
+LibraryHandle&
+CalloutHandle::getLibraryHandle() const {
+ return (manager_->getLibraryHandle());
+}
+
+// Return the context for the currently pointed-to library. This version is
+// used by the "setContext()" method and creates a context for the current
+// library if it does not exist.
+
+CalloutHandle::ElementCollection&
+CalloutHandle::getContextForLibrary() {
+ int libindex = manager_->getLibraryIndex();
+
+ // Access a reference to the element collection for the given index,
+ // creating a new element collection if necessary, and return it.
+ return (context_collection_[libindex]);
+}
+
+// The "const" version of the above, used by the "getContext()" method. If
+// the context for the current library doesn't exist, throw an exception.
+
+const CalloutHandle::ElementCollection&
+CalloutHandle::getContextForLibrary() const {
+ int libindex = manager_->getLibraryIndex();
+
+ ContextCollection::const_iterator libcontext =
+ context_collection_.find(libindex);
+ if (libcontext == context_collection_.end()) {
+ isc_throw(NoSuchCalloutContext, "unable to find callout context "
+ "associated with the current library index (" << libindex <<
+ ")");
+ }
+
+ // Return a reference to the context's element collection.
+ return (libcontext->second);
+}
+
+// Return the name of all items in the context associated with the current]
+// library.
+
+vector<string>
+CalloutHandle::getContextNames() const {
+
+ vector<string> names;
+
+ const ElementCollection& elements = getContextForLibrary();
+ for (ElementCollection::const_iterator i = elements.begin();
+ i != elements.end(); ++i) {
+ names.push_back(i->first);
+ }
+
+ return (names);
+}
+
+} // namespace util
+} // namespace isc
diff --git a/src/lib/util/hooks/callout_handle.h b/src/lib/util/hooks/callout_handle.h
new file mode 100644
index 0000000..0033505
--- /dev/null
+++ b/src/lib/util/hooks/callout_handle.h
@@ -0,0 +1,353 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef CALLOUT_HANDLE_H
+#define CALLOUT_HANDLE_H
+
+#include <exceptions/exceptions.h>
+#include <util/hooks/library_handle.h>
+
+#include <boost/any.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include <map>
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace util {
+
+/// @brief No such argument
+///
+/// Thrown if an attempt is made access an argument that does not exist.
+
+class NoSuchArgument : public Exception {
+public:
+ NoSuchArgument(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// @brief No such callout context item
+///
+/// Thrown if an attempt is made to get an item of data from this callout's
+/// context and either the context or an item in the context with that name
+/// does not exist.
+
+class NoSuchCalloutContext : public Exception {
+public:
+ NoSuchCalloutContext(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+// Forward declaration of the library handle and related collection classes.
+
+class CalloutManager;
+class LibraryHandle;
+
+/// @brief Per-packet callout handle
+///
+/// An object of this class is associated with every packet (or request)
+/// processed by the server. It forms the principle means of passing data
+/// between the server and the user-library callouts.
+///
+/// The class allows access to the following information:
+///
+/// - Arguments. When the callouts associated with a hook are called, they
+/// are passed information by the server (and can return information to it)
+/// through name/value pairs. Each of these pairs is an argument and the
+/// information is accessed through the {get,set}Argument() methods.
+///
+/// - Per-packet context. Each packet has a context associated with it, this
+/// context being on a per-library basis. In other words, As a packet passes
+/// through the callouts associated with a given library, the callouts can
+/// associate and retrieve information with the packet. The per-library
+/// nature of the context means that the callouts within a given library can
+/// pass packet-specific information between one another, but they cannot pass
+/// information to callous within another library. Typically such context
+/// is created in the "context_create" callout and destroyed in the
+/// "context_destroy" callout. The information is accessed through the
+/// {get,set}Context() methods.
+///
+/// - Per-library handle. Allows the callout to dynamically register and
+/// deregister callouts. (In the latter case, only functions registered by
+/// functions in the same library as the callout doing the deregistration
+/// can be removed: callouts registered by other libraries cannot be
+/// modified.)
+
+class CalloutHandle {
+public:
+
+ /// Typedef to allow abbreviation of iterator specification in methods.
+ /// The std::string is the argument name and the "boost::any" is the
+ /// corresponding value associated with it.
+ typedef std::map<std::string, boost::any> ElementCollection;
+
+ /// Typedef to allow abbreviations in specifications when accessing
+ /// context. The ElementCollection is the name/value collection for
+ /// a particular context. The "int" corresponds to the index of an
+ /// associated library - there is a 1:1 correspondence between libraries
+ /// and a name.value collection.
+ ///
+ /// The collection of contexts is stored in a map, as not every library
+ /// will require creation of a context associated with each packet. In
+ /// addition, the structure is more flexible in that the size does not
+ /// need to be set when the CalloutHandle is constructed.
+ typedef std::map<int, ElementCollection> ContextCollection;
+
+
+ /// @brief Constructor
+ ///
+ /// Creates the object and calls the callouts on the "context_create"
+ /// hook.
+ ///
+ /// @param manager Pointer to the callout manager object.
+ CalloutHandle(const boost::shared_ptr<CalloutManager>& manager);
+
+ /// @brief Destructor
+ ///
+ /// Calls the context_destroy callback to release any per-packet context.
+ ~CalloutHandle();
+
+ /// @brief Set argument
+ ///
+ /// Sets the value of an argument. The argument is created if it does not
+ /// already exist.
+ ///
+ /// @param name Name of the argument.
+ /// @param value Value to set. That can be of any data type.
+ template <typename T>
+ void setArgument(const std::string& name, T value) {
+ arguments_[name] = value;
+ }
+
+ /// @brief Get argument
+ ///
+ /// Gets the value of an argument.
+ ///
+ /// @param name Name of the element in the argument list to get.
+ /// @param value [out] Value to set. The type of "value" is important:
+ /// it must match the type of the value set.
+ ///
+ /// @throw NoSuchArgument No argument with the given name is present.
+ /// @throw boost::bad_any_cast An argument with the given name is present,
+ /// but the data type of the value is not the same as the type of
+ /// the variable provided to receive the value.
+ template <typename T>
+ void getArgument(const std::string& name, T& value) const {
+ ElementCollection::const_iterator element_ptr = arguments_.find(name);
+ if (element_ptr == arguments_.end()) {
+ isc_throw(NoSuchArgument, "unable to find argument with name " <<
+ name);
+ }
+
+ value = boost::any_cast<T>(element_ptr->second);
+ }
+
+ /// @brief Get argument names
+ ///
+ /// Returns a vector holding the names of arguments in the argument
+ /// vector.
+ ///
+ /// @return Vector of strings reflecting argument names.
+ std::vector<std::string> getArgumentNames() const;
+
+ /// @brief Delete argument
+ ///
+ /// Deletes an argument of the given name. If an argument of that name
+ /// does not exist, the method is a no-op.
+ ///
+ /// N.B. If the element is a raw pointer, the pointed-to data is NOT deleted
+ /// by this method.
+ ///
+ /// @param name Name of the element in the argument list to set.
+ void deleteArgument(const std::string& name) {
+ static_cast<void>(arguments_.erase(name));
+ }
+
+ /// @brief Delete all arguments
+ ///
+ /// Deletes all arguments associated with this context.
+ ///
+ /// N.B. If any elements are raw pointers, the pointed-to data is NOT
+ /// deleted by this method.
+ void deleteAllArguments() {
+ arguments_.clear();
+ }
+
+ /// @brief Set skip flag
+ ///
+ /// Sets the "skip" variable in the callout handle. This variable is
+ /// interrogated by the server to see if the remaining callouts associated
+ /// with the current hook should be bypassed.
+ ///
+ /// @param skip New value of the "skip" flag.
+ void setSkip(bool skip) {
+ skip_ = skip;
+ }
+
+ /// @brief Get skip flag
+ ///
+ /// Gets the current value of the "skip" flag.
+ ///
+ /// @return Current value of the skip flag.
+ bool getSkip() const {
+ return (skip_);
+ }
+
+ /// @brief Access current library handle
+ ///
+ /// Returns a reference to the current library handle. This function is
+ /// only available when called by a callout (which in turn is called
+ /// through the "callCallouts" method), as it is only then that the current
+ /// library index is valid. A callout uses the library handle to
+ /// dynamically register or deregister callouts.
+ ///
+ /// @return Reference to the library handle.
+ ///
+ /// @throw InvalidIndex thrown if this method is called when the current
+ /// library index is invalid (typically if it is called outside of
+ /// the active callout).
+ LibraryHandle& getLibraryHandle() const;
+
+ /// @brief Set context
+ ///
+ /// Sets an element in the context associated with the current library. If
+ /// an element of the name is already present, it is replaced.
+ ///
+ /// @param name Name of the element in the context to set.
+ /// @param value Value to set.
+ template <typename T>
+ void setContext(const std::string& name, T value) {
+ getContextForLibrary()[name] = value;
+ }
+
+ /// @brief Get context
+ ///
+ /// Gets an element from the context associated with the current library.
+ ///
+ /// @param name Name of the element in the context to get.
+ /// @param value [out] Value to set. The type of "value" is important:
+ /// it must match the type of the value set.
+ ///
+ /// @throw NoSuchCalloutContext Thrown if no context element with the name
+ /// "name" is present.
+ /// @throw boost::bad_any_cast Thrown if the context element is present
+ /// but the type of the data is not the same as the type of the
+ /// variable provided to receive its value.
+ template <typename T>
+ void getContext(const std::string& name, T& value) const {
+ const ElementCollection& lib_context = getContextForLibrary();
+
+ ElementCollection::const_iterator element_ptr = lib_context.find(name);
+ if (element_ptr == lib_context.end()) {
+ isc_throw(NoSuchCalloutContext, "unable to find callout context "
+ "item " << name << " in the context associated with "
+ "current library");
+ }
+
+ value = boost::any_cast<T>(element_ptr->second);
+ }
+
+ /// @brief Get context names
+ ///
+ /// Returns a vector holding the names of items in the context associated
+ /// with the current library.
+ ///
+ /// @return Vector of strings reflecting the names of items in the callout
+ /// context associated with the current library.
+ std::vector<std::string> getContextNames() const;
+
+ /// @brief Delete context element
+ ///
+ /// Deletes an item of the given name from the context associated with the
+ /// current library. If an item of that name does not exist, the method is
+ /// a no-op.
+ ///
+ /// N.B. If the element is a raw pointer, the pointed-to data is NOT deleted
+ /// by this.
+ ///
+ /// @param name Name of the context item to delete.
+ void deleteContext(const std::string& name) {
+ static_cast<void>(getContextForLibrary().erase(name));
+ }
+
+ /// @brief Delete all context items
+ ///
+ /// Deletes all items from the context associated with the current library.
+ ///
+ /// N.B. If any elements are raw pointers, the pointed-to data is NOT
+ /// deleted by this.
+ void deleteAllContext() {
+ getContextForLibrary().clear();
+ }
+
+
+private:
+ /// @brief Check index
+ ///
+ /// Gets the current library index, throwing an exception if it is not set
+ /// or is invalid for the current library collection.
+ ///
+ /// @return Current library index, valid for this library collection.
+ ///
+ /// @throw InvalidIndex current library index is not valid for the library
+ /// handle collection.
+ int getLibraryIndex() const;
+
+ /// @brief Return reference to context for current library
+ ///
+ /// Called by all context-setting functions, this returns a reference to
+ /// the callout context for the current library, creating a context if it
+ /// does not exist.
+ ///
+ /// @return Reference to the collection of name/value pairs associated
+ /// with the current library.
+ ///
+ /// @throw InvalidIndex current library index is not valid for the library
+ /// handle collection.
+ ElementCollection& getContextForLibrary();
+
+ /// @brief Return reference to context for current library (const version)
+ ///
+ /// Called by all context-accessing functions, this a reference to the
+ /// callout context for the current library. An exception is thrown if
+ /// it does not exist.
+ ///
+ /// @return Reference to the collection of name/value pairs associated
+ /// with the current library.
+ ///
+ /// @throw NoSuchCalloutContext Thrown if there is no ElementCollection
+ /// associated with the current library.
+ const ElementCollection& getContextForLibrary() const;
+
+ // Member variables
+
+ /// Collection of arguments passed to the callouts
+ ElementCollection arguments_;
+
+ /// Context collection - there is one entry per library context.
+ ContextCollection context_collection_;
+
+ /// Callout manager.
+ boost::shared_ptr<CalloutManager> manager_;
+
+ /// "Skip" flag, indicating if the caller should bypass remaining callouts.
+ bool skip_;
+};
+
+} // namespace util
+} // namespace isc
+
+
+#endif // CALLOUT_HANDLE_H
diff --git a/src/lib/util/hooks/callout_manager.cc b/src/lib/util/hooks/callout_manager.cc
new file mode 100644
index 0000000..af20619
--- /dev/null
+++ b/src/lib/util/hooks/callout_manager.cc
@@ -0,0 +1,182 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <util/hooks/callout_handle.h>
+#include <util/hooks/callout_manager.h>
+
+#include <boost/static_assert.hpp>
+
+#include <algorithm>
+#include <functional>
+#include <utility>
+
+using namespace std;
+using namespace isc::util;
+
+namespace isc {
+namespace util {
+
+// Register a callout for the current library.
+
+void
+CalloutManager::registerCallout(const std::string& name, CalloutPtr callout) {
+ // Sanity check that the current library index is set to a valid value.
+ checkLibraryIndex(current_library_);
+
+ // Get the index associated with this hook (validating the name in the
+ // process).
+ int hook_index = hooks_->getIndex(name);
+
+ // Iterate through the callout vector for the hook from start to end,
+ // looking for the first entry where the library index is greater than
+ // the present index.
+ for (CalloutVector::iterator i = hook_vector_[hook_index].begin();
+ i != hook_vector_[hook_index].end(); ++i) {
+ if (i->first > current_library_) {
+ // Found an element whose library index number is greater than the
+ // current index, so insert the new element ahead of this one.
+ hook_vector_[hook_index].insert(i, make_pair(current_library_,
+ callout));
+ return;
+ }
+ }
+
+ // Reached the end of the vector, so there is no element in the (possibly
+ // empty) set of callouts with a library index greater than the current
+ // library index. Inset the callout at the end of the list.
+ hook_vector_[hook_index].push_back(make_pair(current_library_, callout));
+}
+
+// Check if callouts are present for a given hook index.
+
+bool
+CalloutManager::calloutsPresent(int hook_index) const {
+ // Validate the hook index.
+ if ((hook_index < 0) || (hook_index >= hook_vector_.size())) {
+ isc_throw(NoSuchHook, "hook index " << hook_index <<
+ " is not valid for the list of registered hooks");
+ }
+
+ // Valid, so are there any callouts associated with that hook?
+ return (!hook_vector_[hook_index].empty());
+}
+
+// Call all the callouts for a given hook.
+
+void
+CalloutManager::callCallouts(int hook_index, CalloutHandle& callout_handle) {
+
+ // Only initialize and iterate if there are callouts present. This check
+ // also catches the case of an invalid index.
+ if (calloutsPresent(hook_index)) {
+
+ // Clear the "skip" flag so we don't carry state from a previous call.
+ callout_handle.setSkip(false);
+
+ // Duplicate the callout vector for this hook and work through that.
+ // This step is needed because we allow dynamic registration and
+ // deregistration of callouts. If a callout attached to a hook modified
+ // the list of callouts on that hook, the underlying CalloutVector would
+ // change and potentially affect the iteration through that vector.
+ CalloutVector callouts(hook_vector_[hook_index]);
+
+ // Call all the callouts.
+ for (CalloutVector::const_iterator i = callouts.begin();
+ i != callouts.end(); ++i) {
+ // In case the callout tries to register or deregister a callout,
+ // set the current library index to the index associated with the
+ // library that registered the callout being called.
+ current_library_ = i->first;
+
+ // Call the callout
+ // @todo Log the return status if non-zero
+ static_cast<void>((*i->second)(callout_handle));
+ }
+
+ // Reset the current library index to an invalid value to catch any
+ // programming errors.
+ current_library_ = -1;
+ }
+}
+
+// Deregister a callout registered by the current library on a particular hook.
+
+bool
+CalloutManager::deregisterCallout(const std::string& name, CalloutPtr callout) {
+ // Sanity check that the current library index is set to a valid value.
+ checkLibraryIndex(current_library_);
+
+ // Get the index associated with this hook (validating the name in the
+ // process).
+ int hook_index = hooks_->getIndex(name);
+
+ /// Construct a CalloutEntry matching the current library and the callout
+ /// we want to remove.
+ CalloutEntry target(current_library_, callout);
+
+ /// To decide if any entries were removed, we'll record the initial size
+ /// of the callout vector for the hook, and compare it with the size after
+ /// the removal.
+ size_t initial_size = hook_vector_[hook_index].size();
+
+ // The next bit is standard STL (see "Item 33" in "Effective STL" by
+ // Scott Meyers).
+ //
+ // remove_if reorders the hook vector so that all items not matching
+ // the predicate are at the start of the vector and returns a pointer
+ // to the next element. (In this case, the predicate is that the item
+ // is equal to the value of the passed callout.) The erase() call
+ // removes everything from that element to the end of the vector, i.e.
+ // all the matching elements.
+ hook_vector_[hook_index].erase(remove_if(hook_vector_[hook_index].begin(),
+ hook_vector_[hook_index].end(),
+ bind1st(equal_to<CalloutEntry>(),
+ target)),
+ hook_vector_[hook_index].end());
+
+ // Return an indication of whether anything was removed.
+ return (initial_size != hook_vector_[hook_index].size());
+}
+
+// Deregister all callouts on a given hook.
+
+bool
+CalloutManager::deregisterAllCallouts(const std::string& name) {
+
+ // Get the index associated with this hook (validating the name in the
+ // process).
+ int hook_index = hooks_->getIndex(name);
+
+ /// Construct a CalloutEntry matching the current library (the callout
+ /// pointer is NULL as we are not checking that).
+ CalloutEntry target(current_library_, NULL);
+
+ /// To decide if any entries were removed, we'll record the initial size
+ /// of the callout vector for the hook, and compare it with the size after
+ /// the removal.
+ size_t initial_size = hook_vector_[hook_index].size();
+
+ // Remove all callouts matching this library.
+ hook_vector_[hook_index].erase(remove_if(hook_vector_[hook_index].begin(),
+ hook_vector_[hook_index].end(),
+ bind1st(CalloutLibraryEqual(),
+ target)),
+ hook_vector_[hook_index].end());
+
+ // Return an indication of whether anything was removed.
+ return (initial_size != hook_vector_[hook_index].size());
+}
+
+} // namespace util
+} // namespace isc
diff --git a/src/lib/util/hooks/callout_manager.h b/src/lib/util/hooks/callout_manager.h
new file mode 100644
index 0000000..7a22433
--- /dev/null
+++ b/src/lib/util/hooks/callout_manager.h
@@ -0,0 +1,301 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef CALLOUT_MANAGER_H
+#define CALLOUT_MANAGER_H
+
+#include <exceptions/exceptions.h>
+#include <util/hooks/library_handle.h>
+#include <util/hooks/server_hooks.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <map>
+#include <string>
+
+namespace isc {
+namespace util {
+
+/// @brief No such library
+///
+/// Thrown if an attempt is made to set the current library index to a value
+/// that is invalid for the number of loaded libraries.
+class NoSuchLibrary : public Exception {
+public:
+ NoSuchLibrary(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+
+/// @brief Callout Manager
+///
+/// This class manages the registration, deregistration and execution of the
+/// library callouts.
+///
+/// In operation, the class needs to know two items of data:
+///
+/// - The list of server hooks, which is used in two ways. Firstly, when a
+/// callout registers or deregisters a hook, it does so by name: the
+/// @ref isc::util::ServerHooks object supplies the names of registered
+/// hooks. Secondly, when the callouts associated with a hook are called by
+/// the server, the server supplies the index of the relevant hook: this is
+/// validated by reference to the list of hook.
+///
+/// - The number of loaded libraries. Each callout registered by a user
+/// library is associated with that library, the callout manager storing both
+/// a pointer to the callout and the index of the library in the list of
+/// loaded libraries. Callouts are allowed to dynamically register and
+/// deregister callouts in the same library (including themselves): they
+/// cannot affect callouts registered by another library. When calling a
+/// callout, the callout manager maintains the idea of a "current library
+/// index": if the callout calls one of the callout registration functions
+/// (indirectly via the @ref LibraryHandle object), the registration
+/// functions use the "current library index" in their processing.
+///
+/// These two items of data are supplied when an object of this class is
+/// constructed.
+///
+/// Note that the callout function do not access the library manager: instead,
+/// they use a LibraryHandle object. This contains an internal pointer to
+/// the CalloutManager, but provides a restricted interface. In that way,
+/// callouts are unable to affect callouts supplied by other libraries.
+
+class CalloutManager {
+private:
+
+ // Private typedefs
+
+ /// Element in the vector of callouts. The elements in the pair are the
+ /// index of the library from which this callout was registered, and a#
+ /// pointer to the callout itself.
+ typedef std::pair<int, CalloutPtr> CalloutEntry;
+
+ /// An element in the hook vector. Each element is a vector of callouts
+ /// associated with a given hook.
+ typedef std::vector<CalloutEntry> CalloutVector;
+
+public:
+
+ /// @brief Constructor
+ ///
+ /// Initializes member variables, in particular sizing the hook vector
+ /// (the vector of callout vectors) to the appropriate size.
+ ///
+ /// @param hooks Collection of known hook names.
+ /// @param num_libraries Number of loaded libraries.
+ ///
+ /// @throw isc::BadValue if the number of libraries is less than or equal
+ /// to 0, or if the pointer to the server hooks object is empty.
+ CalloutManager(const boost::shared_ptr<ServerHooks>& hooks,
+ int num_libraries)
+ : current_library_(-1), hooks_(hooks), hook_vector_(),
+ library_handle_(this), num_libraries_(num_libraries)
+ {
+ if (!hooks) {
+ isc_throw(isc::BadValue, "must pass a pointer to a valid server "
+ "hooks object to the CalloutManager");
+ } else if (num_libraries <= 0) {
+ isc_throw(isc::BadValue, "number of libraries passed to the "
+ "CalloutManager must be >= 0");
+ }
+
+ // Parameters OK, do operations that depend on them.
+ hook_vector_.resize(hooks_->getCount());
+ }
+
+ /// @brief Register a callout on a hook for the current library
+ ///
+ /// Registers a callout function for the current library with a given hook
+ /// (the index of the "current library" being given by the current_library_
+ /// member). The callout is added to the end of the callouts for this
+ /// library that are associated with that hook.
+ ///
+ /// @param name Name of the hook to which the callout is added.
+ /// @param callout Pointer to the callout function to be registered.
+ ///
+ /// @throw NoSuchHook The hook name is unrecognised.
+ /// @throw Unexpected The hook name is valid but an internal data structure
+ /// is of the wrong size.
+ void registerCallout(const std::string& name, CalloutPtr callout);
+
+ /// @brief De-Register a callout on a hook for the current library
+ ///
+ /// Searches through the functions registered by the the current library
+ /// (the index of the "current library" being given by the current_library_
+ /// member) with the named hook and removes all entries matching the
+ /// callout.
+ ///
+ /// @param name Name of the hook from which the callout is removed.
+ /// @param callout Pointer to the callout function to be removed.
+ ///
+ /// @return true if a one or more callouts were deregistered.
+ ///
+ /// @throw NoSuchHook The hook name is unrecognised.
+ /// @throw Unexpected The hook name is valid but an internal data structure
+ /// is of the wrong size.
+ bool deregisterCallout(const std::string& name, CalloutPtr callout);
+
+ /// @brief Removes all callouts on a hook for the current library
+ ///
+ /// Removes all callouts associated with a given hook that were registered
+ /// by the current library (the index of the "current library" being given
+ /// by the current_library_ member).
+ ///
+ /// @param name Name of the hook from which the callouts are removed.
+ ///
+ /// @return true if one or more callouts were deregistered.
+ ///
+ /// @throw NoSuchHook Thrown if the hook name is unrecognised.
+ bool deregisterAllCallouts(const std::string& name);
+
+ /// @brief Checks if callouts are present on a hook
+ ///
+ /// Checks all loaded libraries and returns true if at least one callout
+ /// has been registered by any of them for the given hook.
+ ///
+ /// @param hook_index Hook index for which callouts are checked.
+ ///
+ /// @return true if callouts are present, false if not.
+ ///
+ /// @throw NoSuchHook Given index does not correspond to a valid hook.
+ bool calloutsPresent(int hook_index) const;
+
+ /// @brief Calls the callouts for a given hook
+ ///
+ /// Iterates through the libray handles and calls the callouts associated
+ /// with the given hook index.
+ ///
+ /// @note This method invalidates the current library index set with
+ /// setLibraryIndex().
+ ///
+ /// @param hook_index Index of the hook to call.
+ /// @param callout_handle Reference to the CalloutHandle object for the
+ /// current object being processed.
+ void callCallouts(int hook_index, CalloutHandle& callout_handle);
+
+ /// @brief Get number of libraries
+ ///
+ /// Returns the number of libraries that this CalloutManager is expected
+ /// to serve. This is the number passed to its constructor.
+ ///
+ /// @return Number of libraries server by this CalloutManager.
+ int getNumLibraries() const {
+ return (num_libraries_);
+ }
+
+ /// @brief Get current library index
+ ///
+ /// Returns the index of the "current" library. This the index associated
+ /// with the currently executing callout when callCallouts is executing.
+ /// When callCallouts() is not executing (as is the case when the load()
+ /// function in a user-library is called during the library load process),
+ /// the index can be set by setLibraryIndex().
+ ///
+ /// @note The value set by this method is lost after a call to
+ /// callCallouts.
+ ///
+ /// @return Current library index.
+ int getLibraryIndex() const {
+ return (current_library_);
+ }
+
+ /// @brief Set current library index
+ ///
+ /// Sets the current library index. This must be in the range 0 to
+ /// (numlib - 1), where "numlib" is the number of libraries loaded in the
+ /// system, this figure being passed to this object at construction time.
+ ///
+ /// @param library_index New library index.
+ ///
+ /// @throw NoSuchLibrary if the index is not valid.
+ void setLibraryIndex(int library_index) {
+ checkLibraryIndex(library_index);
+ current_library_ = library_index;
+ }
+
+ /// @brief Return library handle
+ ///
+ /// The library handle is available to the user callout via the callout
+ /// handle object. It provides a cut-down view of the CalloutManager,
+ /// allowing the callout to register and deregister callouts in the
+ /// library of which it is part, whilst denying access to anything that
+ /// may affect other libraries.
+ ///
+ /// @return Reference to callout handle for this manager
+ LibraryHandle& getLibraryHandle() {
+ return (library_handle_);
+ }
+
+private:
+ /// @brief Check library index
+ ///
+ /// Ensures that the current library index is valid. This is called by
+ /// the hook registration functions.
+ ///
+ /// @param library_index Value to check for validity as a library index.
+ ///
+ /// @throw NoSuchLibrary Library index is not valid.
+ void checkLibraryIndex(int library_index) const {
+ if ((library_index < 0) || (library_index >= num_libraries_)) {
+ isc_throw(NoSuchLibrary, "library index " << library_index <<
+ " is not valid for the number of loaded libraries (" <<
+ num_libraries_ << ")");
+ }
+ }
+
+ /// @brief Compare two callout entries for library equality
+ ///
+ /// This is used in callout removal code when all callouts on a hook for a
+ /// given library are being removed. It checks whether two callout entries
+ /// have the same library index.
+ ///
+ /// @param ent1 First callout entry to check
+ /// @param ent2 Second callout entry to check
+ ///
+ /// @return bool true if the library entries are the same
+ class CalloutLibraryEqual :
+ public std::binary_function<CalloutEntry, CalloutEntry, bool> {
+ public:
+ bool operator()(const CalloutEntry& ent1,
+ const CalloutEntry& ent2) const {
+ return (ent1.first == ent2.first);
+ }
+ };
+
+ /// Current library index. When a call is made to any of the callout
+ /// registration methods, this variable indicates the index of the user
+ /// library that should be associated with the call.
+ int current_library_;
+
+ /// List of server hooks.
+ boost::shared_ptr<ServerHooks> hooks_;
+
+ /// Vector of callout vectors. There is one entry in this outer vector for
+ /// each hook. Each element is itself a vector, with one entry for each
+ /// callout registered for that hook.
+ std::vector<CalloutVector> hook_vector_;
+
+ /// LibraryHandle object user by the callout to access the callout
+ /// registration methods on this CalloutManager object.
+ LibraryHandle library_handle_;
+
+ /// Number of libraries.
+ int num_libraries_;
+
+};
+
+} // namespace util
+} // namespace isc
+
+#endif // CALLOUT_MANAGER_H
diff --git a/src/lib/util/hooks/library_handle.cc b/src/lib/util/hooks/library_handle.cc
new file mode 100644
index 0000000..0a65e54
--- /dev/null
+++ b/src/lib/util/hooks/library_handle.cc
@@ -0,0 +1,39 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <util/hooks/callout_manager.h>
+#include <util/hooks/library_handle.h>
+
+namespace isc {
+namespace util {
+
+// Callout manipulation - all deferred to the CalloutManager.
+
+void
+LibraryHandle::registerCallout(const std::string& name, CalloutPtr callout) {
+ callout_manager_->registerCallout(name, callout);
+}
+
+bool
+LibraryHandle::deregisterCallout(const std::string& name, CalloutPtr callout) {
+ return (callout_manager_->deregisterCallout(name, callout));
+}
+
+bool
+LibraryHandle::deregisterAllCallouts(const std::string& name) {
+ return (callout_manager_->deregisterAllCallouts(name));
+}
+
+} // namespace util
+} // namespace isc
diff --git a/src/lib/util/hooks/library_handle.h b/src/lib/util/hooks/library_handle.h
new file mode 100644
index 0000000..43a78a1
--- /dev/null
+++ b/src/lib/util/hooks/library_handle.h
@@ -0,0 +1,115 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef LIBRARY_HANDLE_H
+#define LIBRARY_HANDLE_H
+
+#include <string>
+
+namespace isc {
+namespace util {
+
+// Forward declarations
+class CalloutHandle;
+class CalloutManager;
+
+/// Typedef for a callout pointer. (Callouts must have "C" linkage.)
+extern "C" {
+ typedef int (*CalloutPtr)(CalloutHandle&);
+};
+
+/// @brief Library handle
+///
+/// This class is accessed by the user library when registering callouts,
+/// either by the library's load() function, or by one of the callouts
+/// themselves.
+///
+/// It is really little more than a shell around the CalloutManager. By
+/// presenting this object to the user-library callouts, callouts can manage
+/// the callout list for their own library, but cannot affect the callouts
+/// registered by other libraries.
+///
+/// (This restriction is achieved by the CalloutManager maintaining the concept
+/// of the "current library". When a callout is registered - either by the
+/// library's load() function, or by a callout in the library - the registration
+/// information includes the library active at the time. When that callout is
+/// called, the CalloutManager uses that information to set the "current
+/// library": the registration functions only operator on data whose
+/// associated library is equal to the "current library".)
+
+class LibraryHandle {
+public:
+
+ /// @brief Constructor
+ ///
+ /// @param manager Back pointer to the containing CalloutManager.
+ /// This pointer is used to access appropriate methods in that
+ /// object. Note that the "raw" pointer is safe - the only
+ /// instance of the LibraryHandle in the system is as a member of
+ /// the CalloutManager to which it points.
+ LibraryHandle(CalloutManager* manager) : callout_manager_(manager)
+ {}
+
+ /// @brief Register a callout on a hook
+ ///
+ /// Registers a callout function with a given hook. The callout is added
+ /// to the end of the callouts for the current library that are associated
+ /// with that hook.
+ ///
+ /// @param name Name of the hook to which the callout is added.
+ /// @param callout Pointer to the callout function to be registered.
+ ///
+ /// @throw NoSuchHook The hook name is unrecognised.
+ /// @throw Unexpected The hook name is valid but an internal data structure
+ /// is of the wrong size.
+ void registerCallout(const std::string& name, CalloutPtr callout);
+
+ /// @brief De-Register a callout on a hook
+ ///
+ /// Searches through the functions registered by the current library with
+ /// the named hook and removes all entries matching the callout. It does
+ /// not affect callouts registered by other libraries.
+ ///
+ /// @param name Name of the hook from which the callout is removed.
+ /// @param callout Pointer to the callout function to be removed.
+ ///
+ /// @return true if a one or more callouts were deregistered.
+ ///
+ /// @throw NoSuchHook The hook name is unrecognised.
+ /// @throw Unexpected The hook name is valid but an internal data structure
+ /// is of the wrong size.
+ bool deregisterCallout(const std::string& name, CalloutPtr callout);
+
+ /// @brief Removes all callouts on a hook
+ ///
+ /// Removes all callouts associated with a given hook that were registered.
+ /// by the current library. It does not affect callouts that were
+ /// registered by other libraries.
+ ///
+ /// @param name Name of the hook from which the callouts are removed.
+ ///
+ /// @return true if one or more callouts were deregistered.
+ ///
+ /// @throw NoSuchHook Thrown if the hook name is unrecognised.
+ bool deregisterAllCallouts(const std::string& name);
+
+private:
+ /// Back pointer to the collection object for the library
+ CalloutManager* callout_manager_;
+};
+
+} // namespace util
+} // namespace isc
+
+#endif // LIBRARY_HANDLE_H
diff --git a/src/lib/util/hooks/server_hooks.cc b/src/lib/util/hooks/server_hooks.cc
new file mode 100644
index 0000000..478d94c
--- /dev/null
+++ b/src/lib/util/hooks/server_hooks.cc
@@ -0,0 +1,125 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <exceptions/exceptions.h>
+#include <util/hooks/server_hooks.h>
+
+#include <utility>
+#include <vector>
+
+using namespace std;
+using namespace isc;
+
+namespace isc {
+namespace util {
+
+// Constructor - register the pre-defined hooks and check that the indexes
+// assigned to them are as expected.
+
+ServerHooks::ServerHooks() {
+ int create = registerHook("context_create");
+ int destroy = registerHook("context_destroy");
+
+ if ((create != CONTEXT_CREATE) || (destroy != CONTEXT_DESTROY)) {
+ isc_throw(Unexpected, "pre-defined hook indexes are not as expected. "
+ "context_create: expected = " << CONTEXT_CREATE <<
+ ", actual = " << create <<
+ ". context_destroy: expected = " << CONTEXT_DESTROY <<
+ ", actual = " << destroy);
+ }
+}
+
+// Register a hook. The index assigned to the hook is the current number
+// of entries in the collection, so ensuring that hook indexes are unique
+// and non-negative.
+
+int
+ServerHooks::registerHook(const string& name) {
+
+ // Determine index for the new element and insert.
+ int index = hooks_.size();
+ pair<HookCollection::iterator, bool> result =
+ hooks_.insert(make_pair(name, index));
+
+ if (!result.second) {
+ // New element was not inserted because an element with the same name
+ // already existed.
+ isc_throw(DuplicateHook, "hook with name " << name <<
+ " is already registered");
+ }
+
+ // New element inserted, return numeric index.
+ return (index);
+}
+
+// Find the index associated with a hook name.
+
+int
+ServerHooks::getIndex(const string& name) const {
+
+ // Get iterator to matching element.
+ HookCollection::const_iterator i = hooks_.find(name);
+ if (i == hooks_.end()) {
+ isc_throw(NoSuchHook, "hook name " << name << " is not recognised");
+ }
+
+ return (i->second);
+}
+
+// Return vector of hook names. The names are not sorted - it is up to the
+// caller to perform sorting if required.
+
+vector<string>
+ServerHooks::getHookNames() const {
+
+ vector<string> names;
+ HookCollection::const_iterator i;
+ for (i = hooks_.begin(); i != hooks_.end(); ++i) {
+ names.push_back(i->first);
+ }
+
+ return (names);
+}
+
+// Hook registration function methods
+
+// Access the hook registration function vector itself
+
+std::vector<HookRegistrationFunction::RegistrationFunctionPtr>&
+HookRegistrationFunction::getFunctionVector() {
+ static std::vector<RegistrationFunctionPtr> reg_functions;
+ return (reg_functions);
+}
+
+// Constructor - add a registration function to the function vector
+
+HookRegistrationFunction::HookRegistrationFunction(
+ HookRegistrationFunction::RegistrationFunctionPtr reg_func) {
+ getFunctionVector().push_back(reg_func);
+}
+
+// Execute all registered registration functions
+
+void
+HookRegistrationFunction::execute(ServerHooks& hooks) {
+ std::vector<RegistrationFunctionPtr>& reg_functions = getFunctionVector();
+ for (int i = 0; i < reg_functions.size(); ++i) {
+ (*reg_functions[i])(hooks);
+ }
+}
+
+
+
+} // namespace util
+} // namespace isc
diff --git a/src/lib/util/hooks/server_hooks.h b/src/lib/util/hooks/server_hooks.h
new file mode 100644
index 0000000..ce41fa0
--- /dev/null
+++ b/src/lib/util/hooks/server_hooks.h
@@ -0,0 +1,207 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef SERVER_HOOKS_H
+#define SERVER_HOOKS_H
+
+#include <exceptions/exceptions.h>
+
+#include <map>
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace util {
+
+/// @brief Duplicate hook
+///
+/// Thrown if an attempt is made to register a hook with the same name as a
+/// previously-registered hook.
+class DuplicateHook : public Exception {
+public:
+ DuplicateHook(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// @brief Invalid hook
+///
+/// Thrown if an attempt is made to get the index for an invalid hook.
+class NoSuchHook : public Exception {
+public:
+ NoSuchHook(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+
+/// @brief Server hook collection
+///
+/// This class is used by the server-side code to register hooks - points in the
+/// server processing at which libraries can register functions (callouts) that
+/// the server will call. These functions can modify data and so affect the
+/// processing of the server.
+///
+/// The ServerHooks class is little more than a wrapper around the std::map
+/// class. It stores a hook, assigning to it a unique index number. This
+/// number is then used by the server code to identify the hook being called.
+/// (Although it would be feasible to use a name as an index, using an integer
+/// will speed up the time taken to locate the callouts, which may make a
+/// difference in a frequently-executed piece of code.)
+
+class ServerHooks {
+public:
+
+ /// Index numbers for pre-defined hooks.
+ static const int CONTEXT_CREATE = 0;
+ static const int CONTEXT_DESTROY = 1;
+
+ /// @brief Constructor
+ ///
+ /// This pre-registers two hooks, context_create and context_destroy, which
+ /// are called by the server before processing a packet and after processing
+ /// for the packet has completed. They allow the server code to allocate
+ /// and destroy per-packet context.
+ ///
+ /// @throws isc::Unexpected if the registration of the pre-defined hooks
+ /// fails in some way.
+ ServerHooks();
+
+ /// @brief Register a hook
+ ///
+ /// Registers a hook and returns the hook index.
+ ///
+ /// @param name Name of the hook
+ ///
+ /// @return Index of the hook, to be used in subsequent hook-related calls.
+ /// This will be greater than or equal to zero (so allowing a
+ /// negative value to indicate an invalid index).
+ ///
+ /// @throws DuplicateHook A hook with the same name has already been
+ /// registered.
+ int registerHook(const std::string& name);
+
+ /// @brief Get hook index
+ ///
+ /// Returns the index of a hook.
+ ///
+ /// @param name Name of the hook
+ ///
+ /// @return Index of the hook, to be used in subsequent calls.
+ ///
+ /// @throw NoSuchHook if the hook name is unknown to the caller.
+ int getIndex(const std::string& name) const;
+
+ /// @brief Return number of hooks
+ ///
+ /// Returns the total number of hooks registered.
+ ///
+ /// @return Number of hooks registered.
+ int getCount() const {
+ return (hooks_.size());
+ }
+
+ /// @brief Get hook names
+ ///
+ /// Return list of hooks registered in the object.
+ ///
+ /// @return Vector of strings holding hook names.
+ std::vector<std::string> getHookNames() const;
+
+private:
+ typedef std::map<std::string, int> HookCollection;
+
+ HookCollection hooks_; ///< Hook name/index collection
+};
+
+
+/// @brief Hooks Registration
+///
+/// All hooks must be registered before libraries are loaded and callouts
+/// assigned to them. One way of doing this is to have a global list of hooks:
+/// the addition of any hook anywhere would require updating the list. This
+/// is possible and, if desired, the author of a server can do it.
+///
+/// An alternative is the option provided here, where each component of BIND 10
+/// registers the hooks they are using. To do this, the component should
+/// create a hook registration function of the form:
+///
+/// @code
+/// static int hook1_num = -1; // Initialize number for hook 1
+/// static int hook2_num = -1; // Initialize number for hook 2
+///
+/// void myModuleRegisterHooks(ServerHooks& hooks) {
+/// hook1_num = hooks.registerHook("hook1");
+/// hook2_num = hooks.registerHook("hook2");
+/// }
+/// @endcode
+///
+/// ... which registers the hooks and stores the associated hook index. To
+/// avoid the need to add an explicit call to each of the hook registration
+/// functions to the server initialization code, the component should declare
+/// an object of this class in the same file as the registration function,
+/// but outside of any function. The declaration should include the name
+/// of the registration function, i.e.
+///
+/// @code
+/// HookRegistrationFunction f(myModuleRegisterHooks);
+/// @code
+///
+/// The constructor of this object will run prior to main() getting called and
+/// will add the registration function to a list of such functions. The server
+/// then calls the static class method "execute()" to run all the declared
+/// registration functions.
+
+class HookRegistrationFunction {
+public:
+ /// @brief Pointer to a hook registration function
+ typedef void (*RegistrationFunctionPtr)(ServerHooks&);
+
+ /// @brief Constructor
+ ///
+ /// For variables declared outside functions or methods, the constructors
+ /// are run after the program is loaded and before main() is called. This
+ /// constructor adds the passed pointer to a vector of such pointers.
+ HookRegistrationFunction(RegistrationFunctionPtr reg_func);
+
+ /// @brief Access registration function vector
+ ///
+ /// One of the problems with functions run prior to starting main() is the
+ /// "static initialization fiasco". This occurs because the order in which
+ /// objects outside functions are constructed is not defined. So if this
+ /// constructor were to depend on a vector declared externally, we would
+ /// not be able to guarantee that the vector had been initialised properly
+ /// before we used it.
+ ///
+ /// To get round this situation, the vector is declared statically within
+ /// a static function. The first time the function is called, the vector
+ /// is initialized before it is used.
+ ///
+ /// This function returns a reference to the vector used to hold the
+ /// pointers.
+ ///
+ /// @return Reference to the (static) list of registration functions
+ static std::vector<RegistrationFunctionPtr>& getFunctionVector();
+
+ /// @brief Execute registration functions
+ ///
+ /// Called by the server initialization code, this function executes all
+ /// registered hook registration functions.
+ ///
+ /// @param hooks ServerHooks object to which hook information will be added.
+ static void execute(ServerHooks& hooks);
+};
+
+} // namespace util
+} // namespace isc
+
+#endif // SERVER_HOOKS_H
diff --git a/src/lib/util/memory_segment_mapped.cc b/src/lib/util/memory_segment_mapped.cc
index 9300cae..8fea5ea 100644
--- a/src/lib/util/memory_segment_mapped.cc
+++ b/src/lib/util/memory_segment_mapped.cc
@@ -28,6 +28,8 @@
#include <string>
#include <new>
+#include <stdint.h>
+
// boost::interprocess namespace is big and can cause unexpected import
// (e.g., it has "read_only"), so it's safer to be specific for shortcuts.
using boost::interprocess::basic_managed_mapped_file;
diff --git a/src/lib/util/python/.gitignore b/src/lib/util/python/.gitignore
index c54df80..619c69f 100644
--- a/src/lib/util/python/.gitignore
+++ b/src/lib/util/python/.gitignore
@@ -1,2 +1,3 @@
+/doxygen2pydoc.py
/gen_wiredata.py
/mkpywrapper.py
diff --git a/src/lib/util/python/Makefile.am b/src/lib/util/python/Makefile.am
index 1e05688..c7ddf3d 100644
--- a/src/lib/util/python/Makefile.am
+++ b/src/lib/util/python/Makefile.am
@@ -1,3 +1,3 @@
-noinst_SCRIPTS = gen_wiredata.py mkpywrapper.py const2hdr.py \
+noinst_SCRIPTS = doxygen2pydoc.py gen_wiredata.py mkpywrapper.py const2hdr.py \
pythonize_constants.py
EXTRA_DIST = const2hdr.py pythonize_constants.py
diff --git a/src/lib/util/python/doxygen2pydoc.py.in b/src/lib/util/python/doxygen2pydoc.py.in
new file mode 100755
index 0000000..7aa74ec
--- /dev/null
+++ b/src/lib/util/python/doxygen2pydoc.py.in
@@ -0,0 +1,680 @@
+#!@PYTHON@
+
+# Copyright (C) 2011 Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+r"""
+A helper to semi-auto generate Python docstring text from C++ Doxygen
+documentation.
+
+This script converts an XML-format doxygen documentation for C++ library
+into a template Python docstring for the corresponding Python version
+of the library. While it's not perfect and you'll still need to edit the
+output by hand, but past experiments showed the script produces a pretty
+good template. It will help provide more compatible documentation for
+both C++ and Python versions of library from a unified source (C++ Doxygen
+documentation) with minimizing error-prone and boring manual conversion.
+
+HOW TO USE IT
+
+1. Generate XML output by doxygen. Use bind10/doc/Doxyfile-xml:
+
+ % cd bind10/doc
+ % doxygen Doxyfile-xml
+ (XML files will be generated under bind10/doc/html/xml)
+
+2. Identify the xml file of the conversion target (C++ class, function, etc)
+
+ This is a bit tricky. You'll probably need to do manual search.
+ For example, to identify the xml file for a C++ class
+ isc::datasrc::memory::ZoneWriter, you might do:
+
+ % cd bind10/doc/html/xml
+ % grep ZoneWriter *.xml | grep 'kind="class"'
+ index.xml: <compound refid="d4/d3c/classisc_1_1datasrc_1_1memory_1_1ZoneWriter" kind="class"><name>isc::datasrc::memory::ZoneWriter</name>
+
+ In this case the file under the d4/d3c directory (with .xml suffix) would
+ be the file you're looking for.
+
+3. Run this script for the xml file:
+
+ % python3 doxygen2pydoc.py <top_srcdir>/doc/html/xml/d4/d3c/classisc_1_1datasrc_1_1memory_1_1ZoneWriter.xml > output.cc
+
+ The template content is dumped to standard out (redirected to file
+ "output.cc" in this example).
+
+ Sometimes the script produces additional output to standard error,
+ like this:
+
+ Replaced camelCased terms:
+ resetMemorySegment => reset_memory_segment
+ getConfiguration => get_configuration
+
+ In BIND 10 naming convention for methods is different for C++ and
+ Python. This script uses some heuristic guess to convert the
+ C++-style method names to likely Python-style ones, and the converted
+ method names are used in the dumped template. In many cases the guessed
+ names are correct, but you should check this list and make adjustments
+ by hand if necessary.
+
+ If there's no standard error output, this type of conversion didn't
+ happen.
+
+4. Edit and copy the template
+
+ The dumped template has the following organization:
+
+ namespace {
+ #ifdef COPY_THIS_TO_MAIN_CC
+ { "cleanup", ZoneWriter_cleanup, METH_NOARGS,
+ ZoneWriter_cleanup_doc },
+ { "install", ZoneWriter_install, METH_NOARGS,
+ ZoneWriter_install_doc },
+ { "load", ZoneWriter_load, METH_VARARGS,
+ ZoneWriter_load_doc },
+ #endif // COPY_THIS_TO_MAIN_CC
+
+ const char* const ZoneWriter_doc = "\
+ ...
+ ";
+
+ const char* const ZoneWriter_install_doc = "\
+ ...
+ ";
+
+ ...
+ }
+
+ The ifdef-ed block is a template for class methods information
+ to be added to the corresponding PyMethodDef structure array
+ (your wrapper C++ source would have something like ZoneWriter_methods
+ of this type). These lines should be copied there. As long as
+ the method names and corresponding wrapper function (such as
+ ZoneWriter_cleanup) are correct you shouldn't have to edit this part
+ (and they would be normally correct, unless the guessed method name
+ conversion was needed).
+
+ The rest of the content is a sequence of constant C-string variables.
+ Usually the first variable corresponds to the class description, and
+ the rest are method descriptions (note that ZoneWriter_install_doc
+ is referenced from the ifdef-ed block). The content of this part
+ would generally make sense, but you'll often need to make some
+ adjsutments by hand. A common examples of such adjustment is to
+ replace "NULL" with "None". Also, it's not uncommon that some part
+ of the description simply doesn't apply to the Python version or
+ that Python specific notes are needed. So go through the description
+ carefully and make necessary changes. A common practice is to add
+ comments for a summary of adjustments like this:
+
+ // Modifications:
+ // NULL->None
+ // - removed notes about derived classes (which doesn't apply for python)
+ const char* const ZoneWriter_doc = "\
+ ...
+ ";
+ This note will help next time you need to auto-generate and edit the
+ template (probably because the original C++ document is updated).
+
+ You can simply copy this part to the main C++ wrapper file, but since
+ it's relatively large a common practice is to maintain it in a separate
+ file that is exclusively included from the main file: if the name of
+ the main file is zonewriter_python.cc, the pydoc strings would be copied
+ in zonewriter_python_inc.cc, and the main file would have this line:
+
+ #include "zonewriter_inc.cc"
+
+ (In case you are C++ language police: it's okay to use the unnamed
+ name space for a file to be included because it's essentially a part
+ of the single .cc file, not expected to be included by others).
+
+ In either case, the ifdef-ed part should be removed.
+
+ADVANCED FEATURES
+
+You can use a special "xmlonly" doxygen command in C++ doxygent document
+in order to include Python code excerpt (while hiding it from the doxygen
+output for the C++ version). This command will be converted to
+a special XML tag in the XML output.
+
+The block enclosed by \xmlonly and \endxmlonly should contain
+a verbatim XML tag named "pythonlisting", in which the python code should
+be placed.
+/// \code
+/// Name name("example.com");
+/// std::cout << name.toText() << std::endl;
+/// \endcode
+///
+/// \xmlonly <pythonlisting>
+/// name = Name("example.com")
+/// print(name.to_text())
+/// </pythonlisting> \endxmlonly
+
+Note that there must be a blank line between \endcode and \xmlonly.
+doxygen2pydoc assume the pythonlisting tag is in a separate <para> node.
+This blank ensures doxygen will produce the XML file that meets the
+assumption.
+
+INTERNAL MEMO (incomplete, and not very unredable yet)
+
+This simplified utility assumes the following structure:
+...
+ <compounddef ...>
+ <compoundname>isc::dns::TSIGError</compoundname>
+ <sectiondef kind="user-defined">
+ constructor, destructor
+ </sectiondef>
+ <sectiondef kind="public-type">
+ ..
+ </sectiondef>
+ <sectiondef kind="public-func">
+ <memberdef kind="function"...>
+ <type>return type (if any)</type>
+ <argsstring>(...) [const]</argsstring>
+ <name>method name</name>
+ <briefdescription>method's brief description</briefdescription>
+ <detaileddescription>
+ <para>...</para>...
+ <para>
+ <parameterlist kind="exception">
+ <parameteritem>
+ <parameternamelist>
+ <parametername>Exception name</parametername>
+ </parameternamelist>
+ <parameterdescription>
+ <para>exception desc</para>
+ </parameterdescription>
+ </parameteritem>
+ </parameterlist>
+ <parameterlist kind="param">
+ <parameteritem>
+ <parameternamelist>
+ <parametername>param name</parametername>
+ </parameternamelist>
+ <parameterdescription>
+ <para>param desc</para>
+ </parameterdescription>
+ </parameteritem>
+ ...
+ </parameterlist>
+ <simplesect kind="return">Return value</simplesect>
+ </para>
+ </detaileddescription>
+ </memberdef>
+ </sectiondef>
+ <sectiondef kind="public-static-attrib|user-defined">
+ <memberdef kind="variable"...>
+ <name>class-specific-constant</name>
+ <initializer>value</initializer>
+ <brief|detaileddescription>paragraph(s)</brief|detaileddescription>
+ </sectiondef>
+ <briefdescription>
+ class's brief description
+ </briefdescription>
+ <detaileddescription>
+ class's detailed description
+ </detaileddescription>
+ </compounddef>
+"""
+
+import re, string, sys, textwrap
+from xml.dom.minidom import parse
+from textwrap import fill, dedent, TextWrapper
+
+camel_replacements = {}
+member_functions = []
+constructors = []
+class_variables = []
+
+RE_CAMELTERM = re.compile('([\s\.]|^)[a-z]+[A-Z]\S*')
+RE_SIMPLECAMEL = re.compile("([a-z])([A-Z])")
+RE_CAMELAFTERUPPER = re.compile("([A-Z])([A-Z])([a-z])")
+
+class Paragraph:
+ TEXT = 0
+ ITEMIZEDLIST = 1
+ CPPLISTING = 2
+ PYLISTING = 3
+ VERBATIM = 4
+
+ def __init__(self, xml_node):
+ if len(xml_node.getElementsByTagName("pythonlisting")) > 0:
+ self.type = self.PYLISTING
+ self.text = re.sub("///", "", get_text(xml_node))
+ elif len(xml_node.getElementsByTagName("verbatim")) > 0:
+ self.type = self.VERBATIM
+ self.text = get_text(xml_node)
+ elif len(xml_node.getElementsByTagName("programlisting")) > 0:
+ # We ignore node containing a "programlisting" tag.
+ # They are C++ example code, and we are not interested in them
+ # in pydoc.
+ self.type = self.CPPLISTING
+ elif len(xml_node.getElementsByTagName("itemizedlist")) > 0:
+ self.type = self.ITEMIZEDLIST
+ self.items = []
+ for item in xml_node.getElementsByTagName("listitem"):
+ self.items.append(get_text(item))
+ else:
+ self.type = self.TEXT
+
+ # A single textual paragraph could have multiple simple sections
+ # if it contains notes.
+
+ self.texts = []
+ subnodes = []
+ for child in xml_node.childNodes:
+ if child.nodeType == child.ELEMENT_NODE and \
+ child.nodeName == 'simplesect' and \
+ child.getAttribute('kind') == 'note':
+ if len(subnodes) > 0:
+ self.texts.append(get_text_fromnodelist(subnodes))
+ subnodes = []
+ subtext = 'Note: '
+ for t in child.childNodes:
+ subtext += get_text(t)
+ self.texts.append(subtext)
+ else:
+ subnodes.append(child)
+ if len(subnodes) > 0:
+ self.texts.append(get_text_fromnodelist(subnodes))
+
+ def dump(self, f, wrapper):
+ if self.type == self.CPPLISTING:
+ return
+ elif self.type == self.ITEMIZEDLIST:
+ for item in self.items:
+ item_wrapper = TextWrapper(\
+ initial_indent=wrapper.initial_indent + "- ",
+ subsequent_indent=wrapper.subsequent_indent + " ")
+ dump_filled_text(f, item_wrapper, item)
+ f.write("\\n\\\n")
+ elif self.type == self.TEXT:
+ for text in self.texts:
+ if text != self.texts[0]:
+ f.write("\\n\\\n")
+ dump_filled_text(f, wrapper, text)
+ f.write("\\n\\\n")
+ else:
+ dump_filled_text(f, None, self.text)
+ f.write("\\n\\\n")
+ f.write("\\n\\\n")
+
+class NamedItem:
+ def __init__(self, name, desc):
+ self.name = name
+ self.desc = desc
+
+ def dump(self, f, wrapper):
+ # we use deeper indent inside the item list.
+ new_initial_indent = wrapper.initial_indent + " " * 2
+ new_subsequent_indent = wrapper.subsequent_indent + " " * (2 + 11)
+ local_wrapper = TextWrapper(initial_indent=new_initial_indent,
+ subsequent_indent=new_subsequent_indent)
+
+ # concatenate name and description with a fixed width (up to 10 chars)
+ # for the name, and wrap the entire text, then dump it to file.
+ dump_filled_text(f, local_wrapper, "%-10s %s" % (self.name, self.desc))
+ f.write("\\n\\\n")
+
+class FunctionDefinition:
+ # function types
+ CONSTRUCTOR = 0
+ COPY_CONSTRUCTOR = 1
+ DESTRUCTOR = 2
+ ASSIGNMENT_OP = 3
+ OTHER = 4
+
+ def __init__(self):
+ self.type = self.OTHER
+ self.name = None
+ self.pyname = None
+ self.args = ""
+ self.ret_type = None
+ self.brief_desc = None
+ self.detailed_desc = []
+ self.exceptions = []
+ self.parameters = []
+ self.returns = None
+ self.have_param = False
+
+ def dump_doc(self, f, wrapper=TextWrapper()):
+ f.write(self.pyname + "(" + self.args + ")")
+ if self.ret_type is not None:
+ f.write(" -> " + self.ret_type)
+ f.write("\\n\\\n\\n\\\n")
+
+ if self.brief_desc is not None:
+ dump_filled_text(f, wrapper, self.brief_desc)
+ f.write("\\n\\\n\\n\\\n")
+
+ for para in self.detailed_desc:
+ para.dump(f, wrapper)
+
+ if len(self.exceptions) > 0:
+ f.write(wrapper.fill("Exceptions:") + "\\n\\\n")
+ for ex_desc in self.exceptions:
+ ex_desc.dump(f, wrapper)
+ f.write("\\n\\\n")
+ if len(self.parameters) > 0:
+ f.write(wrapper.fill("Parameters:") + "\\n\\\n")
+ for param_desc in self.parameters:
+ param_desc.dump(f, wrapper)
+ f.write("\\n\\\n")
+ if self.returns is not None:
+ dump_filled_text(f, wrapper, "Return Value(s): " + self.returns)
+ f.write("\\n\\\n")
+
+ def dump_pymethod_def(self, f, class_name):
+ f.write(' { "' + self.pyname + '", ')
+ f.write(class_name + '_' + self.name + ', ')
+ if len(self.parameters) == 0:
+ f.write('METH_NOARGS,\n')
+ else:
+ f.write('METH_VARARGS,\n')
+ f.write(' ' + class_name + '_' + self.name + '_doc },\n')
+
+class VariableDefinition:
+ def __init__(self, nodelist):
+ self.value = None
+ self.brief_desc = None
+ self.detailed_desc = []
+
+ for node in nodelist:
+ if node.nodeName == "name":
+ self.name = get_text(node)
+ elif node.nodeName == "initializer":
+ self.value = get_text(node)
+ elif node.nodeName == "briefdescription":
+ self.brief_desc = get_text(node)
+ elif node.nodeName == "detaileddescription":
+ for para in node.childNodes:
+ if para.nodeName != "para":
+ # ignore surrounding empty nodes
+ continue
+ self.detailed_desc.append(Paragraph(para))
+
+ def dump_doc(self, f, wrapper=TextWrapper()):
+ name_value = self.name
+ if self.value is not None:
+ name_value += ' = ' + self.value
+ dump_filled_text(f, wrapper, name_value)
+ f.write('\\n\\\n')
+
+ desc_initial_indent = wrapper.initial_indent + " "
+ desc_subsequent_indent = wrapper.subsequent_indent + " "
+ desc_wrapper = TextWrapper(initial_indent=desc_initial_indent,
+ subsequent_indent=desc_subsequent_indent)
+ if self.brief_desc is not None:
+ dump_filled_text(f, desc_wrapper, self.brief_desc)
+ f.write("\\n\\\n\\n\\\n")
+
+ for para in self.detailed_desc:
+ para.dump(f, desc_wrapper)
+
+def dump_filled_text(f, wrapper, text):
+ """Fill given text using wrapper, and dump it to the given file
+ appending an escaped CR at each end of line.
+ """
+ filled_text = wrapper.fill(text) if wrapper is not None else text
+ f.write("".join(re.sub("\n", r"\\n\\\n", filled_text)))
+
+def camel_to_lowerscores(matchobj):
+ oldtext = matchobj.group(0)
+ newtext = re.sub(RE_SIMPLECAMEL, r"\1_\2", oldtext)
+ newtext = re.sub(RE_CAMELAFTERUPPER, r"\1_\2\3", newtext)
+ newtext = newtext.lower()
+ camel_replacements[oldtext] = newtext
+ return newtext.lower()
+
+def cpp_to_python(text):
+ text = text.replace("::", ".")
+ text = text.replace('"', '\\"')
+
+ # convert camelCase to "_"-concatenated format
+ # (e.g. getLength -> get_length)
+ return re.sub(RE_CAMELTERM, camel_to_lowerscores, text)
+
+def convert_type_name(type_name):
+ """Convert C++ type name to python type name using common conventions"""
+ # strip off leading 'const' and trailing '&/*'
+ type_name = re.sub("^const\S*", "", type_name)
+ type_name = re.sub("\S*[&\*]$", "", type_name)
+
+ # We often typedef smart pointers as [Const]TypePtr. Convert them to
+ # just "Type"
+ type_name = re.sub("^Const", "", type_name)
+ type_name = re.sub("Ptr$", "", type_name)
+
+ if type_name == "std::string":
+ return "string"
+ if re.search(r"(int\d+_t|size_t)", type_name):
+ return "integer"
+ return type_name
+
+def get_text(root, do_convert=True):
+ """Recursively extract bare text inside the specified node (root),
+ concatenate all extracted text and return the result.
+ """
+ nodelist = root.childNodes
+ rc = []
+ for node in nodelist:
+ if node.nodeType == node.TEXT_NODE:
+ if do_convert:
+ rc.append(cpp_to_python(node.data))
+ else:
+ rc.append(node.data)
+ elif node.nodeType == node.ELEMENT_NODE:
+ rc.append(get_text(node))
+ # return the result, removing any leading newlines (that often happens for
+ # brief descriptions, which will cause lines not well aligned)
+ return re.sub("^(\n*)", "", ''.join(rc))
+
+def get_text_fromnodelist(nodelist, do_convert=True):
+ """Recursively extract bare text inside the specified node (root),
+ concatenate all extracted text and return the result.
+ """
+ rc = []
+ for node in nodelist:
+ if node.nodeType == node.TEXT_NODE:
+ if do_convert:
+ rc.append(cpp_to_python(node.data))
+ else:
+ rc.append(node.data)
+ elif node.nodeType == node.ELEMENT_NODE:
+ rc.append(get_text(node))
+ # return the result, removing any leading newlines (that often happens for
+ # brief descriptions, which will cause lines not well aligned)
+ return re.sub("^(\n*)", "", ''.join(rc))
+
+def parse_parameters(nodelist):
+ rc = []
+ for node in nodelist:
+ if node.nodeName != "parameteritem":
+ continue
+ # for simplicity, we assume one parametername and one
+ # parameterdescription for each parameter.
+ name = get_text(node.getElementsByTagName("parametername")[0])
+ desc = get_text(node.getElementsByTagName("parameterdescription")[0])
+ rc.append(NamedItem(name, desc))
+ return rc
+
+def parse_function_description(func_def, nodelist):
+ for node in nodelist:
+ # nodelist contains beginning and ending empty text nodes.
+ # ignore them (otherwise they cause disruption below).
+ if node.nodeName != "para":
+ continue
+
+ if node.getElementsByTagName("parameterlist"):
+ # within this node there may be exception list, parameter list,
+ # and description for return value. parse and store them
+ # seprately.
+ for paramlist in node.getElementsByTagName("parameterlist"):
+ if paramlist.getAttribute("kind") == "exception":
+ func_def.exceptions = \
+ parse_parameters(paramlist.childNodes)
+ elif paramlist.getAttribute("kind") == "param":
+ func_def.parameters = \
+ parse_parameters(paramlist.childNodes)
+ if node.getElementsByTagName("simplesect"):
+ simplesect = node.getElementsByTagName("simplesect")[0]
+ if simplesect.getAttribute("kind") == "return":
+ func_def.returns = get_text(simplesect)
+ else:
+ # for normal text, python listing and itemized list, append them
+ # to the list of paragraphs
+ func_def.detailed_desc.append(Paragraph(node))
+
+def parse_function(func_def, class_name, nodelist):
+ for node in nodelist:
+ if node.nodeName == "name":
+ func_def.name = get_text(node, False)
+ func_def.pyname = cpp_to_python(func_def.name)
+ elif node.nodeName == "argsstring":
+ # extract parameter names only, assuming they immediately follow
+ # their type name + space, and are immeidatelly followed by
+ # either "," or ")". If it's a pointer or reference, */& is
+ # prepended to the parameter name without a space:
+ # e.g. (int var1, char *var2, Foo &var3)
+ args = get_text(node, False)
+ # extract parameter names, possibly with */&
+ func_def.args = ', '.join(re.findall(r"\s(\S+)[,)]", args))
+ # then remove any */& symbols
+ func_def.args = re.sub("[\*&]", "", func_def.args)
+ elif node.nodeName == "type" and node.hasChildNodes():
+ func_def.ret_type = convert_type_name(get_text(node, False))
+ elif node.nodeName == "param":
+ func_def.have_param = True
+ elif node.nodeName == "briefdescription":
+ func_def.brief_desc = get_text(node)
+ elif node.nodeName == "detaileddescription":
+ parse_function_description(func_def, node.childNodes)
+ # identify the type of function using the name and arg
+ if func_def.name == class_name and \
+ re.search("^\(const " + class_name + " &[^,]*$", args):
+ # This function is ClassName(const ClassName& param), which is
+ # the copy constructor.
+ func_def.type = func_def.COPY_CONSTRUCTOR
+ elif func_def.name == class_name:
+ # if it's not the copy ctor but the function name == class name,
+ # it's a constructor.
+ func_def.type = func_def.CONSTRUCTOR
+ elif func_def.name == "~" + class_name:
+ func_def.type = func_def.DESTRUCTOR
+ elif func_def.name == "operator=":
+ func_def.type = func_def.ASSIGNMENT_OP
+
+ # register the definition to the approriate list
+ if func_def.type == func_def.CONSTRUCTOR:
+ constructors.append(func_def)
+ elif func_def.type == func_def.OTHER:
+ member_functions.append(func_def)
+
+def parse_functions(class_name, nodelist):
+ for node in nodelist:
+ if node.nodeName == "memberdef" and \
+ node.getAttribute("kind") == "function":
+ func_def = FunctionDefinition()
+ parse_function(func_def, class_name, node.childNodes)
+
+def parse_class_variables(class_name, nodelist):
+ for node in nodelist:
+ if node.nodeName == "memberdef" and \
+ node.getAttribute("kind") == "variable":
+ class_variables.append(VariableDefinition(node.childNodes))
+
+def dump(f, class_name, class_brief_doc, class_detailed_doc):
+ f.write("namespace {\n")
+
+ f.write('#ifdef COPY_THIS_TO_MAIN_CC\n')
+ for func in member_functions:
+ func.dump_pymethod_def(f, class_name)
+ f.write('#endif // COPY_THIS_TO_MAIN_CC\n\n')
+
+ f.write("const char* const " + class_name + '_doc = "\\\n')
+ if class_brief_doc is not None:
+ f.write("".join(re.sub("\n", r"\\n\\\n", fill(class_brief_doc))))
+ f.write("\\n\\\n")
+ f.write("\\n\\\n")
+ if len(class_detailed_doc) > 0:
+ for para in class_detailed_doc:
+ para.dump(f, wrapper=TextWrapper())
+
+ # dump constructors
+ for func in constructors:
+ indent = " " * 4
+ func.dump_doc(f, wrapper=TextWrapper(initial_indent=indent,
+ subsequent_indent=indent))
+
+ # dump class variables
+ if len(class_variables) > 0:
+ f.write("Class constant data:\\n\\\n")
+ for var in class_variables:
+ var.dump_doc(f)
+
+ f.write("\";\n")
+
+ for func in member_functions:
+ f.write("\n")
+ f.write("const char* const " + class_name + "_" + func.name + \
+ "_doc = \"\\\n");
+ func.dump_doc(f)
+ f.write("\";\n")
+
+ f.write("} // unnamed namespace") # close namespace
+
+if __name__ == '__main__':
+ dom = parse(sys.argv[1])
+ class_elements = dom.getElementsByTagName("compounddef")[0].childNodes
+ class_brief_doc = None
+ class_detailed_doc = []
+ for node in class_elements:
+ if node.nodeName == "compoundname":
+ # class name is the last portion of the period-separated fully
+ # qualified class name. (this should exist)
+ class_name = re.split("\.", get_text(node))[-1]
+ if node.nodeName == "briefdescription":
+ # we assume a brief description consists at most one para
+ class_brief_doc = get_text(node)
+ elif node.nodeName == "detaileddescription":
+ # a detaild description consists of one or more paragraphs
+ for para in node.childNodes:
+ if para.nodeName != "para": # ignore surrounding empty nodes
+ continue
+ class_detailed_doc.append(Paragraph(para))
+ elif node.nodeName == "sectiondef" and \
+ node.getAttribute("kind") == "public-func":
+ parse_functions(class_name, node.childNodes)
+ elif node.nodeName == "sectiondef" and \
+ node.getAttribute("kind") == "public-static-attrib":
+ parse_class_variables(class_name, node.childNodes)
+ elif node.nodeName == "sectiondef" and \
+ node.getAttribute("kind") == "user-defined":
+ # there are two possiblities: functions and variables
+ for child in node.childNodes:
+ if child.nodeName != "memberdef":
+ continue
+ if child.getAttribute("kind") == "function":
+ parse_function(FunctionDefinition(), class_name,
+ child.childNodes)
+ elif child.getAttribute("kind") == "variable":
+ class_variables.append(VariableDefinition(child.childNodes))
+
+ dump(sys.stdout, class_name, class_brief_doc, class_detailed_doc)
+
+ if len(camel_replacements) > 0:
+ sys.stderr.write("Replaced camelCased terms:\n")
+ for oldterm in camel_replacements.keys():
+ sys.stderr.write("%s => %s\n" % (oldterm,
+ camel_replacements[oldterm]))
diff --git a/src/lib/util/random/qid_gen.h b/src/lib/util/random/qid_gen.h
index 80f532f..fd7ce36 100644
--- a/src/lib/util/random/qid_gen.h
+++ b/src/lib/util/random/qid_gen.h
@@ -25,6 +25,8 @@
#include <boost/random/uniform_int.hpp>
#include <boost/random/variate_generator.hpp>
+#include <stdint.h>
+
namespace isc {
namespace util {
namespace random {
diff --git a/src/lib/util/tests/Makefile.am b/src/lib/util/tests/Makefile.am
index ab85fa2..ceb8b95 100644
--- a/src/lib/util/tests/Makefile.am
+++ b/src/lib/util/tests/Makefile.am
@@ -25,10 +25,13 @@ run_unittests_SOURCES = run_unittests.cc
run_unittests_SOURCES += base32hex_unittest.cc
run_unittests_SOURCES += base64_unittest.cc
run_unittests_SOURCES += buffer_unittest.cc
+run_unittests_SOURCES += callout_handle_unittest.cc
+run_unittests_SOURCES += callout_manager_unittest.cc
run_unittests_SOURCES += fd_share_tests.cc
run_unittests_SOURCES += fd_tests.cc
run_unittests_SOURCES += filename_unittest.cc
run_unittests_SOURCES += hex_unittest.cc
+run_unittests_SOURCES += handles_unittest.cc
run_unittests_SOURCES += io_utilities_unittest.cc
run_unittests_SOURCES += lru_list_unittest.cc
run_unittests_SOURCES += memory_segment_local_unittest.cc
@@ -39,6 +42,7 @@ run_unittests_SOURCES += memory_segment_common_unittest.h
run_unittests_SOURCES += memory_segment_common_unittest.cc
run_unittests_SOURCES += qid_gen_unittest.cc
run_unittests_SOURCES += random_number_generator_unittest.cc
+run_unittests_SOURCES += server_hooks_unittest.cc
run_unittests_SOURCES += sha1_unittest.cc
run_unittests_SOURCES += socketsession_unittest.cc
run_unittests_SOURCES += strutil_unittest.cc
diff --git a/src/lib/util/tests/buffer_unittest.cc b/src/lib/util/tests/buffer_unittest.cc
index 02ca83d..76b884c 100644
--- a/src/lib/util/tests/buffer_unittest.cc
+++ b/src/lib/util/tests/buffer_unittest.cc
@@ -248,6 +248,11 @@ TEST_F(BufferTest, outputBufferAssign) {
});
}
+// Check assign to self doesn't break stuff
+TEST_F(BufferTest, outputBufferAssignSelf) {
+ EXPECT_NO_THROW(obuffer = obuffer);
+}
+
TEST_F(BufferTest, outputBufferZeroSize) {
// Some OSes might return NULL on malloc for 0 size, so check it works
EXPECT_NO_THROW({
diff --git a/src/lib/util/tests/callout_handle_unittest.cc b/src/lib/util/tests/callout_handle_unittest.cc
new file mode 100644
index 0000000..76e18d8
--- /dev/null
+++ b/src/lib/util/tests/callout_handle_unittest.cc
@@ -0,0 +1,329 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <util/hooks/callout_handle.h>
+#include <util/hooks/callout_manager.h>
+#include <util/hooks/library_handle.h>
+#include <util/hooks/server_hooks.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <gtest/gtest.h>
+
+using namespace isc::util;
+using namespace std;
+
+namespace {
+
+/// @file
+/// @brief Holds the CalloutHandle argument tests
+///
+/// Additional testing of the CalloutHandle - together with the interaction
+/// of the LibraryHandle - is done in the handles_unittests set of tests.
+
+class CalloutHandleTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor
+ ///
+ /// Sets up a callout manager to be referenced by the CalloutHandle in
+ /// these tests. (The "4" for the number of libraries in the
+ /// CalloutManager is arbitrary - it is not used in these tests.)
+ CalloutHandleTest()
+ : hooks_(new ServerHooks()), manager_(new CalloutManager(hooks_, 4))
+ {}
+
+ /// Obtain hook manager
+ boost::shared_ptr<CalloutManager>& getCalloutManager() {
+ return (manager_);
+ }
+
+private:
+ /// List of server hooks
+ boost::shared_ptr<ServerHooks> hooks_;
+
+ /// Callout manager accessed by this CalloutHandle.
+ boost::shared_ptr<CalloutManager> manager_;
+};
+
+// *** Argument Tests ***
+//
+// The first set of tests check that the CalloutHandle can store and retrieve
+// arguments. These are very similar to the LibraryHandle context tests.
+
+// Test that we can store multiple values of the same type and that they
+// are distinct.
+
+TEST_F(CalloutHandleTest, ArgumentDistinctSimpleType) {
+ CalloutHandle handle(getCalloutManager());
+
+ // Store and retrieve an int (random value).
+ int a = 42;
+ handle.setArgument("integer1", a);
+ EXPECT_EQ(42, a);
+
+ int b = 0;
+ handle.getArgument("integer1", b);
+ EXPECT_EQ(42, b);
+
+ // Add another integer (another random value).
+ int c = 142;
+ handle.setArgument("integer2", c);
+ EXPECT_EQ(142, c);
+
+ int d = 0;
+ handle.getArgument("integer2", d);
+ EXPECT_EQ(142, d);
+
+ // Add a short (random value).
+ short e = -81;
+ handle.setArgument("short", e);
+ EXPECT_EQ(-81, e);
+
+ short f = 0;
+ handle.getArgument("short", f);
+ EXPECT_EQ(-81, f);
+}
+
+// Test that trying to get an unknown argument throws an exception.
+
+TEST_F(CalloutHandleTest, ArgumentUnknownName) {
+ CalloutHandle handle(getCalloutManager());
+
+ // Set an integer
+ int a = 42;
+ handle.setArgument("integer1", a);
+ EXPECT_EQ(42, a);
+
+ // Check we can retrieve it
+ int b = 0;
+ handle.getArgument("integer1", b);
+ EXPECT_EQ(42, b);
+
+ // Check that getting an unknown name throws an exception.
+ int c = 0;
+ EXPECT_THROW(handle.getArgument("unknown", c), NoSuchArgument);
+}
+
+// Test that trying to get an argument with an incorrect type throws an
+// exception.
+
+TEST_F(CalloutHandleTest, ArgumentIncorrectType) {
+ CalloutHandle handle(getCalloutManager());
+
+ // Set an integer
+ int a = 42;
+ handle.setArgument("integer1", a);
+ EXPECT_EQ(42, a);
+
+ // Check we can retrieve it
+ long b = 0;
+ EXPECT_THROW(handle.getArgument("integer1", b), boost::bad_any_cast);
+}
+
+// Now try with some very complex types. The types cannot be defined within
+// the function and they should contain a copy constructor. For this reason,
+// a simple "struct" is used.
+
+struct Alpha {
+ int a;
+ int b;
+ Alpha(int first = 0, int second = 0) : a(first), b(second) {}
+};
+
+struct Beta {
+ int c;
+ int d;
+ Beta(int first = 0, int second = 0) : c(first), d(second) {}
+};
+
+TEST_F(CalloutHandleTest, ComplexTypes) {
+ CalloutHandle handle(getCalloutManager());
+
+ // Declare two variables of different (complex) types. (Note as to the
+ // variable names: aleph and beth are the first two letters of the Hebrew
+ // alphabet.)
+ Alpha aleph(1, 2);
+ EXPECT_EQ(1, aleph.a);
+ EXPECT_EQ(2, aleph.b);
+ handle.setArgument("aleph", aleph);
+
+ Beta beth(11, 22);
+ EXPECT_EQ(11, beth.c);
+ EXPECT_EQ(22, beth.d);
+ handle.setArgument("beth", beth);
+
+ // Ensure we can extract the data correctly.
+ Alpha aleph2;
+ EXPECT_EQ(0, aleph2.a);
+ EXPECT_EQ(0, aleph2.b);
+ handle.getArgument("aleph", aleph2);
+ EXPECT_EQ(1, aleph2.a);
+ EXPECT_EQ(2, aleph2.b);
+
+ Beta beth2;
+ EXPECT_EQ(0, beth2.c);
+ EXPECT_EQ(0, beth2.d);
+ handle.getArgument("beth", beth2);
+ EXPECT_EQ(11, beth2.c);
+ EXPECT_EQ(22, beth2.d);
+
+ // Ensure that complex types also thrown an exception if we attempt to
+ // get a context element of the wrong type.
+ EXPECT_THROW(handle.getArgument("aleph", beth), boost::bad_any_cast);
+}
+
+// Check that the context can store pointers. And also check that it respects
+// that a "pointer to X" is not the same as a "pointer to const X".
+
+TEST_F(CalloutHandleTest, PointerTypes) {
+ CalloutHandle handle(getCalloutManager());
+
+ // Declare a couple of variables, const and non-const.
+ Alpha aleph(5, 10);
+ const Beta beth(15, 20);
+
+ Alpha* pa = ℵ
+ const Beta* pcb = ℶ
+
+ // Check pointers can be set and retrieved OK.
+ handle.setArgument("non_const_pointer", pa);
+ handle.setArgument("const_pointer", pcb);
+
+ Alpha* pa2 = 0;
+ handle.getArgument("non_const_pointer", pa2);
+ EXPECT_TRUE(pa == pa2);
+
+ const Beta* pcb2 = 0;
+ handle.getArgument("const_pointer", pcb2);
+ EXPECT_TRUE(pcb == pcb2);
+
+ // Check that the "const" is protected in the context.
+ const Alpha* pca3;
+ EXPECT_THROW(handle.getArgument("non_const_pointer", pca3),
+ boost::bad_any_cast);
+
+ Beta* pb3;
+ EXPECT_THROW(handle.getArgument("const_pointer", pb3),
+ boost::bad_any_cast);
+}
+
+// Check that we can get the names of the arguments.
+
+TEST_F(CalloutHandleTest, ContextItemNames) {
+ CalloutHandle handle(getCalloutManager());
+
+ vector<string> expected_names;
+
+ expected_names.push_back("faith");
+ handle.setArgument("faith", 42);
+ expected_names.push_back("hope");
+ handle.setArgument("hope", 43);
+ expected_names.push_back("charity");
+ handle.setArgument("charity", 44);
+
+ // Get the names and check against the expected names. We'll sort
+ // both arrays to simplify the checking.
+ vector<string> actual_names = handle.getArgumentNames();
+
+ sort(actual_names.begin(), actual_names.end());
+ sort(expected_names.begin(), expected_names.end());
+ EXPECT_TRUE(expected_names == actual_names);
+}
+
+// Test that we can delete an argument.
+
+TEST_F(CalloutHandleTest, DeleteArgument) {
+ CalloutHandle handle(getCalloutManager());
+
+ int one = 1;
+ int two = 2;
+ int three = 3;
+ int four = 4;
+ int value; // Return value
+
+ handle.setArgument("one", one);
+ handle.setArgument("two", two);
+ handle.setArgument("three", three);
+ handle.setArgument("four", four);
+
+ // Delete "one".
+ handle.getArgument("one", value);
+ EXPECT_EQ(1, value);
+ handle.deleteArgument("one");
+
+ EXPECT_THROW(handle.getArgument("one", value), NoSuchArgument);
+ handle.getArgument("two", value);
+ EXPECT_EQ(2, value);
+ handle.getArgument("three", value);
+ EXPECT_EQ(3, value);
+ handle.getArgument("four", value);
+ EXPECT_EQ(4, value);
+
+ // Delete "three".
+ handle.getArgument("three", value);
+ EXPECT_EQ(3, value);
+ handle.deleteArgument("three");
+
+ EXPECT_THROW(handle.getArgument("one", value), NoSuchArgument);
+ handle.getArgument("two", value);
+ EXPECT_EQ(2, value);
+ EXPECT_THROW(handle.getArgument("three", value), NoSuchArgument);
+ handle.getArgument("four", value);
+ EXPECT_EQ(4, value);
+}
+
+// Test that we can delete all arguments.
+
+TEST_F(CalloutHandleTest, DeleteAllArguments) {
+ CalloutHandle handle(getCalloutManager());
+
+ int one = 1;
+ int two = 2;
+ int three = 3;
+ int four = 4;
+ int value; // Return value
+
+ // Set the arguments. The previous test verifies that this works.
+ handle.setArgument("one", one);
+ handle.setArgument("two", two);
+ handle.setArgument("three", three);
+ handle.setArgument("four", four);
+
+ // Delete all arguments...
+ handle.deleteAllArguments();
+
+ // ... and check that none are left.
+ EXPECT_THROW(handle.getArgument("one", value), NoSuchArgument);
+ EXPECT_THROW(handle.getArgument("two", value), NoSuchArgument);
+ EXPECT_THROW(handle.getArgument("three", value), NoSuchArgument);
+ EXPECT_THROW(handle.getArgument("four", value), NoSuchArgument);
+}
+
+// Test the "skip" flag.
+
+TEST_F(CalloutHandleTest, SkipFlag) {
+ CalloutHandle handle(getCalloutManager());
+
+ // Should be false on construction.
+ EXPECT_FALSE(handle.getSkip());
+
+ handle.setSkip(true);
+ EXPECT_TRUE(handle.getSkip());
+
+ handle.setSkip(false);
+ EXPECT_FALSE(handle.getSkip());
+}
+
+} // Anonymous namespace
diff --git a/src/lib/util/tests/callout_manager_unittest.cc b/src/lib/util/tests/callout_manager_unittest.cc
new file mode 100644
index 0000000..ca9b8ad
--- /dev/null
+++ b/src/lib/util/tests/callout_manager_unittest.cc
@@ -0,0 +1,765 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <exceptions/exceptions.h>
+#include <util/hooks/callout_handle.h>
+#include <util/hooks/callout_manager.h>
+#include <util/hooks/library_handle.h>
+#include <util/hooks/server_hooks.h>
+
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+/// @file
+/// @brief CalloutManager and LibraryHandle tests
+///
+/// These set of tests check the CalloutManager and LibraryHandle. They are
+/// together in the same file because the LibraryHandle is little more than a
+/// restricted interface to the CalloutManager, and a lot of the support
+/// structure for the tests is common.
+
+using namespace isc;
+using namespace isc::util;
+using namespace std;
+
+namespace {
+
+class CalloutManagerTest : public ::testing::Test {
+public:
+ /// @brief Constructor
+ ///
+ /// Sets up a collection of three LibraryHandle objects to use in the test.
+ CalloutManagerTest() : hooks_(new ServerHooks()) {
+
+ // Set up the server hooks
+ alpha_index_ = hooks_->registerHook("alpha");
+ beta_index_ = hooks_->registerHook("beta");
+ gamma_index_ = hooks_->registerHook("gamma");
+ delta_index_ = hooks_->registerHook("delta");
+
+ // Set up the callout manager with these hooks. Assume a maximum of
+ // four libraries.
+ callout_manager_.reset(new CalloutManager(hooks_, 10));
+
+ // Set up the callout handle.
+ callout_handle_.reset(new CalloutHandle(callout_manager_));
+
+ // Initialize the static variable.
+ callout_value_ = 0;
+ }
+
+ /// @brief Return the callout handle
+ CalloutHandle& getCalloutHandle() {
+ return (*callout_handle_);
+ }
+
+ /// @brief Return the callout manager
+ boost::shared_ptr<CalloutManager> getCalloutManager() {
+ return (callout_manager_);
+ }
+
+ boost::shared_ptr<ServerHooks> getServerHooks() {
+ return (hooks_);
+ }
+
+ /// Static variable used for accumulating information
+ static int callout_value_;
+
+ /// Hook indexes. These are somewhat ubiquitous, so are made public for
+ /// ease of reference instead of being accessible by a function.
+ int alpha_index_;
+ int beta_index_;
+ int gamma_index_;
+ int delta_index_;
+
+private:
+ /// Callout handle used in calls
+ boost::shared_ptr<CalloutHandle> callout_handle_;
+
+ /// Callout manager used for the test
+ boost::shared_ptr<CalloutManager> callout_manager_;
+
+ /// Server hooks
+ boost::shared_ptr<ServerHooks> hooks_;
+};
+
+// Definition of the static variable.
+int CalloutManagerTest::callout_value_ = 0;
+
+// Callout definitions
+//
+// The callouts defined here are structured in such a way that it is possible
+// to determine the order in which they are called and whether they are called
+// at all. The method used is simple - after a sequence of callouts, the digits
+// in the value, reading left to right, determines the order of the callouts
+// called. For example, callout one followed by two followed by three followed
+// by two followed by one results in a value of 12321.
+//
+// Functions return a zero to indicate success.
+
+extern "C" {
+int callout_general(int number) {
+ CalloutManagerTest::callout_value_ =
+ 10 * CalloutManagerTest::callout_value_ + number;
+ return (0);
+}
+
+int callout_one(CalloutHandle&) {
+ return (callout_general(1));
+}
+
+int callout_two(CalloutHandle&) {
+ return (callout_general(2));
+}
+
+int callout_three(CalloutHandle&) {
+ return (callout_general(3));
+}
+
+int callout_four(CalloutHandle&) {
+ return (callout_general(4));
+}
+
+int callout_five(CalloutHandle&) {
+ return (callout_general(5));
+}
+
+int callout_six(CalloutHandle&) {
+ return (callout_general(6));
+}
+
+int callout_seven(CalloutHandle&) {
+ return (callout_general(7));
+}
+
+// The next functions are duplicates of some of the above, but return an error.
+
+int callout_one_error(CalloutHandle& handle) {
+ (void) callout_one(handle);
+ return (1);
+}
+
+int callout_two_error(CalloutHandle& handle) {
+ (void) callout_two(handle);
+ return (1);
+}
+
+int callout_three_error(CalloutHandle& handle) {
+ (void) callout_three(handle);
+ return (1);
+}
+
+int callout_four_error(CalloutHandle& handle) {
+ (void) callout_four(handle);
+ return (1);
+}
+
+}; // extern "C"
+
+// *** Callout Tests ***
+//
+// The next set of tests check that callouts can be called.
+
+// Constructor - check that we trap bad parameters.
+
+TEST_F(CalloutManagerTest, BadConstructorParameters) {
+ boost::scoped_ptr<CalloutManager> cm;
+
+ // Invalid number of libraries
+ EXPECT_THROW(cm.reset(new CalloutManager(getServerHooks(), 0)), BadValue);
+ EXPECT_THROW(cm.reset(new CalloutManager(getServerHooks(), -1)), BadValue);
+
+ // Invalid server hooks pointer.
+ boost::shared_ptr<ServerHooks> sh;
+ EXPECT_THROW(cm.reset(new CalloutManager(sh, 4)), BadValue);
+}
+
+// Check the number of libraries is reported successfully.
+
+TEST_F(CalloutManagerTest, GetNumLibraries) {
+ boost::scoped_ptr<CalloutManager> cm;
+
+ // Check two valid values of number of libraries to ensure that the
+ // GetNumLibraries() returns the value set.
+ EXPECT_NO_THROW(cm.reset(new CalloutManager(getServerHooks(), 4)));
+ EXPECT_EQ(4, cm->getNumLibraries());
+
+ EXPECT_NO_THROW(cm.reset(new CalloutManager(getServerHooks(), 42)));
+ EXPECT_EQ(42, cm->getNumLibraries());
+}
+
+// Check that we can only set the current library index to the correct values.
+
+TEST_F(CalloutManagerTest, CheckLibraryIndex) {
+ // Check valid indexes
+ for (int i = 0; i < 4; ++i) {
+ EXPECT_NO_THROW(getCalloutManager()->setLibraryIndex(i));
+ }
+
+ // Check invalid ones
+ EXPECT_THROW(getCalloutManager()->setLibraryIndex(-1), NoSuchLibrary);
+ EXPECT_THROW(getCalloutManager()->setLibraryIndex(15), NoSuchLibrary);
+}
+
+// Check that we can only register callouts on valid hook names.
+
+TEST_F(CalloutManagerTest, ValidHookNames) {
+ getCalloutManager()->setLibraryIndex(0);
+ EXPECT_NO_THROW(getCalloutManager()->registerCallout("alpha", callout_one));
+ EXPECT_THROW(getCalloutManager()->registerCallout("unknown", callout_one),
+ NoSuchHook);
+}
+
+
+// Check we can register callouts appropriately.
+
+TEST_F(CalloutManagerTest, RegisterCallout) {
+ // Ensure that no callouts are attached to any of the hooks.
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
+
+ // Set up so that hooks "alpha" and "beta" have callouts attached from a
+ // different libraries.
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("alpha", callout_one);
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->registerCallout("beta", callout_two);
+
+ // Check all is as expected.
+ EXPECT_TRUE(getCalloutManager()->calloutsPresent(alpha_index_));
+ EXPECT_TRUE(getCalloutManager()->calloutsPresent(beta_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
+
+ // Check that calling the callouts returns as expected. (This is also a
+ // test of the callCallouts method.)
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(1, callout_value_);
+
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(beta_index_, getCalloutHandle());
+ EXPECT_EQ(2, callout_value_);
+
+ // Register some more callouts from different libraries on hook "alpha".
+ getCalloutManager()->setLibraryIndex(2);
+ getCalloutManager()->registerCallout("alpha", callout_three);
+ getCalloutManager()->registerCallout("alpha", callout_four);
+ getCalloutManager()->setLibraryIndex(3);
+ getCalloutManager()->registerCallout("alpha", callout_five);
+
+ // Check it is as expected.
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(1345, callout_value_);
+
+ // ... and check the additional callouts were not registered on the "beta"
+ // hook.
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(beta_index_, getCalloutHandle());
+ EXPECT_EQ(2, callout_value_);
+
+ // Add another callout to hook "alpha" from library index 2 - this should
+ // appear at the end of the callout list for that library.
+ getCalloutManager()->setLibraryIndex(2);
+ getCalloutManager()->registerCallout("alpha", callout_six);
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(13465, callout_value_);
+
+ // Add a callout from library index 1 - this should appear between the
+ // callouts from library index 0 and linrary index 2.
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->registerCallout("alpha", callout_seven);
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(173465, callout_value_);
+}
+
+// Check the "calloutsPresent()" method.
+
+TEST_F(CalloutManagerTest, CalloutsPresent) {
+ // Ensure that no callouts are attached to any of the hooks.
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
+
+ // Set up so that hooks "alpha", "beta" and "delta" have callouts attached
+ // to them, and callout "gamma" does not. (In the statements below, the
+ // exact callouts attached to a hook are not relevant - only the fact
+ // that some callouts are). Chose the libraries for which the callouts
+ // are registered randomly.
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("alpha", callout_one);
+
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->registerCallout("alpha", callout_two);
+ getCalloutManager()->registerCallout("beta", callout_two);
+
+ getCalloutManager()->setLibraryIndex(3);
+ getCalloutManager()->registerCallout("alpha", callout_three);
+ getCalloutManager()->registerCallout("delta", callout_four);
+
+ // Check all is as expected.
+ EXPECT_TRUE(getCalloutManager()->calloutsPresent(alpha_index_));
+ EXPECT_TRUE(getCalloutManager()->calloutsPresent(beta_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
+ EXPECT_TRUE(getCalloutManager()->calloutsPresent(delta_index_));
+
+ // Check we fail on an invalid hook index.
+ EXPECT_THROW(getCalloutManager()->calloutsPresent(42), NoSuchHook);
+ EXPECT_THROW(getCalloutManager()->calloutsPresent(-1), NoSuchHook);
+}
+
+// Test that calling a hook with no callouts on it returns success.
+
+TEST_F(CalloutManagerTest, CallNoCallouts) {
+ // Ensure that no callouts are attached to any of the hooks.
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
+
+ // Call the callouts on an arbitrary hook and ensure that nothing happens.
+ callout_value_ = 475;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(475, callout_value_); // Unchanged
+}
+
+// Test that the callouts are called in the correct order (i.e. the callouts
+// from the first library in the order they were registered, then the callouts
+// from the second library in the order they were registered etc.)
+
+TEST_F(CalloutManagerTest, CallCalloutsSuccess) {
+ // Ensure that no callouts are attached to any of the hooks.
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
+
+ // Each library contributes one callout on hook "alpha".
+ callout_value_ = 0;
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->registerCallout("alpha", callout_one);
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->registerCallout("alpha", callout_two);
+ getCalloutManager()->setLibraryIndex(2);
+ getCalloutManager()->registerCallout("alpha", callout_three);
+ getCalloutManager()->setLibraryIndex(3);
+ getCalloutManager()->registerCallout("alpha", callout_four);
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(1234, callout_value_);
+
+ // Do a random selection of callouts on hook "beta".
+ callout_value_ = 0;
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("beta", callout_one);
+ getCalloutManager()->registerCallout("beta", callout_three);
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->registerCallout("beta", callout_two);
+ getCalloutManager()->setLibraryIndex(3);
+ getCalloutManager()->registerCallout("beta", callout_four);
+ getCalloutManager()->callCallouts(beta_index_, getCalloutHandle());
+ EXPECT_EQ(1324, callout_value_);
+
+ // Ensure that calling the callouts on a hook with no callouts works.
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(gamma_index_, getCalloutHandle());
+ EXPECT_EQ(0, callout_value_);
+}
+
+// Test that the callouts are called in order, but that callouts occurring
+// after a callout that returns an error are not called.
+//
+// (Note: in this test, the callouts that return an error set the value of
+// callout_value_ before they return the error code.)
+
+TEST_F(CalloutManagerTest, CallCalloutsError) {
+ // Ensure that no callouts are attached to any of the hooks.
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
+
+ // Each library contributing one callout on hook "alpha". The first callout
+ // returns an error (after adding its value to the result).
+ callout_value_ = 0;
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("alpha", callout_one_error);
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->registerCallout("alpha", callout_two);
+ getCalloutManager()->setLibraryIndex(2);
+ getCalloutManager()->registerCallout("alpha", callout_three);
+ getCalloutManager()->setLibraryIndex(3);
+ getCalloutManager()->registerCallout("alpha", callout_four);
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(1234, callout_value_);
+
+ // Each library contributing multiple callouts on hook "beta". The last
+ // callout on the first library returns an error.
+ callout_value_ = 0;
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("beta", callout_one);
+ getCalloutManager()->registerCallout("beta", callout_one_error);
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->registerCallout("beta", callout_two);
+ getCalloutManager()->registerCallout("beta", callout_two);
+ getCalloutManager()->registerCallout("beta", callout_three);
+ getCalloutManager()->registerCallout("beta", callout_three);
+ getCalloutManager()->setLibraryIndex(3);
+ getCalloutManager()->registerCallout("beta", callout_four);
+ getCalloutManager()->registerCallout("beta", callout_four);
+ getCalloutManager()->callCallouts(beta_index_, getCalloutHandle());
+ EXPECT_EQ(11223344, callout_value_);
+
+ // A callout in a random position in the callout list returns an error.
+ callout_value_ = 0;
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("gamma", callout_one);
+ getCalloutManager()->registerCallout("gamma", callout_one);
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->registerCallout("gamma", callout_two);
+ getCalloutManager()->registerCallout("gamma", callout_two);
+ getCalloutManager()->setLibraryIndex(3);
+ getCalloutManager()->registerCallout("gamma", callout_four_error);
+ getCalloutManager()->registerCallout("gamma", callout_four);
+ getCalloutManager()->callCallouts(gamma_index_, getCalloutHandle());
+ EXPECT_EQ(112244, callout_value_);
+
+ // The last callout on a hook returns an error.
+ callout_value_ = 0;
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("delta", callout_one);
+ getCalloutManager()->registerCallout("delta", callout_one);
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->registerCallout("delta", callout_two);
+ getCalloutManager()->registerCallout("delta", callout_two);
+ getCalloutManager()->setLibraryIndex(2);
+ getCalloutManager()->registerCallout("delta", callout_three);
+ getCalloutManager()->registerCallout("delta", callout_three);
+ getCalloutManager()->setLibraryIndex(3);
+ getCalloutManager()->registerCallout("delta", callout_four);
+ getCalloutManager()->registerCallout("delta", callout_four_error);
+ getCalloutManager()->callCallouts(delta_index_, getCalloutHandle());
+ EXPECT_EQ(11223344, callout_value_);
+}
+
+// Now test that we can deregister a single callout on a hook.
+
+TEST_F(CalloutManagerTest, DeregisterSingleCallout) {
+ // Ensure that no callouts are attached to any of the hooks.
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
+
+ // Add a callout to hook "alpha" and check it is added correctly.
+ callout_value_ = 0;
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("alpha", callout_two);
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(2, callout_value_);
+
+ // Remove it and check that the no callouts are present. We have to reset
+ // the current library index here as it was invalidated by the call
+ // to callCallouts().
+ getCalloutManager()->setLibraryIndex(0);
+ EXPECT_TRUE(getCalloutManager()->calloutsPresent(alpha_index_));
+ EXPECT_TRUE(getCalloutManager()->deregisterCallout("alpha", callout_two));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
+}
+
+// Now test that we can deregister a single callout on a hook that has multiple
+// callouts from the same library.
+
+TEST_F(CalloutManagerTest, DeregisterSingleCalloutSameLibrary) {
+ // Ensure that no callouts are attached to any of the hooks.
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
+
+ // Add multiple callouts to hook "alpha".
+ callout_value_ = 0;
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("alpha", callout_one);
+ getCalloutManager()->registerCallout("alpha", callout_two);
+ getCalloutManager()->registerCallout("alpha", callout_three);
+ getCalloutManager()->registerCallout("alpha", callout_four);
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(1234, callout_value_);
+
+ // Remove the callout_two callout. We have to reset the current library
+ // index here as it was invalidated by the call to callCallouts().
+ getCalloutManager()->setLibraryIndex(0);
+ EXPECT_TRUE(getCalloutManager()->deregisterCallout("alpha", callout_two));
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(134, callout_value_);
+
+ // Try removing it again.
+ getCalloutManager()->setLibraryIndex(0);
+ EXPECT_FALSE(getCalloutManager()->deregisterCallout("alpha", callout_two));
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(134, callout_value_);
+
+}
+
+// Check we can deregister multiple callouts from the same library.
+
+TEST_F(CalloutManagerTest, DeregisterMultipleCalloutsSameLibrary) {
+ // Ensure that no callouts are attached to any of the hooks.
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
+
+ // Each library contributes one callout on hook "alpha".
+ callout_value_ = 0;
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("alpha", callout_one);
+ getCalloutManager()->registerCallout("alpha", callout_two);
+ getCalloutManager()->registerCallout("alpha", callout_one);
+ getCalloutManager()->registerCallout("alpha", callout_two);
+ getCalloutManager()->registerCallout("alpha", callout_three);
+ getCalloutManager()->registerCallout("alpha", callout_four);
+ getCalloutManager()->registerCallout("alpha", callout_three);
+ getCalloutManager()->registerCallout("alpha", callout_four);
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(12123434, callout_value_);
+
+ // Remove the callout_two callouts. We have to reset the current library
+ // index here as it was invalidated by the call to callCallouts().
+ getCalloutManager()->setLibraryIndex(0);
+ EXPECT_TRUE(getCalloutManager()->deregisterCallout("alpha", callout_two));
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(113434, callout_value_);
+
+ // Try removing multiple callouts that includes one at the end of the
+ // list of callouts.
+ getCalloutManager()->setLibraryIndex(0);
+ EXPECT_TRUE(getCalloutManager()->deregisterCallout("alpha", callout_four));
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(1133, callout_value_);
+
+ // ... and from the start.
+ getCalloutManager()->setLibraryIndex(0);
+ EXPECT_TRUE(getCalloutManager()->deregisterCallout("alpha", callout_one));
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(33, callout_value_);
+
+ // ... and the remaining callouts.
+ getCalloutManager()->setLibraryIndex(0);
+ EXPECT_TRUE(getCalloutManager()->deregisterCallout("alpha", callout_three));
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(0, callout_value_);
+
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
+}
+
+// Check we can deregister multiple callouts from multiple libraries.
+
+TEST_F(CalloutManagerTest, DeregisterMultipleCalloutsMultipleLibraries) {
+ // Ensure that no callouts are attached to any of the hooks.
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
+
+ // Each library contributes two callouts to hook "alpha".
+ callout_value_ = 0;
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("alpha", callout_one);
+ getCalloutManager()->registerCallout("alpha", callout_two);
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->registerCallout("alpha", callout_three);
+ getCalloutManager()->registerCallout("alpha", callout_four);
+ getCalloutManager()->setLibraryIndex(2);
+ getCalloutManager()->registerCallout("alpha", callout_five);
+ getCalloutManager()->registerCallout("alpha", callout_two);
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(123452, callout_value_);
+
+ // Remove the callout_two callout from library 0. It should not affect
+ // the second callout_two callout registered by library 2.
+ getCalloutManager()->setLibraryIndex(0);
+ EXPECT_TRUE(getCalloutManager()->deregisterCallout("alpha", callout_two));
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(13452, callout_value_);
+}
+
+// Check we can deregister all callouts from a single library.
+
+TEST_F(CalloutManagerTest, DeregisterAllCallouts) {
+ // Ensure that no callouts are attached to hook one.
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
+
+ // Each library contributes two callouts to hook "alpha".
+ callout_value_ = 0;
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("alpha", callout_one);
+ getCalloutManager()->registerCallout("alpha", callout_two);
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->registerCallout("alpha", callout_three);
+ getCalloutManager()->registerCallout("alpha", callout_four);
+ getCalloutManager()->setLibraryIndex(2);
+ getCalloutManager()->registerCallout("alpha", callout_five);
+ getCalloutManager()->registerCallout("alpha", callout_six);
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(123456, callout_value_);
+
+ // Remove all callouts from library index 1.
+ getCalloutManager()->setLibraryIndex(1);
+ EXPECT_TRUE(getCalloutManager()->deregisterAllCallouts("alpha"));
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(1256, callout_value_);
+
+ // Remove all callouts from library index 2.
+ getCalloutManager()->setLibraryIndex(2);
+ EXPECT_TRUE(getCalloutManager()->deregisterAllCallouts("alpha"));
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(12, callout_value_);
+}
+
+// Check that we can register/deregister callouts on different libraries
+// and different hooks, and that the callout instances are regarded as
+// unique and do not affect one another.
+
+TEST_F(CalloutManagerTest, MultipleCalloutsLibrariesHooks) {
+ // Ensure that no callouts are attached to any of the hooks.
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
+
+ // Register callouts on the alpha hook.
+ callout_value_ = 0;
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("alpha", callout_one);
+ getCalloutManager()->registerCallout("alpha", callout_two);
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->registerCallout("alpha", callout_three);
+ getCalloutManager()->registerCallout("alpha", callout_four);
+ getCalloutManager()->setLibraryIndex(2);
+ getCalloutManager()->registerCallout("alpha", callout_five);
+ getCalloutManager()->registerCallout("alpha", callout_two);
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(123452, callout_value_);
+
+ // Register the same callouts on the beta hook, and check that those
+ // on the alpha hook are not affected.
+ callout_value_ = 0;
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("beta", callout_five);
+ getCalloutManager()->registerCallout("beta", callout_one);
+ getCalloutManager()->setLibraryIndex(2);
+ getCalloutManager()->registerCallout("beta", callout_four);
+ getCalloutManager()->registerCallout("beta", callout_three);
+ getCalloutManager()->callCallouts(beta_index_, getCalloutHandle());
+ EXPECT_EQ(5143, callout_value_);
+
+ // Check that the order of callouts on the alpha hook has not been affected.
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(123452, callout_value_);
+
+ // Remove callout four from beta and check that alpha is not affected.
+ getCalloutManager()->setLibraryIndex(2);
+ EXPECT_TRUE(getCalloutManager()->deregisterCallout("beta", callout_four));
+
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(beta_index_, getCalloutHandle());
+ EXPECT_EQ(513, callout_value_);
+
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(123452, callout_value_);
+}
+
+// Library handle tests. As by inspection the LibraryHandle can be seen to be
+// little more than shell around CalloutManager, only a basic set of tests
+// is done concerning registration and deregistration of functions.
+//
+// More extensive tests (i.e. checking that when a callout is called it can
+// only register and deregister callouts within its library) require that
+// the CalloutHandle object pass the appropriate LibraryHandle to the
+// callout. These tests are done in the handles_unittest tests.
+
+TEST_F(CalloutManagerTest, LibraryHandleRegistration) {
+ // Ensure that no callouts are attached to any of the hooks.
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
+
+ // Set up so that hooks "alpha" and "beta" have callouts attached from a
+ // different libraries.
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->getLibraryHandle().registerCallout("alpha",
+ callout_one);
+ getCalloutManager()->getLibraryHandle().registerCallout("alpha",
+ callout_two);
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->getLibraryHandle().registerCallout("alpha",
+ callout_three);
+ getCalloutManager()->getLibraryHandle().registerCallout("alpha",
+ callout_four);
+
+ // Check all is as expected.
+ EXPECT_TRUE(getCalloutManager()->calloutsPresent(alpha_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
+
+ // Check that calling the callouts returns as expected. (This is also a
+ // test of the callCallouts method.)
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(1234, callout_value_);
+
+ // Deregister a callout on library index 0 (after we check we can't
+ // deregister it through library index 1).
+ getCalloutManager()->setLibraryIndex(1);
+ EXPECT_FALSE(getCalloutManager()->deregisterCallout("alpha", callout_two));
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(1234, callout_value_);
+
+ getCalloutManager()->setLibraryIndex(0);
+ EXPECT_TRUE(getCalloutManager()->deregisterCallout("alpha", callout_two));
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(134, callout_value_);
+
+ // Deregister all callouts on library index 1.
+ getCalloutManager()->setLibraryIndex(1);
+ EXPECT_TRUE(getCalloutManager()->deregisterAllCallouts("alpha"));
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(1, callout_value_);
+}
+
+
+
+} // Anonymous namespace
diff --git a/src/lib/util/tests/handles_unittest.cc b/src/lib/util/tests/handles_unittest.cc
new file mode 100644
index 0000000..5e5b237
--- /dev/null
+++ b/src/lib/util/tests/handles_unittest.cc
@@ -0,0 +1,917 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <util/hooks/callout_handle.h>
+#include <util/hooks/callout_manager.h>
+#include <util/hooks/library_handle.h>
+#include <util/hooks/server_hooks.h>
+
+#include <boost/lexical_cast.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+
+#include <algorithm>
+#include <string>
+
+/// @file
+/// CalloutHandle/LibraryHandle interaction tests
+///
+/// This file holds unit tests checking the interaction between the
+/// CalloutHandle/LibraryHandle and CalloutManager classes. In particular,
+/// they check that:
+///
+/// - A CalloutHandle's context is shared between callouts from the same
+/// library, but there is a separate context for each library.
+///
+/// - The various methods manipulating the items in the CalloutHandle's context
+/// work correctly.
+///
+/// - An active callout can only modify the registration of callouts registered
+/// by its own library.
+
+using namespace isc::util;
+using namespace std;
+
+namespace {
+
+class HandlesTest : public ::testing::Test {
+public:
+ /// @brief Constructor
+ ///
+ /// Sets up the various elements used in each test.
+ HandlesTest() : hooks_(new ServerHooks()), manager_()
+ {
+ // Set up four hooks, although through gamma
+ alpha_index_ = hooks_->registerHook("alpha");
+ beta_index_ = hooks_->registerHook("beta");
+ gamma_index_ = hooks_->registerHook("gamma");
+ delta_index_ = hooks_->registerHook("delta");
+
+ // Set up for three libraries.
+ manager_.reset(new CalloutManager(hooks_, 3));
+
+ // Initialize remaining variables.
+ common_string_ = "";
+ }
+
+ /// @brief Return callout manager
+ boost::shared_ptr<CalloutManager> getCalloutManager() {
+ return (manager_);
+ }
+
+ /// Hook indexes - these are frequently accessed, so are accessed directly.
+ int alpha_index_;
+ int beta_index_;
+ int gamma_index_;
+ int delta_index_;
+
+ /// String accessible by all callouts whatever the library
+ static std::string common_string_;
+
+private:
+ /// Server hooks
+ boost::shared_ptr<ServerHooks> hooks_;
+
+ /// Callout manager. Declared static so that the callout functions can
+ /// access it.
+ boost::shared_ptr<CalloutManager> manager_;
+};
+
+/// Define the common string
+std::string HandlesTest::common_string_;
+
+
+// The next set of functions define the callouts used by the tests. They
+// manipulate the data in such a way that callouts called - and the order in
+// which they were called - can be determined. The functions also check that
+// the "callout context" data areas are separate.
+//
+// Three libraries are assumed, and each supplies four callouts. All callouts
+// manipulate two context elements the CalloutHandle, the elements being called
+// "string" and "int" (which describe the type of data manipulated).
+//
+// For the string item, each callout shifts data to the left and inserts its own
+// data. The data is a string of the form "nmc", where "n" is the number of
+// the library, "m" is the callout number and "y" is the indication of what
+// callout handle was passed as an argument ("1" or "2": "0" is used when no
+// identification has been set in the callout handle).
+//
+// For simplicity, and to cut down the number of functions actually written,
+// the callout indicator ("1" or "2") ) used in the in the CalloutHandle
+// functions is passed via a CalloutArgument. The argument is named "string":
+// use of a name the same as that of one of the context elements serves as a
+// check that the argument name space and argument context space are separate.
+//
+// For integer data, the value starts at zero and an increment is added on each
+// call. This increment is equal to:
+//
+// 100 * library number + 10 * callout number + callout handle
+//
+// Although this gives less information than the string value, the reasons for
+// using it are:
+//
+// - It is a separate item in the context, so checks that the context can
+// handle multiple items.
+// - It provides an item that can be deleted by the context deletion
+// methods.
+
+
+// Values set in the CalloutHandle context. There are three libraries, so
+// there are three contexts for the callout, one for each library.
+
+std::string& resultCalloutString(int index) {
+ static std::string result_callout_string[3];
+ return (result_callout_string[index]);
+}
+
+int& resultCalloutInt(int index) {
+ static int result_callout_int[3];
+ return (result_callout_int[index]);
+}
+
+// A simple function to zero the results.
+
+static void zero_results() {
+ for (int i = 0; i < 3; ++i) {
+ resultCalloutString(i) = "";
+ resultCalloutInt(i) = 0;
+ }
+}
+
+
+// Library callouts.
+
+// Common code for setting the callout context values.
+
+int
+execute(CalloutHandle& callout_handle, int library_num, int callout_num) {
+
+ // Obtain the callout handle number
+ int handle_num = 0;
+ try {
+ callout_handle.getArgument("handle_num", handle_num);
+ } catch (const NoSuchArgument&) {
+ // handle_num argument not set: this is the case in the tests where
+ // the context_create hook check is tested.
+ handle_num = 0;
+ }
+
+ // Create the basic data to be appended to the context value.
+ int idata = 100 * library_num + 10 * callout_num + handle_num;
+ string sdata = boost::lexical_cast<string>(idata);
+
+ // Get the context data. As before, this will not exist for the first
+ // callout called. (In real life, the library should create it when the
+ // "context_create" hook gets called before any packet processing takes
+ // place.)
+ int int_value = 0;
+ try {
+ callout_handle.getContext("int", int_value);
+ } catch (const NoSuchCalloutContext&) {
+ int_value = 0;
+ }
+
+ string string_value = "";
+ try {
+ callout_handle.getContext("string", string_value);
+ } catch (const NoSuchCalloutContext&) {
+ string_value = "";
+ }
+
+ // Update the values and set them back in the callout context.
+ int_value += idata;
+ callout_handle.setContext("int", int_value);
+
+ string_value += sdata;
+ callout_handle.setContext("string", string_value);
+
+ return (0);
+}
+
+// The following functions are the actual callouts - the name is of the
+// form "callout_<library number>_<callout number>"
+
+int
+callout11(CalloutHandle& callout_handle) {
+ return (execute(callout_handle, 1, 1));
+}
+
+int
+callout12(CalloutHandle& callout_handle) {
+ return (execute(callout_handle, 1, 2));
+}
+
+int
+callout13(CalloutHandle& callout_handle) {
+ return (execute(callout_handle, 1, 3));
+}
+
+int
+callout21(CalloutHandle& callout_handle) {
+ return (execute(callout_handle, 2, 1));
+}
+
+int
+callout22(CalloutHandle& callout_handle) {
+ return (execute(callout_handle, 2, 2));
+}
+
+int
+callout23(CalloutHandle& callout_handle) {
+ return (execute(callout_handle, 2, 3));
+}
+
+int
+callout31(CalloutHandle& callout_handle) {
+ return (execute(callout_handle, 3, 1));
+}
+
+int
+callout32(CalloutHandle& callout_handle) {
+ return (execute(callout_handle, 3, 2));
+}
+
+int
+callout33(CalloutHandle& callout_handle) {
+ return (execute(callout_handle, 3, 3));
+}
+
+// Common callout code for the fourth hook (which makes the data available for
+// checking). It copies the library and callout context data to the global
+// variables.
+
+int printExecute(CalloutHandle& callout_handle, int library_num) {
+ callout_handle.getContext("string", resultCalloutString(library_num - 1));
+ callout_handle.getContext("int", resultCalloutInt(library_num - 1));
+
+ return (0);
+}
+
+// These are the actual callouts.
+
+int
+print1(CalloutHandle& callout_handle) {
+ return (printExecute(callout_handle, 1));
+}
+
+int
+print2(CalloutHandle& callout_handle) {
+ return (printExecute(callout_handle, 2));
+}
+
+int
+print3(CalloutHandle& callout_handle) {
+ return (printExecute(callout_handle, 3));
+}
+
+// This test checks the many-faced nature of the context for the CalloutContext.
+
+TEST_F(HandlesTest, ContextAccessCheck) {
+ // Register callouts for the different libraries.
+ CalloutHandle handle(getCalloutManager());
+
+ // Library 0.
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("alpha", callout11);
+ getCalloutManager()->registerCallout("beta", callout12);
+ getCalloutManager()->registerCallout("gamma", callout13);
+ getCalloutManager()->registerCallout("delta", print1);
+
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->registerCallout("alpha", callout21);
+ getCalloutManager()->registerCallout("beta", callout22);
+ getCalloutManager()->registerCallout("gamma", callout23);
+ getCalloutManager()->registerCallout("delta", print2);
+
+ getCalloutManager()->setLibraryIndex(2);
+ getCalloutManager()->registerCallout("alpha", callout31);
+ getCalloutManager()->registerCallout("beta", callout32);
+ getCalloutManager()->registerCallout("gamma", callout33);
+ getCalloutManager()->registerCallout("delta", print3);
+
+ // Create the callout handles and distinguish them by setting the
+ // "handle_num" argument.
+ CalloutHandle callout_handle_1(getCalloutManager());
+ callout_handle_1.setArgument("handle_num", static_cast<int>(1));
+
+ CalloutHandle callout_handle_2(getCalloutManager());
+ callout_handle_2.setArgument("handle_num", static_cast<int>(2));
+
+ // Now call the callouts attached to the first three hooks. Each hook is
+ // called twice (once for each callout handle) before the next hook is
+ // called.
+ getCalloutManager()->callCallouts(alpha_index_, callout_handle_1);
+ getCalloutManager()->callCallouts(alpha_index_, callout_handle_2);
+ getCalloutManager()->callCallouts(beta_index_, callout_handle_1);
+ getCalloutManager()->callCallouts(beta_index_, callout_handle_2);
+ getCalloutManager()->callCallouts(gamma_index_, callout_handle_1);
+ getCalloutManager()->callCallouts(gamma_index_, callout_handle_2);
+
+ // Get the results for each callout (the callout on hook "delta" copies
+ // the context values into a location the test can access). Explicitly
+ // zero the variables before getting the results so we are certain that
+ // the values are the results of the callouts.
+
+ zero_results();
+
+ // To explain the expected callout context results.
+ //
+ // Each callout handle maintains a separate context for each library. When
+ // the first call to callCallouts() is made, "111" gets appended to
+ // the context for library 1 maintained by the first callout handle, "211"
+ // gets appended to the context maintained for library 2, and "311" to
+ // the context maintained for library 3. In each case, the first digit
+ // corresponds to the library number, the second to the callout number and
+ // the third to the "handle_num" of the callout handle. For the first call
+ // to callCallouts, handle 1 is used, so the last digit is always 1.
+ //
+ // The next call to callCallouts() calls the same callouts but for the
+ // second callout handle. It also maintains three contexts (one for
+ // each library) and they will get "112", "212", "312" appended to
+ // them. The explanation for the digits is the same as before, except that
+ // in this case, the callout handle is number 2, so the third digit is
+ // always 2. These additions don't affect the contexts maintained by
+ // callout handle 1.
+ //
+ // The process is then repeated for hooks "beta" and "gamma" which, for
+ // callout handle 1, append "121", "221" and "321" for hook "beta" and
+ // "311", "321" and "331" for hook "gamma".
+ //
+ // The expected integer values can be found by summing up the values
+ // corresponding to the elements of the strings.
+
+ // At this point, we have only called the "print" function for callout
+ // handle "1", so the following results are checking the context values
+ // maintained in that callout handle.
+
+ getCalloutManager()->callCallouts(delta_index_, callout_handle_1);
+ EXPECT_EQ("111121131", resultCalloutString(0));
+ EXPECT_EQ("211221231", resultCalloutString(1));
+ EXPECT_EQ("311321331", resultCalloutString(2));
+
+ EXPECT_EQ((111 + 121 + 131), resultCalloutInt(0));
+ EXPECT_EQ((211 + 221 + 231), resultCalloutInt(1));
+ EXPECT_EQ((311 + 321 + 331), resultCalloutInt(2));
+
+ // Repeat the checks for callout 2.
+
+ zero_results();
+ getCalloutManager()->callCallouts(delta_index_, callout_handle_2);
+
+ EXPECT_EQ((112 + 122 + 132), resultCalloutInt(0));
+ EXPECT_EQ((212 + 222 + 232), resultCalloutInt(1));
+ EXPECT_EQ((312 + 322 + 332), resultCalloutInt(2));
+
+ EXPECT_EQ("112122132", resultCalloutString(0));
+ EXPECT_EQ("212222232", resultCalloutString(1));
+ EXPECT_EQ("312322332", resultCalloutString(2));
+}
+
+// Now repeat the test, but add a deletion callout to the list. The "beta"
+// hook of library 2 will have an additional callout to delete the "int"
+// element: the same hook for library 3 will delete both elements. In
+// addition, the names of context elements for the libraries at this point
+// will be printed.
+
+// List of context item names.
+
+vector<string>&
+getItemNames(int index) {
+ static vector<string> context_items[3];
+ return (context_items[index]);
+}
+
+// Context item deletion functions.
+
+int
+deleteIntContextItem(CalloutHandle& handle) {
+ handle.deleteContext("int");
+ return (0);
+}
+
+int
+deleteAllContextItems(CalloutHandle& handle) {
+ handle.deleteAllContext();
+ return (0);
+}
+
+// Generic print function - copy names in sorted order.
+
+int
+printContextNamesExecute(CalloutHandle& handle, int library_num) {
+ const int index = library_num - 1;
+ getItemNames(index) = handle.getContextNames();
+ sort(getItemNames(index).begin(), getItemNames(index).end());
+ return (0);
+}
+
+int
+printContextNames1(CalloutHandle& handle) {
+ return (printContextNamesExecute(handle, 1));
+}
+
+int
+printContextNames2(CalloutHandle& handle) {
+ return (printContextNamesExecute(handle, 2));
+}
+
+int
+printContextNames3(CalloutHandle& handle) {
+ return (printContextNamesExecute(handle, 3));
+}
+
+// Perform the test including deletion of context items.
+
+TEST_F(HandlesTest, ContextDeletionCheck) {
+
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("alpha", callout11);
+ getCalloutManager()->registerCallout("beta", callout12);
+ getCalloutManager()->registerCallout("beta", printContextNames1);
+ getCalloutManager()->registerCallout("gamma", callout13);
+ getCalloutManager()->registerCallout("delta", print1);
+
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->registerCallout("alpha", callout21);
+ getCalloutManager()->registerCallout("beta", callout22);
+ getCalloutManager()->registerCallout("beta", deleteIntContextItem);
+ getCalloutManager()->registerCallout("beta", printContextNames2);
+ getCalloutManager()->registerCallout("gamma", callout23);
+ getCalloutManager()->registerCallout("delta", print2);
+
+ getCalloutManager()->setLibraryIndex(2);
+ getCalloutManager()->registerCallout("alpha", callout31);
+ getCalloutManager()->registerCallout("beta", callout32);
+ getCalloutManager()->registerCallout("beta", deleteAllContextItems);
+ getCalloutManager()->registerCallout("beta", printContextNames3);
+ getCalloutManager()->registerCallout("gamma", callout33);
+ getCalloutManager()->registerCallout("delta", print3);
+
+ // Create the callout handles and distinguish them by setting the "long"
+ // argument.
+ CalloutHandle callout_handle_1(getCalloutManager());
+ callout_handle_1.setArgument("handle_num", static_cast<int>(1));
+
+ CalloutHandle callout_handle_2(getCalloutManager());
+ callout_handle_2.setArgument("handle_num", static_cast<int>(2));
+
+ // Now call the callouts attached to the first three hooks. Each hook is
+ // called twice (once for each callout handle) before the next hook is
+ // called.
+ getCalloutManager()->callCallouts(alpha_index_, callout_handle_1);
+ getCalloutManager()->callCallouts(alpha_index_, callout_handle_2);
+ getCalloutManager()->callCallouts(beta_index_, callout_handle_1);
+ getCalloutManager()->callCallouts(beta_index_, callout_handle_2);
+ getCalloutManager()->callCallouts(gamma_index_, callout_handle_1);
+ getCalloutManager()->callCallouts(gamma_index_, callout_handle_2);
+
+ // Get the results for each callout. Explicitly zero the variables before
+ // getting the results so we are certain that the values are the results
+ // of the callouts.
+
+ zero_results();
+ getCalloutManager()->callCallouts(delta_index_, callout_handle_1);
+
+ // The logic by which the expected results are arrived at is described
+ // in the ContextAccessCheck test. The results here are different
+ // because context items have been modified along the way.
+
+ EXPECT_EQ((111 + 121 + 131), resultCalloutInt(0));
+ EXPECT_EQ(( 231), resultCalloutInt(1));
+ EXPECT_EQ(( 331), resultCalloutInt(2));
+
+ EXPECT_EQ("111121131", resultCalloutString(0));
+ EXPECT_EQ("211221231", resultCalloutString(1));
+ EXPECT_EQ( "331", resultCalloutString(2));
+
+ // Repeat the checks for callout handle 2.
+
+ zero_results();
+ getCalloutManager()->callCallouts(delta_index_, callout_handle_2);
+
+ EXPECT_EQ((112 + 122 + 132), resultCalloutInt(0));
+ EXPECT_EQ(( 232), resultCalloutInt(1));
+ EXPECT_EQ(( 332), resultCalloutInt(2));
+
+ EXPECT_EQ("112122132", resultCalloutString(0));
+ EXPECT_EQ("212222232", resultCalloutString(1));
+ EXPECT_EQ( "332", resultCalloutString(2));
+
+ // ... and check what the names of the context items are after the callouts
+ // for hook "beta". We know they are in sorted order.
+
+ EXPECT_EQ(2, getItemNames(0).size());
+ EXPECT_EQ(string("int"), getItemNames(0)[0]);
+ EXPECT_EQ(string("string"), getItemNames(0)[1]);
+
+ EXPECT_EQ(1, getItemNames(1).size());
+ EXPECT_EQ(string("string"), getItemNames(1)[0]);
+
+ EXPECT_EQ(0, getItemNames(2).size());
+}
+
+// Tests that the CalloutHandle's constructor and destructor call the
+// context_create and context_destroy callbacks (if registered). For
+// simplicity, we'll use the same callout functions as used above.
+
+TEST_F(HandlesTest, ConstructionDestructionCallouts) {
+
+ // Register context callouts.
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("context_create", callout11);
+ getCalloutManager()->registerCallout("context_create", print1);
+ getCalloutManager()->registerCallout("context_destroy", callout12);
+ getCalloutManager()->registerCallout("context_destroy", print1);
+
+ // Create the CalloutHandle and check that the constructor callout
+ // has run.
+ zero_results();
+ boost::scoped_ptr<CalloutHandle>
+ callout_handle(new CalloutHandle(getCalloutManager()));
+ EXPECT_EQ("110", resultCalloutString(0));
+ EXPECT_EQ(110, resultCalloutInt(0));
+
+ // Check that the destructor callout runs. Note that the "print1" callout
+ // didn't destroy the library context - it only copied it to where it
+ // could be examined. As a result, the destructor callout appends its
+ // elements to the constructor's values and the result is printed.
+ zero_results();
+ callout_handle.reset();
+
+ EXPECT_EQ("110120", resultCalloutString(0));
+ EXPECT_EQ((110 + 120), resultCalloutInt(0));
+}
+
+// Dynamic callout registration and deregistration.
+// The following are the dynamic registration/deregistration callouts.
+
+
+// Add callout_78_alpha - adds a callout to hook alpha that appends "78x"
+// (where "x" is the callout handle) to the current output.
+
+int
+callout78(CalloutHandle& callout_handle) {
+ return (execute(callout_handle, 7, 8));
+}
+
+int
+add_callout78_alpha(CalloutHandle& callout_handle) {
+ callout_handle.getLibraryHandle().registerCallout("alpha", callout78);
+ return (0);
+}
+
+int
+delete_callout78_alpha(CalloutHandle& callout_handle) {
+ static_cast<void>(
+ callout_handle.getLibraryHandle().deregisterCallout("alpha",
+ callout78));
+ return (0);
+}
+
+// Check that a callout can register another callout on a different hook.
+
+TEST_F(HandlesTest, DynamicRegistrationAnotherHook) {
+ // Register callouts for the different libraries.
+ CalloutHandle handle(getCalloutManager());
+
+ // Set up callouts on "alpha".
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("alpha", callout11);
+ getCalloutManager()->registerCallout("delta", print1);
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->registerCallout("alpha", callout21);
+ getCalloutManager()->registerCallout("delta", print2);
+ getCalloutManager()->setLibraryIndex(2);
+ getCalloutManager()->registerCallout("alpha", callout31);
+ getCalloutManager()->registerCallout("delta", print3);
+
+ // ... and on "beta", set up the function to add a hook to alpha (but only
+ // for library 1).
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->registerCallout("beta", add_callout78_alpha);
+
+ // See what we get for calling the callouts on alpha first.
+ CalloutHandle callout_handle_1(getCalloutManager());
+ callout_handle_1.setArgument("handle_num", static_cast<int>(1));
+ getCalloutManager()->callCallouts(alpha_index_, callout_handle_1);
+
+ zero_results();
+ getCalloutManager()->callCallouts(delta_index_, callout_handle_1);
+ EXPECT_EQ("111", resultCalloutString(0));
+ EXPECT_EQ("211", resultCalloutString(1));
+ EXPECT_EQ("311", resultCalloutString(2));
+
+ // All as expected, now call the callouts on beta. This should add a
+ // callout to the list of callouts for alpha, which we should see when
+ // we run the test again.
+ getCalloutManager()->callCallouts(beta_index_, callout_handle_1);
+
+ // Use a new callout handle so as to get fresh callout context.
+ CalloutHandle callout_handle_2(getCalloutManager());
+ callout_handle_2.setArgument("handle_num", static_cast<int>(2));
+ getCalloutManager()->callCallouts(alpha_index_, callout_handle_2);
+
+ zero_results();
+ getCalloutManager()->callCallouts(delta_index_, callout_handle_2);
+ EXPECT_EQ("112", resultCalloutString(0));
+ EXPECT_EQ("212782", resultCalloutString(1));
+ EXPECT_EQ("312", resultCalloutString(2));
+}
+
+// Check that a callout can register another callout on the same hook.
+// Note that the registration only applies to a subsequent invocation of
+// callCallouts, not to the current one. In other words, if
+//
+// * the callout list for a library is "A then B then C"
+// * when callCallouts is executed "B" adds "D" to that list,
+//
+// ... the current execution of callCallouts only executes A, B and C. A
+// subsequent invocation will execute A, B, C then D.
+
+TEST_F(HandlesTest, DynamicRegistrationSameHook) {
+ // Register callouts for the different libraries.
+ CalloutHandle handle(getCalloutManager());
+
+ // Set up callouts on "alpha".
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("alpha", callout11);
+ getCalloutManager()->registerCallout("alpha", add_callout78_alpha);
+ getCalloutManager()->registerCallout("delta", print1);
+
+ // See what we get for calling the callouts on alpha first.
+ CalloutHandle callout_handle_1(getCalloutManager());
+ callout_handle_1.setArgument("handle_num", static_cast<int>(1));
+ getCalloutManager()->callCallouts(alpha_index_, callout_handle_1);
+ zero_results();
+ getCalloutManager()->callCallouts(delta_index_, callout_handle_1);
+ EXPECT_EQ("111", resultCalloutString(0));
+
+ // Run it again - we should have added something to this hook.
+ CalloutHandle callout_handle_2(getCalloutManager());
+ callout_handle_2.setArgument("handle_num", static_cast<int>(2));
+ getCalloutManager()->callCallouts(alpha_index_, callout_handle_2);
+ zero_results();
+ getCalloutManager()->callCallouts(delta_index_, callout_handle_2);
+ EXPECT_EQ("112782", resultCalloutString(0));
+
+ // And a third time...
+ CalloutHandle callout_handle_3(getCalloutManager());
+ callout_handle_3.setArgument("handle_num", static_cast<int>(3));
+ getCalloutManager()->callCallouts(alpha_index_, callout_handle_3);
+ zero_results();
+ getCalloutManager()->callCallouts(delta_index_, callout_handle_3);
+ EXPECT_EQ("113783783", resultCalloutString(0));
+}
+
+// Deregistration of a callout from a different hook
+
+TEST_F(HandlesTest, DynamicDeregistrationDifferentHook) {
+ // Register callouts for the different libraries.
+ CalloutHandle handle(getCalloutManager());
+
+ // Set up callouts on "alpha".
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("alpha", callout11);
+ getCalloutManager()->registerCallout("alpha", callout78);
+ getCalloutManager()->registerCallout("alpha", callout11);
+ getCalloutManager()->registerCallout("delta", print1);
+
+ getCalloutManager()->registerCallout("beta", delete_callout78_alpha);
+
+ // Call the callouts on alpha
+ CalloutHandle callout_handle_1(getCalloutManager());
+ callout_handle_1.setArgument("handle_num", static_cast<int>(1));
+ getCalloutManager()->callCallouts(alpha_index_, callout_handle_1);
+
+ zero_results();
+ getCalloutManager()->callCallouts(delta_index_, callout_handle_1);
+ EXPECT_EQ("111781111", resultCalloutString(0));
+
+ // Run the callouts on hook beta to remove the callout on alpha.
+ getCalloutManager()->callCallouts(beta_index_, callout_handle_1);
+
+ // The run of the callouts should have altered the callout list on the
+ // first library for hook alpha, so call again to make sure.
+ CalloutHandle callout_handle_2(getCalloutManager());
+ callout_handle_2.setArgument("handle_num", static_cast<int>(2));
+ getCalloutManager()->callCallouts(alpha_index_, callout_handle_2);
+
+ zero_results();
+ getCalloutManager()->callCallouts(delta_index_, callout_handle_2);
+ EXPECT_EQ("112112", resultCalloutString(0));
+}
+
+// Deregistration of a callout from the same hook
+
+TEST_F(HandlesTest, DynamicDeregistrationSameHook) {
+ // Register callouts for the different libraries.
+ CalloutHandle handle(getCalloutManager());
+
+ // Set up callouts on "alpha".
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("alpha", callout11);
+ getCalloutManager()->registerCallout("alpha", delete_callout78_alpha);
+ getCalloutManager()->registerCallout("alpha", callout78);
+ getCalloutManager()->registerCallout("delta", print1);
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->registerCallout("alpha", callout21);
+ getCalloutManager()->registerCallout("alpha", callout78);
+ getCalloutManager()->registerCallout("delta", print2);
+
+ // Call the callouts on alpha
+ CalloutHandle callout_handle_1(getCalloutManager());
+ callout_handle_1.setArgument("handle_num", static_cast<int>(1));
+ getCalloutManager()->callCallouts(alpha_index_, callout_handle_1);
+
+ zero_results();
+ getCalloutManager()->callCallouts(delta_index_, callout_handle_1);
+ EXPECT_EQ("111781", resultCalloutString(0));
+ EXPECT_EQ("211781", resultCalloutString(1));
+
+ // The run of the callouts should have altered the callout list on the
+ // first library for hook alpha, so call again to make sure.
+ CalloutHandle callout_handle_2(getCalloutManager());
+ callout_handle_2.setArgument("handle_num", static_cast<int>(2));
+ getCalloutManager()->callCallouts(alpha_index_, callout_handle_2);
+
+ zero_results();
+ getCalloutManager()->callCallouts(delta_index_, callout_handle_2);
+ EXPECT_EQ("112", resultCalloutString(0));
+ EXPECT_EQ("212782", resultCalloutString(1));
+}
+
+// Testing the operation of the "skip" flag. Callouts print the value
+// they see in the flag and either leave it unchanged, set it or clear it.
+
+int
+calloutPrintSkip(CalloutHandle& handle) {
+ static const std::string YES("Y");
+ static const std::string NO("N");
+
+ HandlesTest::common_string_ = HandlesTest::common_string_ +
+ (handle.getSkip() ? YES : NO);
+ return (0);
+}
+
+int
+calloutSetSkip(CalloutHandle& handle) {
+ static_cast<void>(calloutPrintSkip(handle));
+ handle.setSkip(true);
+ return (0);
+}
+
+int
+calloutClearSkip(CalloutHandle& handle) {
+ static_cast<void>(calloutPrintSkip(handle));
+ handle.setSkip(false);
+ return (0);
+}
+
+// Do a series of tests, returning with the skip flag set "true".
+
+TEST_F(HandlesTest, ReturnSkipSet) {
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("alpha", calloutPrintSkip);
+ getCalloutManager()->registerCallout("alpha", calloutSetSkip);
+ getCalloutManager()->registerCallout("alpha", calloutSetSkip);
+ getCalloutManager()->registerCallout("alpha", calloutClearSkip);
+
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->registerCallout("alpha", calloutPrintSkip);
+ getCalloutManager()->registerCallout("alpha", calloutSetSkip);
+ getCalloutManager()->registerCallout("alpha", calloutSetSkip);
+ getCalloutManager()->registerCallout("alpha", calloutClearSkip);
+ getCalloutManager()->registerCallout("alpha", calloutClearSkip);
+
+ getCalloutManager()->setLibraryIndex(2);
+ getCalloutManager()->registerCallout("alpha", calloutPrintSkip);
+ getCalloutManager()->registerCallout("alpha", calloutSetSkip);
+ getCalloutManager()->registerCallout("alpha", calloutClearSkip);
+ getCalloutManager()->registerCallout("alpha", calloutSetSkip);
+
+ CalloutHandle callout_handle(getCalloutManager());
+ getCalloutManager()->callCallouts(alpha_index_, callout_handle);
+
+ // Check result. For each of visual checking, the expected string is
+ // divided into sections corresponding to the blocks of callouts above.
+ EXPECT_EQ(std::string("NNYY" "NNYYN" "NNYN"), common_string_);
+
+ // ... and check that the skip flag on exit from callCallouts is set.
+ EXPECT_TRUE(callout_handle.getSkip());
+}
+
+// Repeat the test, returning with the skip flag clear.
+TEST_F(HandlesTest, ReturnSkipClear) {
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("alpha", calloutSetSkip);
+ getCalloutManager()->registerCallout("alpha", calloutSetSkip);
+ getCalloutManager()->registerCallout("alpha", calloutClearSkip);
+
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->registerCallout("alpha", calloutPrintSkip);
+ getCalloutManager()->registerCallout("alpha", calloutSetSkip);
+ getCalloutManager()->registerCallout("alpha", calloutClearSkip);
+ getCalloutManager()->registerCallout("alpha", calloutSetSkip);
+ getCalloutManager()->registerCallout("alpha", calloutClearSkip);
+ getCalloutManager()->registerCallout("alpha", calloutClearSkip);
+
+ getCalloutManager()->setLibraryIndex(2);
+ getCalloutManager()->registerCallout("alpha", calloutClearSkip);
+ getCalloutManager()->registerCallout("alpha", calloutPrintSkip);
+ getCalloutManager()->registerCallout("alpha", calloutSetSkip);
+ getCalloutManager()->registerCallout("alpha", calloutClearSkip);
+
+ CalloutHandle callout_handle(getCalloutManager());
+ getCalloutManager()->callCallouts(alpha_index_, callout_handle);
+
+ // Check result. For each of visual checking, the expected string is
+ // divided into sections corresponding to the blocks of callouts above.
+ EXPECT_EQ(std::string("NYY" "NNYNYN" "NNNY"), common_string_);
+
+ // ... and check that the skip flag on exit from callCallouts is set.
+ EXPECT_FALSE(callout_handle.getSkip());
+}
+
+// The next set of callouts do a similar thing to the above "skip" tests,
+// but alter the value of a string argument. This is for testing that the
+// a callout is able to change an argument and return it to the caller.
+
+const char* MODIFIED_ARG = "modified_arg";
+
+int
+calloutSetArgumentCommon(CalloutHandle& handle, const char* what) {
+ std::string modified_arg = "";
+
+ handle.getArgument(MODIFIED_ARG, modified_arg);
+ modified_arg = modified_arg + std::string(what);
+ handle.setArgument(MODIFIED_ARG, modified_arg);
+ return (0);
+}
+
+int
+calloutSetArgumentYes(CalloutHandle& handle) {
+ return (calloutSetArgumentCommon(handle, "Y"));
+}
+
+int
+calloutSetArgumentNo(CalloutHandle& handle) {
+ return (calloutSetArgumentCommon(handle, "N"));
+}
+
+// ... and a callout to just copy the argument to the "common_string_" variable
+// but otherwise not alter it.
+
+int
+calloutPrintArgument(CalloutHandle& handle) {
+ handle.getArgument(MODIFIED_ARG, HandlesTest::common_string_);
+ return (0);
+}
+
+TEST_F(HandlesTest, CheckModifiedArgument) {
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("alpha", calloutSetArgumentYes);
+ getCalloutManager()->registerCallout("alpha", calloutSetArgumentNo);
+ getCalloutManager()->registerCallout("alpha", calloutSetArgumentNo);
+
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->registerCallout("alpha", calloutSetArgumentYes);
+ getCalloutManager()->registerCallout("alpha", calloutSetArgumentYes);
+ getCalloutManager()->registerCallout("alpha", calloutPrintArgument);
+ getCalloutManager()->registerCallout("alpha", calloutSetArgumentNo);
+ getCalloutManager()->registerCallout("alpha", calloutSetArgumentNo);
+
+ getCalloutManager()->setLibraryIndex(2);
+ getCalloutManager()->registerCallout("alpha", calloutSetArgumentYes);
+ getCalloutManager()->registerCallout("alpha", calloutSetArgumentNo);
+ getCalloutManager()->registerCallout("alpha", calloutSetArgumentYes);
+
+ // Create the argument with an initial empty string value. Then call the
+ // sequence of callouts above.
+ CalloutHandle callout_handle(getCalloutManager());
+ std::string modified_arg = "";
+ callout_handle.setArgument(MODIFIED_ARG, modified_arg);
+ getCalloutManager()->callCallouts(alpha_index_, callout_handle);
+
+ // Check the intermediate and results. For visual checking, the expected
+ // string is divided into sections corresponding to the blocks of callouts
+ // above.
+ EXPECT_EQ(std::string("YNN" "YY"), common_string_);
+
+ callout_handle.getArgument(MODIFIED_ARG, modified_arg);
+ EXPECT_EQ(std::string("YNN" "YYNN" "YNY"), modified_arg);
+}
+
+
+} // Anonymous namespace
+
diff --git a/src/lib/util/tests/memory_segment_mapped_unittest.cc b/src/lib/util/tests/memory_segment_mapped_unittest.cc
index 287ee38..c22b59e 100644
--- a/src/lib/util/tests/memory_segment_mapped_unittest.cc
+++ b/src/lib/util/tests/memory_segment_mapped_unittest.cc
@@ -242,8 +242,10 @@ TEST_F(MemorySegmentMappedTest, badAllocate) {
const int ret = chmod(mapped_file, 0444);
ASSERT_EQ(0, ret);
- EXPECT_DEATH_IF_SUPPORTED(
- {segment_->allocate(DEFAULT_INITIAL_SIZE * 2);}, "");
+ if (!isc::util::unittests::runningOnValgrind()) {
+ EXPECT_DEATH_IF_SUPPORTED(
+ {segment_->allocate(DEFAULT_INITIAL_SIZE * 2);}, "");
+ }
}
// XXX: this test can cause too strong side effect (creating a very large
diff --git a/src/lib/util/tests/server_hooks_unittest.cc b/src/lib/util/tests/server_hooks_unittest.cc
new file mode 100644
index 0000000..f029ad2
--- /dev/null
+++ b/src/lib/util/tests/server_hooks_unittest.cc
@@ -0,0 +1,242 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <util/hooks/server_hooks.h>
+
+#include <gtest/gtest.h>
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+using namespace isc;
+using namespace isc::util;
+using namespace std;
+
+namespace {
+
+// Checks the registration of hooks and the interrogation methods. As the
+// constructor registers two hooks, this is also a test of the constructor.
+
+TEST(ServerHooksTest, RegisterHooks) {
+ ServerHooks hooks;
+
+ // There should be two hooks already registered, with indexes 0 and 1.
+ EXPECT_EQ(2, hooks.getCount());
+ EXPECT_EQ(0, hooks.getIndex("context_create"));
+ EXPECT_EQ(1, hooks.getIndex("context_destroy"));
+
+ // Check that the constants are as expected. (The intermediate variables
+ // are used because of problems with g++ 4.6.1/Ubuntu 11.10 when resolving
+ // the value of the ServerHooks constants when they appeared within the
+ // gtest macro.)
+ const int create_value = ServerHooks::CONTEXT_CREATE;
+ const int destroy_value = ServerHooks::CONTEXT_DESTROY;
+ EXPECT_EQ(0, create_value);
+ EXPECT_EQ(1, destroy_value);
+
+ // Register another couple of hooks. The test on returned index is based
+ // on knowledge that the hook indexes are assigned in ascending order.
+ int alpha = hooks.registerHook("alpha");
+ EXPECT_EQ(2, alpha);
+ EXPECT_EQ(2, hooks.getIndex("alpha"));
+
+ int beta = hooks.registerHook("beta");
+ EXPECT_EQ(3, beta);
+ EXPECT_EQ(3, hooks.getIndex("beta"));
+
+ // Should be four hooks now
+ EXPECT_EQ(4, hooks.getCount());
+}
+
+// Check that duplicate names cannot be registered.
+
+TEST(ServerHooksTest, DuplicateHooks) {
+ ServerHooks hooks;
+
+ // Ensure we can't duplicate one of the existing names.
+ EXPECT_THROW(hooks.registerHook("context_create"), DuplicateHook);
+
+ // Check we can't duplicate a newly registered hook.
+ int gamma = hooks.registerHook("gamma");
+ EXPECT_EQ(2, gamma);
+ EXPECT_THROW(hooks.registerHook("gamma"), DuplicateHook);
+}
+
+// Checks that we can get the name of the hooks.
+
+TEST(ServerHooksTest, GetHookNames) {
+ vector<string> expected_names;
+ ServerHooks hooks;
+
+ // Add names into the hooks object and to the set of expected names.
+ expected_names.push_back("alpha");
+ expected_names.push_back("beta");
+ expected_names.push_back("gamma");
+ expected_names.push_back("delta");
+ for (int i = 0; i < expected_names.size(); ++i) {
+ hooks.registerHook(expected_names[i].c_str());
+ };
+
+ // Update the expected names to include the pre-defined hook names.
+ expected_names.push_back("context_create");
+ expected_names.push_back("context_destroy");
+
+ // Get the actual hook names
+ vector<string> actual_names = hooks.getHookNames();
+
+ // For comparison, sort the names into alphabetical order and do a straight
+ // vector comparison.
+ sort(expected_names.begin(), expected_names.end());
+ sort(actual_names.begin(), actual_names.end());
+
+ EXPECT_TRUE(expected_names == actual_names);
+}
+
+// Check that getting an unknown name throws an exception.
+
+TEST(ServerHooksTest, UnknownHookName) {
+ ServerHooks hooks;
+
+ EXPECT_THROW(static_cast<void>(hooks.getIndex("unknown")), NoSuchHook);
+}
+
+// Check that the count of hooks is correct.
+
+TEST(ServerHooksTest, HookCount) {
+ ServerHooks hooks;
+
+ // Insert the names into the hooks object
+ hooks.registerHook("alpha");
+ hooks.registerHook("beta");
+ hooks.registerHook("gamma");
+ hooks.registerHook("delta");
+
+ // Should be two more hooks that the number we have registered.
+ EXPECT_EQ(6, hooks.getCount());
+}
+
+// HookRegistrationFunction tests
+
+// Declare some hook registration functions.
+
+int alpha = 0;
+int beta = 0;
+int gamma = 0;
+int delta = 0;
+
+void registerAlphaBeta(ServerHooks& hooks) {
+ alpha = hooks.registerHook("alpha");
+ beta = hooks.registerHook("beta");
+}
+
+void registerGammaDelta(ServerHooks& hooks) {
+ gamma = hooks.registerHook("gamma");
+ delta = hooks.registerHook("delta");
+}
+
+// Add them to the registration vector. This addition should happen before
+// any tests are run, so we should start off with two functions in the
+// registration vector.
+
+HookRegistrationFunction f1(registerAlphaBeta);
+HookRegistrationFunction f2(registerGammaDelta);
+
+// This is not registered statically: it is used in the latter part of the
+// test.
+
+int epsilon = 0;
+void registerEpsilon(ServerHooks& hooks) {
+ epsilon = hooks.registerHook("epsilon");
+}
+
+// Test that the registration functions were defined and can be executed.
+
+TEST(HookRegistrationFunction, Registration) {
+
+ // The first part of the tests checks the static registration. As there
+ // is only one list of registration functions, we have to do this first
+ // as the static registration is done outside our control, before the
+ // tests are loaded.
+
+ // Ensure that the hook numbers are initialized.
+ EXPECT_EQ(0, alpha);
+ EXPECT_EQ(0, beta);
+ EXPECT_EQ(0, gamma);
+ EXPECT_EQ(0, delta);
+
+ // Should have two hook registration functions registered.
+ EXPECT_EQ(2, HookRegistrationFunction::getFunctionVector().size());
+
+ // Execute the functions and check that four new hooks were defined (two
+ // from each function).
+ ServerHooks hooks;
+ EXPECT_EQ(2, hooks.getCount());
+ HookRegistrationFunction::execute(hooks);
+ EXPECT_EQ(6, hooks.getCount());
+
+ // Check the hook names are as expected.
+ vector<string> names = hooks.getHookNames();
+ ASSERT_EQ(6, names.size());
+ sort(names.begin(), names.end());
+ EXPECT_EQ(string("alpha"), names[0]);
+ EXPECT_EQ(string("beta"), names[1]);
+ EXPECT_EQ(string("context_create"), names[2]);
+ EXPECT_EQ(string("context_destroy"), names[3]);
+ EXPECT_EQ(string("delta"), names[4]);
+ EXPECT_EQ(string("gamma"), names[5]);
+
+ // Check that numbers in the range 2-5 inclusive were assigned as the
+ // hook indexes (0 and 1 being reserved for context_create and
+ // context_destroy).
+ vector<int> indexes;
+ indexes.push_back(alpha);
+ indexes.push_back(beta);
+ indexes.push_back(gamma);
+ indexes.push_back(delta);
+ sort(indexes.begin(), indexes.end());
+ EXPECT_EQ(2, indexes[0]);
+ EXPECT_EQ(3, indexes[1]);
+ EXPECT_EQ(4, indexes[2]);
+ EXPECT_EQ(5, indexes[3]);
+
+ // One last check. We'll test that the constructor of does indeed
+ // add a function to the function vector and that the static initialization
+ // was not somehow by chance.
+ HookRegistrationFunction::getFunctionVector().clear();
+ EXPECT_TRUE(HookRegistrationFunction::getFunctionVector().empty());
+ epsilon = 0;
+
+ // Register a single registration function.
+ HookRegistrationFunction f3(registerEpsilon);
+ EXPECT_EQ(1, HookRegistrationFunction::getFunctionVector().size());
+
+ // Execute it...
+ ServerHooks hooks2;
+ EXPECT_EQ(0, epsilon);
+ EXPECT_EQ(2, hooks2.getCount());
+ HookRegistrationFunction::execute(hooks2);
+
+ // There should be three hooks, with the new one assigned an index of 2.
+ names = hooks2.getHookNames();
+ ASSERT_EQ(3, names.size());
+ sort(names.begin(), names.end());
+ EXPECT_EQ(string("context_create"), names[0]);
+ EXPECT_EQ(string("context_destroy"), names[1]);
+ EXPECT_EQ(string("epsilon"), names[2]);
+
+ EXPECT_EQ(2, epsilon);
+}
+
+} // Anonymous namespace
diff --git a/src/lib/util/unittests/mock_socketsession.h b/src/lib/util/unittests/mock_socketsession.h
index 01ca34f..fb155a2 100644
--- a/src/lib/util/unittests/mock_socketsession.h
+++ b/src/lib/util/unittests/mock_socketsession.h
@@ -42,7 +42,12 @@ class MockSocketSessionForwarder :
public:
MockSocketSessionForwarder() :
is_connected_(false), connect_ok_(true), push_ok_(true),
- close_ok_(true)
+ close_ok_(true),
+ // These are not used until set, but we set them anyway here,
+ // partly to silence cppcheck, and partly to be cleaner. Put some
+ // invalid values in.
+ pushed_sock_(-1), pushed_family_(-1), pushed_type_(-1),
+ pushed_protocol_(-1)
{}
virtual void connectToReceiver() {
diff --git a/tests/lettuce/configurations/example.org.inmem.config b/tests/lettuce/configurations/example.org.inmem.config
index 2e9ca41..2eadedb 100644
--- a/tests/lettuce/configurations/example.org.inmem.config
+++ b/tests/lettuce/configurations/example.org.inmem.config
@@ -22,6 +22,12 @@
"params": {
"example.org": "data/example.org"
}
+ },
+ {
+ "type": "broken_libraries_should_be_skipped",
+ "cache-enable": false,
+ "params": {
+ }
}
]
}
diff --git a/tests/lettuce/features/auth_badzone.feature b/tests/lettuce/features/auth_badzone.feature
index ca805c8..8b902b3 100644
--- a/tests/lettuce/features/auth_badzone.feature
+++ b/tests/lettuce/features/auth_badzone.feature
@@ -24,7 +24,7 @@ Feature: Authoritative DNS server with a bad zone
And bind10 module Resolver should not be running
A query for www.example.org should have rcode NOERROR
- The last query response should have flags qr aa rd
+ The last query response should have flags qr aa
The last query response should have ancount 1
The last query response should have nscount 2
The last query response should have adcount 2
diff --git a/tests/lettuce/features/ddns_system.feature b/tests/lettuce/features/ddns_system.feature
index 184c8ae..6747b53 100644
--- a/tests/lettuce/features/ddns_system.feature
+++ b/tests/lettuce/features/ddns_system.feature
@@ -118,6 +118,42 @@ Feature: DDNS System
A query for new3.example.org should have rcode NOERROR
The SOA serial for example.org should be 1236
+ Scenario: Zone validation check
+ Given I have bind10 running with configuration ddns/ddns.config
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+ And wait for bind10 stderr message DDNS_STARTED
+
+ # Sanity check
+ A query for example.org type NS should have rcode NOERROR
+ The answer section of the last query response should be
+ """
+ example.org. 3600 IN NS ns1.example.org.
+ example.org. 3600 IN NS ns2.example.org.
+ example.org. 3600 IN NS ns3.example.org.
+ """
+ The SOA serial for example.org should be 1234
+
+ # Test failed validation. Here, example.org has ns1.example.org
+ # configured as a name server. CNAME records cannot be added for
+ # ns1.example.org.
+ When I use DDNS to add a record ns1.example.org. 3600 IN CNAME ns3.example.org.
+ The DDNS response should be REFUSED
+ A query for ns1.example.org type CNAME should have rcode NXDOMAIN
+ The SOA serial for example.org should be 1234
+
+ # Test passed validation. Here, example.org does not have
+ # ns4.example.org configured as a name server. CNAME records can
+ # be added for ns4.example.org.
+ When I use DDNS to add a record ns4.example.org. 3600 IN CNAME ns3.example.org.
+ The DDNS response should be SUCCESS
+ A query for ns4.example.org type CNAME should have rcode NOERROR
+ The answer section of the last query response should be
+ """
+ ns4.example.org. 3600 IN CNAME ns3.example.org.
+ """
+ The SOA serial for example.org should be 1235
+
#Scenario: DDNS and Xfrout
## Unfortunately, Xfrout can only notify to inzone slaves, and hence only
## to port 53, which we do not want to use for Lettuce tests (for various
diff --git a/tests/lettuce/features/example.feature b/tests/lettuce/features/example.feature
index 86d20d3..ee84b46 100644
--- a/tests/lettuce/features/example.feature
+++ b/tests/lettuce/features/example.feature
@@ -120,7 +120,7 @@ Feature: Example feature
The last query response should have adcount 0
# When checking flags, we must pass them exactly as they appear in
# the output of dig.
- The last query response should have flags qr aa rd
+ The last query response should have flags qr aa
A query for www.example.org type TXT should have rcode NOERROR
The last query response should have ancount 0
diff --git a/tests/lettuce/features/nsec3_auth.feature b/tests/lettuce/features/nsec3_auth.feature
index 6d3a556..8ead43f 100644
--- a/tests/lettuce/features/nsec3_auth.feature
+++ b/tests/lettuce/features/nsec3_auth.feature
@@ -25,7 +25,7 @@ Feature: NSEC3 Authoritative service
And bind10 module StatsHttpd should not be running
A dnssec query for a.c.x.w.example. should have rcode NXDOMAIN
- The last query response should have flags qr aa rd
+ The last query response should have flags qr aa
The last query response should have edns_flags do
The last query response should have ancount 0
The last query response should have nscount 8
@@ -57,7 +57,7 @@ Feature: NSEC3 Authoritative service
And bind10 module StatsHttpd should not be running
A dnssec query for ns1.example. type MX should have rcode NOERROR
- The last query response should have flags qr aa rd
+ The last query response should have flags qr aa
The last query response should have edns_flags do
The last query response should have ancount 0
The last query response should have nscount 4
@@ -85,7 +85,7 @@ Feature: NSEC3 Authoritative service
And bind10 module StatsHttpd should not be running
A dnssec query for y.w.example. should have rcode NOERROR
- The last query response should have flags qr aa rd
+ The last query response should have flags qr aa
The last query response should have edns_flags do
The last query response should have ancount 0
The last query response should have nscount 4
@@ -113,7 +113,7 @@ Feature: NSEC3 Authoritative service
And bind10 module StatsHttpd should not be running
A dnssec query for mc.c.example. type MX should have rcode NOERROR
- The last query response should have flags qr rd
+ The last query response should have flags qr
The last query response should have edns_flags do
The last query response should have ancount 0
The last query response should have nscount 6
@@ -148,7 +148,7 @@ Feature: NSEC3 Authoritative service
And bind10 module StatsHttpd should not be running
A dnssec query for a.z.w.example. type MX should have rcode NOERROR
- The last query response should have flags qr aa rd
+ The last query response should have flags qr aa
The last query response should have edns_flags do
The last query response should have ancount 2
The last query response should have nscount 5
@@ -195,7 +195,7 @@ Feature: NSEC3 Authoritative service
And bind10 module StatsHttpd should not be running
A dnssec query for a.z.w.example. type AAAA should have rcode NOERROR
- The last query response should have flags qr aa rd
+ The last query response should have flags qr aa
The last query response should have edns_flags do
The last query response should have ancount 0
The last query response should have nscount 8
@@ -227,7 +227,7 @@ Feature: NSEC3 Authoritative service
And bind10 module StatsHttpd should not be running
A dnssec query for example. type DS should have rcode NOERROR
- The last query response should have flags qr aa rd
+ The last query response should have flags qr aa
The last query response should have edns_flags do
The last query response should have ancount 0
The last query response should have nscount 4
@@ -259,7 +259,7 @@ Feature: NSEC3 Authoritative service
And bind10 module StatsHttpd should not be running
A dnssec query for b.x.w.example. should have rcode NXDOMAIN
- The last query response should have flags qr aa rd
+ The last query response should have flags qr aa
The last query response should have edns_flags do
The last query response should have ancount 0
The last query response should have nscount 6
@@ -289,7 +289,7 @@ Feature: NSEC3 Authoritative service
And bind10 module StatsHttpd should not be running
A dnssec query for a.w.example. should have rcode NOERROR
- The last query response should have flags qr aa rd
+ The last query response should have flags qr aa
The last query response should have edns_flags do
The last query response should have ancount 0
The last query response should have nscount 6
@@ -319,7 +319,7 @@ Feature: NSEC3 Authoritative service
And bind10 module StatsHttpd should not be running
A dnssec query for *.w.example. type MX should have rcode NOERROR
- The last query response should have flags qr aa rd
+ The last query response should have flags qr aa
The last query response should have edns_flags do
The last query response should have ancount 2
The last query response should have nscount 3
@@ -362,7 +362,7 @@ Feature: NSEC3 Authoritative service
And bind10 module StatsHttpd should not be running
A dnssec query for *.w.example. type A should have rcode NOERROR
- The last query response should have flags qr aa rd
+ The last query response should have flags qr aa
The last query response should have edns_flags do
The last query response should have ancount 0
The last query response should have nscount 4
@@ -390,7 +390,7 @@ Feature: NSEC3 Authoritative service
And bind10 module StatsHttpd should not be running
A dnssec query for 0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example. type NSEC3 should have rcode NXDOMAIN
- The last query response should have flags qr aa rd
+ The last query response should have flags qr aa
The last query response should have edns_flags do
The last query response should have ancount 0
The last query response should have nscount 8
@@ -422,7 +422,7 @@ Feature: NSEC3 Authoritative service
And bind10 module StatsHttpd should not be running
A dnssec query for ai.example. type DS should have rcode NOERROR
- The last query response should have flags qr aa rd
+ The last query response should have flags qr aa
The last query response should have edns_flags do
The last query response should have ancount 0
The last query response should have nscount 4
@@ -450,7 +450,7 @@ Feature: NSEC3 Authoritative service
And bind10 module StatsHttpd should not be running
A dnssec query for c.example. type DS should have rcode NOERROR
- The last query response should have flags qr aa rd
+ The last query response should have flags qr aa
The last query response should have edns_flags do
The last query response should have ancount 0
The last query response should have nscount 6
diff --git a/tests/lettuce/features/queries.feature b/tests/lettuce/features/queries.feature
index b0a6fac..2db6c3e 100644
--- a/tests/lettuce/features/queries.feature
+++ b/tests/lettuce/features/queries.feature
@@ -53,6 +53,12 @@ Feature: Querying feature
And wait for bind10 stderr message BIND10_STARTED_CC
And wait for bind10 stderr message CMDCTL_STARTED
And wait for bind10 stderr message AUTH_SERVER_STARTED
+
+ # DATASRC_LIBRARY_ERROR must be generated due to
+ # "broken_libraries_should_be_skipped" in
+ # example.org.inmem.config
+ And wait for bind10 stderr message DATASRC_LIBRARY_ERROR
+
And wait for bind10 stderr message STATS_STARTING
bind10 module Auth should be running
@@ -75,7 +81,7 @@ Feature: Querying feature
The statistics counters are 0 in category .Auth.zones._SERVER_
A query for www.example.org should have rcode NOERROR
- The last query response should have flags qr aa rd
+ The last query response should have flags qr aa
The last query response should have ancount 1
The last query response should have nscount 2
The last query response should have adcount 2
@@ -121,7 +127,7 @@ Feature: Querying feature
# Repeat of the above
A query for www.example.org should have rcode NOERROR
- The last query response should have flags qr aa rd
+ The last query response should have flags qr aa
The last query response should have ancount 1
The last query response should have nscount 2
The last query response should have adcount 2
@@ -165,7 +171,7 @@ Feature: Querying feature
| rcode.noerror | 2 |
# And now query something completely different
- A query for nosuchname.example.org should have rcode NXDOMAIN
+ A recursive query for nosuchname.example.org should have rcode NXDOMAIN
The last query response should have flags qr aa rd
The last query response should have ancount 0
The last query response should have nscount 1
@@ -196,6 +202,7 @@ Feature: Querying feature
| responses | 3 |
| qrysuccess | 2 |
| qryauthans | 3 |
+ | qryrecursion | 1 |
| rcode.noerror | 2 |
| rcode.nxdomain | 1 |
@@ -225,7 +232,7 @@ Feature: Querying feature
The statistics counters are 0 in category .Auth.zones._SERVER_
A query for example.org type ANY should have rcode NOERROR
- The last query response should have flags qr aa rd
+ The last query response should have flags qr aa
The last query response should have ancount 4
The last query response should have nscount 0
The last query response should have adcount 3
@@ -284,7 +291,7 @@ Feature: Querying feature
The statistics counters are 0 in category .Auth.zones._SERVER_
A dnssec query for www.sub.example.org type AAAA should have rcode NOERROR
- The last query response should have flags qr rd
+ The last query response should have flags qr
The last query response should have edns_flags do
The last query response should have ancount 0
The last query response should have nscount 1
diff --git a/tests/lettuce/features/resolver_basic.feature b/tests/lettuce/features/resolver_basic.feature
index 47fc123..341c14c 100644
--- a/tests/lettuce/features/resolver_basic.feature
+++ b/tests/lettuce/features/resolver_basic.feature
@@ -24,13 +24,13 @@ Feature: Basic Resolver
And bind10 module StatsHttpd should not be running
# The ACL is set to reject any queries
- A query for l.root-servers.net. should have rcode REFUSED
+ A recursive query for l.root-servers.net. should have rcode REFUSED
# Test whether acl ACCEPT works
When I set bind10 configuration Resolver/query_acl[0] to {"action": "ACCEPT", "from": "127.0.0.1"}
# This address is currently hardcoded, so shouldn't cause outside traffic
- A query for l.root-servers.net. should have rcode NOERROR
+ A recursive query for l.root-servers.net. should have rcode NOERROR
# Check whether setting the ACL to reject again works
When I set bind10 configuration Resolver/query_acl[0] to {"action": "REJECT", "from": "127.0.0.1"}
- A query for l.root-servers.net. should have rcode REFUSED
+ A recursive query for l.root-servers.net. should have rcode REFUSED
diff --git a/tests/lettuce/features/terrain/querying.py b/tests/lettuce/features/terrain/querying.py
index ae348fd..ec75490 100644
--- a/tests/lettuce/features/terrain/querying.py
+++ b/tests/lettuce/features/terrain/querying.py
@@ -200,14 +200,19 @@ class QueryResult(object):
"""
pass
- at step('A (dnssec )?query for ([\S]+) (?:type ([A-Z0-9]+) )?' +
+ at step('A (dnssec )?(recursive )?query for ([\S]+) (?:type ([A-Z0-9]+) )?' +
'(?:class ([A-Z]+) )?(?:to ([^:]+|\[[0-9a-fA-F:]+\])(?::([0-9]+))? )?' +
'should have rcode ([\w.]+)')
-def query(step, dnssec, query_name, qtype, qclass, addr, port, rcode):
+def query(step, dnssec, recursive, query_name, qtype, qclass, addr, port,
+ rcode):
"""
Run a query, check the rcode of the response, and store the query
result in world.last_query_result.
Parameters:
+ dnssec ('dnssec'): DO bit is set in the query.
+ Defaults to unset (no DNSSEC).
+ recursive ('recursive'): RD bit is set in the query.
+ Defaults to unset (no recursion).
query_name ('query for <name>'): The domain name to query.
qtype ('type <type>', optional): The RR type to query. Defaults to A.
qclass ('class <class>', optional): The RR class to query. Defaults to IN.
@@ -234,6 +239,9 @@ def query(step, dnssec, query_name, qtype, qclass, addr, port, rcode):
# additional counts, so unless we need dnssec, explicitly
# disable edns0
additional_arguments.append("+noedns")
+ # dig sets RD bit by default.
+ if recursive is None:
+ additional_arguments.append("+norecurse")
query_result = QueryResult(query_name, qtype, qclass, addr, port,
additional_arguments)
assert query_result.rcode == rcode,\
diff --git a/tests/tools/perfdhcp/stats_mgr.h b/tests/tools/perfdhcp/stats_mgr.h
index c51ba6d..a8b5d98 100644
--- a/tests/tools/perfdhcp/stats_mgr.h
+++ b/tests/tools/perfdhcp/stats_mgr.h
@@ -455,9 +455,16 @@ public:
packet_period.length().total_seconds() +
(static_cast<double>(packet_period.length().fractional_seconds())
/ packet_period.length().ticks_per_second());
- if (drop_time_ > 0 &&
- (period_fractional > drop_time_)) {
- eraseSent(sent_packets_.template project<0>(it));
+ if (drop_time_ > 0 && (period_fractional > drop_time_)) {
+ // The packet pointed to by 'it' is timed out so we
+ // have to remove it. Removal may invalidate the
+ // next_sent_ pointer if it points to the packet
+ // being removed. So, we set the next_sent_ to point
+ // to the next packet after removed one. This
+ // pointer will be further updated in the following
+ // iterations, if the subsequent packets are also
+ // timed out.
+ next_sent_ = eraseSent(sent_packets_.template project<0>(it));
++collected_;
}
}
More information about the bind10-changes
mailing list