BIND 10 trac2836, updated. b9c101fd307c164f7551d00068358828af4ed549 [2836] Set the segment holder separately
BIND 10 source code commits
bind10-changes at lists.isc.org
Wed May 15 09:08:53 UTC 2013
The branch, trac2836 has been updated
via b9c101fd307c164f7551d00068358828af4ed549 (commit)
via 025659c532f8521d8a36476d2f1a14593c9690b5 (commit)
via 066c983e091831645218810fb921986a659ccb1b (commit)
via 95c6f676d504950bab0964fe9297893f36d4fac6 (commit)
via a3d4fe8a32003534150ed076ea0bbf80e1fcc43c (commit)
via 650c6ce282800e712fb378741638ac66240d59c0 (commit)
via 7ad877ff1d3b88b2b8430a80f6102590837c2e74 (commit)
via 2df5585f42f1af03b89204c7cc22569720a26ae4 (commit)
via c312fa4f4866b21aca35bc4987f8f0e83b198ef9 (commit)
via 69cdc5f74d067196ec35daad43f842f716897bcc (commit)
via 211389e129c7c27129fda481deda0dd649630a98 (commit)
via de6e68cf258f9349795368f5bf773f8911a79acf (commit)
via 3c98ade56f07c44740f1bde2e37f8fdc9842bd18 (commit)
via bf30f052334922f729c8805f109f838ec1a126b9 (commit)
via 2f808b5a9d101109016361c6143b1add3887d1b6 (commit)
via 17590957db6a352e86c208c3520a340349b340ea (commit)
via e2889aabb93e201bfaf49046472ffc42df8b4b77 (commit)
via 9e44958839b3f9d8fbe6cbd1f0c8f711763396fb (commit)
via 76e6edb548bfa6eb3df302ddd74ecff71caee3eb (commit)
via 6bff7db76bded2e571e2ee827c776c45bdcd6c6d (commit)
via 5bfdcfaa4a495e36255de546354d1673e4581d3c (commit)
via 46bf6c05bd7c5aa7cc79af834843db68cb6b5471 (commit)
via c6cadebf7b86855a7da91f14f66bd7d605ee13ea (commit)
via 8ba0a7bc496c135f80c71bf035fcca0687a16e27 (commit)
via db8cec0cd9505bd0ab091e6258822dcb2891338c (commit)
via 04c159afd7ae18b98b50d1a3d2e5f1eefdb3bae8 (commit)
via c3f6b67fa16a07f7f7ede24dd85feaa7c157e1cb (commit)
via d5a472119f28ad0f7f15756997c741ba25208ab9 (commit)
via bc3d482930fad8d97305f07e116093ace8110e9c (commit)
via 32d750c6674613bd630eb1e724adc82b8ec8a622 (commit)
via 7bea7dd8c15ce158908b8c42c93533089d12da8a (commit)
via 4cac3ddb232eac0e52451c0aff718f3207fc7977 (commit)
via 56099e3711191e22b41cc6f404a69569c1f3b7e1 (commit)
via eda6761d2607385c10bb6a9e8bdc5c1209405093 (commit)
via 8d9a833a05515867753578af948c537a4d8ad127 (commit)
via 14a35130eaf81efdc8483fdfb1b8eabbc97653be (commit)
via cea0d1058d5b96e616ce8f71afd0b45a1f81b103 (commit)
via 3ebd4b039b2596328c1c112398a9e629c9a73001 (commit)
via 89e0087450e73ea2336f56c0f7024f4741eaeba0 (commit)
via 0404a592b185c5473343a3faa25de18845cd192c (commit)
via 0ef74c3696b50b84cc1613fbdff09acb775c3e10 (commit)
via 654ec49fc5f8976cc6567b31ab8277d392f79b76 (commit)
via c65fde7c57736e089aa9d83f82d340d7dfb167e2 (commit)
via d4a8366000c44fe71b70dcee0615d38253b4f932 (commit)
via 4d67c32a6d5ea8a09f9a9dc995b33619f56db5fd (commit)
via 2cb89753827b015cd183fff26b52e7c28154727f (commit)
via 211025a21adc6230894092b606c8f86cef3dc913 (commit)
via be918f9a8b657005605fc8856ed037fafc2374e9 (commit)
via c6603decaadcd33ccf9aee4a7b22447acec4b7f6 (commit)
via 299f1f5ff94bb77306019c1d26d12e57a30a9ab4 (commit)
via ad0e3e26471403d9b019352132cbc04b2c1c055c (commit)
via 2015f7356237f5004c6aa9b678b442575476681b (commit)
via a7dac3f7ff64093a4958710cc45a7dc28f727182 (commit)
via e6d1be1a6718874ab37b0f403c3e266c90bcdf87 (commit)
via 148c5bbf6132ac5b149a0a97286f13ec212443cb (commit)
via 4542e8960d4accbef74ac41e5082209f3776b5b8 (commit)
via 56d109e8a0c5d46ac542870723ba324aa2425b88 (commit)
via 315d2846dec8b2f36df1aa8ed23feddfc621a356 (commit)
via 131a89175e97714ac87f14f95703cd76df7a965c (commit)
via bc6af598cfc62d68cc4a80c0aa10d8573d63cfc0 (commit)
via 468687262b865e264f2bdc235ec4aff45fffaf29 (commit)
via 7ece6fb69d2791ea263c080083a725c36e7a12ea (commit)
via 65b7adb8a1578ae6f070b9c877149d0db77aefda (commit)
via 7e2d747ba455749e7ccee8eec36fe5f7e51ed749 (commit)
via d2861efce4cf71c924de470c48594727e0eba80f (commit)
via 70066be6e51afc479dc485dfdf9ab79169a9d3c1 (commit)
via 9d90db228e26173b0781d090ea2b0e078c7969f6 (commit)
via a19cdcff7accbce09eab6bf7ea359a830564c1f8 (commit)
via d003ebd54cb9b0454e60af6270177e202c766892 (commit)
via c630ff8a3861c5597a5c77a81eee52c09aaef643 (commit)
via 36f7b7fa7bddd07da90c9346d5d62dbed77c2a49 (commit)
via 9b930275a2a875d91aa425cf8bcbe552c5bee453 (commit)
via 45e7d86237a1b483d27819c5f5fe0d5c6ebb6dc4 (commit)
via e1a0ea8ef5c51b9b25afa111fbfe9347afbe5413 (commit)
via d36c276d69d8897175964b8a2002405c53630663 (commit)
via f7cfb3332667e260a60660af23290087fd0c4fbc (commit)
via 7ccddd64de7f7928d20f7eff23053b64c644349d (commit)
via 5bdee1a5056c8e3517d2dd3de954d40323027ee1 (commit)
via 034e1d3b1490054f265c4e4ac20cfa43127a5625 (commit)
via aedbd11c6780fb36fbaa8c6c8c4521caf24fa5b4 (commit)
via dac1394a30be534559eda0cfeaa140eace9000a6 (commit)
via 885821d16691e4179678e4d7f61d550774c1a7a6 (commit)
via 225a1e04ba13d2a48bbf3e1f958ce4e3f86aed49 (commit)
via caefbb6f9016955ad62391d799fd69886aa363b4 (commit)
via b308ba3bf71ccc768f302022e7ab571ff2cfbdfd (commit)
via 72604e2508ea78f0f0fcce74b6e34838e75675a2 (commit)
via c519aa88c8cf4edb25a53ec761364b076a6ae497 (commit)
via 0475216923e0c60294c0901ae3e6af09f5e961ae (commit)
via 8f4a354b90fbf0be2ddc5c54f72325d52f0eff2e (commit)
via 1fd14dfefcd2339d9dea25b30dc9204bd10548f4 (commit)
via 39fba2a15cb200dbe77ae3c9a4d894496f9eee66 (commit)
via 557794895c01555c32906ad8422d5d6fbb878fe8 (commit)
via 3af2603f0f08ad709590766051eea370c470642f (commit)
via 03ce449ee10c5a8d8f60a68cb42839b9ae63ec00 (commit)
via 9784a1841ff7a5f08c92a0c646bbc4ff9a0b4ccb (commit)
via 5082c255bbeb8c83f9ab08d4e28429cc271b2fec (commit)
via 3599c07417b1bcf40ae8e59959278b4a59ede827 (commit)
via 043b3d2952712cd61e7ffb4f73f3253d11ce6bd2 (commit)
via b5cd040334461d9f5ab0f1267d505bc7ac3e0ca9 (commit)
via 54a1d02e8816b53e0d4ab77817380946ddf156d4 (commit)
via 7d1a2ca88d0daac2779ecccf50c4308eb0914ebd (commit)
via ad233427c14503e34980780761700596f13e14f4 (commit)
via 6dd94680bed03c6423d1674bc4193c64a7fed4a1 (commit)
via 9377868e58e5303d06688010224ae325972a1547 (commit)
via 8efaec345efaf37272cd7098177b60ea787e3ee7 (commit)
via 48c33c6c5adbe7e46d5a7853c1ee0f7085fcab6f (commit)
via 74c2ed970512562b5ea53004d3e152e2d8519329 (commit)
via 2d3ed739129c40c2473a927f51e37a445b47fee5 (commit)
via 3e678f3bda8a502c8a4fe483e12166bf1c9077d3 (commit)
via 73e1a3cf8920bfb4e7cd8f7672b3f82600f2766f (commit)
via 31075ad95c5c27f30d2b9812ead3dd8b6edff236 (commit)
via 721708db656444bf46451aeed3500bf3580b3466 (commit)
via 5045f86bbd1c7ffaf07c95f7c6ce4d64b1e1c467 (commit)
via 94d255e2f177399d659e53ae06d622f2576b7e4c (commit)
via b9608c20b42ed6db6592a1f7851c91efce974b32 (commit)
via f18722f2e329c939584ff1b45936eed172fb16e3 (commit)
via 214b2747a3213b7267a4a8a597d07851f00fb1c8 (commit)
via f3259cf183a8bea65acad690b50982057f24abe5 (commit)
via 0dccb67c0e49f3448e7fc3da7a5e414113bf36dc (commit)
via 622d3e7123555d0115149e3f4527dbe5f11da864 (commit)
via 6bb2e2de9bc899e31c8233c1ef74e8ff79c1bccc (commit)
via 366cf13a9ca23ea0ace26677754d1968106c277b (commit)
via 186195174b0d8256da12f91395f8e57c17ea6a7c (commit)
via 6548cfede8e4f8fb9457eb62b0c458969a358155 (commit)
via fd6c9be84e460ead7e1fd3e0888828854adccb4f (commit)
via 71be4dc12e4ffee2641ea54f70bd26f16bdc1460 (commit)
via 673bbeb9b8b1ecd0629c0807578624705c58085b (commit)
via 02c2f7f80e6d858233a595cae04842666fbd29c8 (commit)
via fc4e4e5c2c469c8c464146bfe56cda99effd4593 (commit)
via e3c5c430e69e4b3b2211421be2dc23723c4c40eb (commit)
via ca15d37a2e8d6eff63b52eb5fef0e8d3bdb3e2c2 (commit)
via 10386454c422828a5d46a3b927b1c284e6216372 (commit)
via f41e4c3104480b6a8a6aa08cc5c28cf71a13a3bc (commit)
via b30eb9348603cb2181794ca00acbc1bcffa54db7 (commit)
via ac473294894ef65cd01174114183a42657d6151d (commit)
via 3456e30bce45c286b120d176a517ca88e9e21b1c (commit)
via 4325f3dfd81569be4ef09a84cf0fb74246a5b666 (commit)
via 74bad1ad71292fa998f14ad3953236f7e979efac (commit)
via 035fbe1da0e77a45d108046e5399c5a676e098a8 (commit)
via 5d65c74c127243790200028375786fced2bd1ad7 (commit)
via 2b96d4487c517ab209a1ca5fce4af877fecae0a7 (commit)
via 9f92c87372c0a3b37fabb3ee1963dca5409ccde5 (commit)
via 943453b21cb4a91ada2f0da363906e29d3659421 (commit)
via 271a74c31ae50d5f713dd6bccc2127f56f950466 (commit)
via 96f3f46af4cba730dfccaf13ea8350529dc71bb6 (commit)
via 30004e59d00244ebea35e8df0abce51acb2cdef2 (commit)
via 340c8a6ce05ef5c72e81801066191c18875bbec1 (commit)
via 46af17adcadf913b3ee26a70394b8faaf5dd7b36 (commit)
via f85b274b85b57a094d33ca06dfbe12ae67bb47df (commit)
via 79fe8bd49de676e941f804350bde4115ab454124 (commit)
via 36d8374051ef3df79035debd0bcdf63016d0aa85 (commit)
via bec8813a793940089543c8875afc3ea475662459 (commit)
via e8655ab138c37f22582c7370677a4361cb6d6363 (commit)
via 23db9e9d61274b5c8c4d23b493cbd582eb9eda4a (commit)
via 2a6431ad41175316c82c7d13c0bffa0de9a44a7b (commit)
via 05504fc9e53521a9e5f55f6dbfc619813a08c733 (commit)
via 70994c681382b8c8b8c220af97999c887e01a423 (commit)
via 339c94b4664819a817eda8008778efd2c114ae93 (commit)
via 6d3e0f4b36a754248f8a03a29e2c36aef644cdcc (commit)
via 1815740f4154dea79aa15ec64cffe2f5d010769d (commit)
via 0b7af64f6b3a567371a38ceb83001008dcc8f4b2 (commit)
via a1d81937904fd5f646f40c655975855aea5985d4 (commit)
via 26326a8f0780a2603c7ff7268998fbcce7bc5dbc (commit)
via 7a49270e4d0117b5432dfedec70e41a9c399745a (commit)
via c45efc1b129fc4b64d79040e0bec33f08658b170 (commit)
via 4a5506ec8f3474513c7de10969628385cec138b2 (commit)
via e646d8af58ff6b9863a89ae65b6244275405bce0 (commit)
via 68343c845a46a9be3f38c38388ec02a8bb4ef884 (commit)
via 10f2ae4625a238a4899372432df13d29eca9f9b3 (commit)
via 8d6493eb98994ac01c4305d12a148d78d048b681 (commit)
via f182b85e7ab01bae5c9c7a181a2b9db7977a9948 (commit)
via bd7e56727b6d8228f2fdc685bff3a18349415c69 (commit)
via 85873bf8615dc643db8619414e417dffef827c4f (commit)
via 909a05ac4185589eb2304d815b5ccd108f92f4b7 (commit)
via 1453084c9e0ecc7c3663678031f3d19c5f91adff (commit)
via fa4c5b912c4e4deab0e42af46eaeb6ef3e41df72 (commit)
via 5c82cc617522160d1c3d248aa215d84031b2de9c (commit)
via 75235e6f7c1b9b646d68c6d6238b2c38df9814d8 (commit)
via b5b6b43f34a3901dff343367515ea08f09a9d6ce (commit)
via 4f3776c2b1d06bca0e7e02feeba79caba01ac865 (commit)
via bf4f7d8e7a3404deafa3901720e58a524e178a18 (commit)
via 03cdf37097aac9e1f10f0f349e6c0aa83f6debe9 (commit)
via 702512a6bbb48421042b69fba312bb41296eab61 (commit)
via 53a1410acc5726fe8ed83ded19b65be7c9002ee1 (commit)
via 832eddac5d9724af4b228cb123a84ed7ddae0473 (commit)
via c055314da48eaadb3f88b64eda17263fc6b387fa (commit)
via d86119932d1b6f2674f3685515c35be147f2d327 (commit)
via 846996720639e0a37682312641bd6ed504870488 (commit)
via fa72716d183569839306fa8bde1d7ec451ce8919 (commit)
via 1a7685ff5054b4a51178ae24064d8cf41e817ced (commit)
via d3896075acb7d3b5dcfd8441d9ec9c40f3c44f4b (commit)
via dc507ec0a035a393304313f21184c9a9c804e46e (commit)
via 6df5353aa49ff281af8c5931b8b556595cd70c6b (commit)
via e4390d4f07ed2af66782abfa41ecfdbd7b1966cd (commit)
via 7ba811ceb844372d04d8f0ca62f112a377889ae9 (commit)
via 55dd08057c52c3f0baf3f1b34f23f87c2ee7ff35 (commit)
via 63e9d90b093a7938f0b7cdb34e9e71379a15d5de (commit)
via ac3da26ee0505f828be081d284b4216f5ada820a (commit)
via 79d4b470181b50699125d1cf3394e11071f6178d (commit)
via b6de5342bd648c4f38e892df6c93177c243ec993 (commit)
via 7ff403a459b34457e433a4cf87ee9fdc64ba5af1 (commit)
via bb0d8b35c3f52e2900abccda0f52995683eee4ab (commit)
via 33e10c84c8b6ab274c157816ba81e6d2fc8ba628 (commit)
via 3a382331f1308099930d6b653dd6d11b70da8e22 (commit)
via 0a89b921bb8cb268721e8ca38d99f7ea967fdf8c (commit)
via c0acd591b18839863e5c46be32daeebc07c162c4 (commit)
via 5ee122cfa69f8bd6be3c4861407a3da35cb72bdb (commit)
via 5ee0c3b143458e52d59e3e162c5d9cd4e11e0d1f (commit)
via 2a8863be72c4fdf0b9aba1e05e525de7fe18d4b4 (commit)
via 58a2bdc6b084fdb4ac57726b305f49463370cc2c (commit)
via 861a1055e8d48f682b5aed9942f1eb1963d93a55 (commit)
via e9fa118cd1b9d4d1c47ddb94244ddf78266c9dbd (commit)
via 08f6a3e96b00395c6132b7558ca8c3e0764e7adf (commit)
via a6328cdc2907b9becde3d261373521dc1a3b963f (commit)
via 1c295702731e5fb76d8ea39c14104347fb0a8b91 (commit)
via 51be92846c3bf4881701f17e869eb9f12f9cd239 (commit)
via 35df226d16838f37390270d9d3d1a5e736cfcd12 (commit)
via 2aaa162fd5f5921aac58baeeb0dd026b7a0a2fbb (commit)
via db5ddcf7b62eb21d9cf0f057ee45ec31adc832f3 (commit)
via 125dd1132b09275fbb528ebdd4358f5977f17402 (commit)
via f82f47b299e9f48c86c6c8f7967f29c381d5df06 (commit)
via ca3018c0273b41dac1f67892cdf59e3673a183f5 (commit)
via 7d6c238e01f66ff90e073c458312e732b9ce3f72 (commit)
via 719a1cb238f32588f5b891dc3f5d1461efff6905 (commit)
via 9f0934c0e2ca79fb5b1c03c57cc850fb9bdc22f8 (commit)
via 73bdf354a07a3e7ad4585881c434fd71c36ca775 (commit)
via 1d3b43a5f11c82ea80c9c8469cb8aee4f504cb20 (commit)
via e825b22e4ea06a8f0ecc8645fbd42af230d05151 (commit)
via 8250e0313cf3e7f81646e8dd978f4a16f0578222 (commit)
via 4933a1c6284498e2656909d15a84657b5b6ecb8b (commit)
via d24f908722f73071dd220b3457123d3e9dd2deae (commit)
via 6b983412d569f2e7e81f6e6bebe37720f146d9b2 (commit)
via bf06401cd681a172444c7a07b8f99083be86a508 (commit)
via fa392e8eb391a17d30550d4b290c975710651d98 (commit)
via 27b1c2767aa9f7e7b74a640b548aa099605b28c4 (commit)
via 0c84643032d36c4b28fcc730872f8866dacebe2c (commit)
via de20a4c519712d5076b3e26c6eb1e4a40f689ed8 (commit)
via b47cd4c09f84751609f52a4a989889a7aa475b2a (commit)
via 88e8eec059a9e91725f8a12ff76e6f9c3af40155 (commit)
via 69dfb4544d9ded3c10cffbbfd573ae05fdeb771f (commit)
via 199beea87baee4dd3d4339ec20c32d0d2cb47f40 (commit)
via f3ca4f39256d94a48ed38f886b70d8195f89c83d (commit)
via b4093345f5b38af66fc7b2630baeaf843a817f74 (commit)
via a354d4ed95f1368c961efabea0eb2e1db95675c3 (commit)
via 0a2277b4be0ef6e326d170ad7a0836a17b82c0f4 (commit)
via 6b3b78fa009e557f65deee688302c573106a3ef1 (commit)
via 4c8efe3ad82e6f62f663579c4e792f70db7012bc (commit)
via 3bfd98d48a7b8e132931a950cf5d52ae1b324159 (commit)
via d06ff546486bc123c1fafbe8b20bb5e60557970c (commit)
via b154498887ecdedb12bcde74d91d2e76a3f57e1a (commit)
via db2e9d7c08423014a3aba97ffe3d48d23b3ec28a (commit)
via 4ee02ef54b1056af032347aca528dd1d78c610e9 (commit)
via 65bbffe7c89169fc958fad642185684154c85aa4 (commit)
via abd65c5b6ee4df38adc9dd03207525b805067544 (commit)
via a6e56d56d0a24ba1b8ce4c77f3ae7eddf56c7d24 (commit)
via e2d04f2d4343bf4c9bf8384f44187145c76d1717 (commit)
via f0f403fc8e950db97ddb71a0fe1ba2fc9ab13b3f (commit)
via c66811b7635660c28041c947cbd75e6dcc322168 (commit)
via 4c45f29f28ae766a9f7dc3142859f1d0000284e1 (commit)
via 9a2bcae912446f9ebe821c1e226f9629f89fa8fd (commit)
via d6cae3d58234c7fabfc8d1c9bc5b1b59c7c1e3cf (commit)
via 91a4343fe608ab91dda3f8caf05fef277a2eb215 (commit)
via 7b3ab876a7fb697245c81d03bd477f81136355e8 (commit)
via 4963031b007d9e9f15cacfd058edc20a6d33bf37 (commit)
via b4e44a1c5f4f1fa5c16af5bcb2a3ed48a29c1da6 (commit)
via 73feee77d8a05f458c90990c8ff95f3338c54d72 (commit)
via d2b40a13c798fbbc64ef8aa8d02f6572405ab160 (commit)
via 31f138b96f8713469feb16ff361e40334ec8b83d (commit)
via 4b79543220fa1f81ffaf16bdcfb79845587f51cd (commit)
via c7fd8cf0745b7a13c3ddf0e8309b64c7bdb18f8c (commit)
via 11d850685bf9e0d6a46b434fde322463a056c95a (commit)
via bf60255cc1f000a7251436f2461cd8e9b31e52ff (commit)
via bf807435010663e517215f69a5777b76f903927b (commit)
via 6287fdfff0892a1a71312e0cd2109a03c00cdaea (commit)
via 6700ecd585dac224bd92008fef7f6ee8a275d04b (commit)
via e7f74b8e4b4b8b63d1ebddac464cf713f18e8a15 (commit)
via 953b4dcd1d40790aafa513ed9be7409da2128ae9 (commit)
via e0f000ed71e1eb4a83b8db1b28ff96b6d13b6481 (commit)
via a16cb0089b11bb4a2cbc3230ee6870693c469fc7 (commit)
via 104820a94bef55a4fa1de729723cffc0e3801ac5 (commit)
via 81d23e305105f215c3d6142f750445f7e2ce0fe2 (commit)
via 1e763b3d4808e0f722e2f971b726cae5aefde463 (commit)
via 146f2e6c679ee84783d3b260cf96dac35de9511b (commit)
via cacb5d70d80379f908b368f933b72815393beed0 (commit)
via b9c84e0e73848c17bc4e5e43a31677d0d84dc6d8 (commit)
via 05e0a3a8a9d6b142b8a6a554c9ce29e531904edd (commit)
via 8076ec59d2a4efa5e7bc2b28d47749819acdb32f (commit)
via 27cae0d6fd4b8f5c0fa205aa67e236e9572b8e64 (commit)
via 23b8228fb4b4fc431a12f1f13668f16c2c7517a2 (commit)
via 29b74f8137e19df1e0389df99d9b20f0fa231f35 (commit)
via f217f1e62497393d63fde870191c9aeda6d75d14 (commit)
via 3b86761e66244e8536b3fbb46ba1d74bbb8dd526 (commit)
via 858d5facb7af996524fed919231d5ff55ad54aa4 (commit)
via 2fdd3551e1012e61c414c475741f0013fce9e11e (commit)
via 2b67b0507c180a32d14f843d26b99614cb1a88ae (commit)
via eab6ead64d7f7d78b024988bc29e95bb28426a37 (commit)
via f85cdd324c2d51046c2030587d8d5053b4a0ebbd (commit)
via 754c38ee58da3a9d9667072c16b58ab53d45e368 (commit)
via 74b59ef97880f4bba6e660b32c45fb1a8027c727 (commit)
via e9556924dcd1cf285dc358c47d65ed7c413e02cf (commit)
via 946b1dfec880e605bc154bc963f4b3bd860c1b55 (commit)
via df3ee4b9e04459e2ba6dbc50702b3c7f29c0ceb7 (commit)
via af37bfe0c400e0dd923f57e9775d67a8f17af802 (commit)
via b82db9d608868c11c68d692458769e9afb47f871 (commit)
via 784caaf1d2a386777df43178579417107c6492e4 (commit)
via c16ff5e78c1a0f80c57ce8fb7235674b839fef81 (commit)
via 9036a1857725ba935a78b4aeb9750741df9f1287 (commit)
via 430bc7504e0b6d439440376bc6b1c56fc69280f7 (commit)
via c6c92db7bae905d428a876996d8414ee1d278fa1 (commit)
via f00928ec99591b2cabde3d213f7756e90b1e6f50 (commit)
via 33ffc9a750cd3fb34158ef676aab6b05df0302e2 (commit)
via 8544c8e88d5519acf996ef6c4659b75b52ce6839 (commit)
via 5db914470d01eeb3ebde4b71f13647b093df3138 (commit)
via 1cef6595d167a14adcec8674ec51d6059baa06fc (commit)
via 04e248bf10707b003b4584a2c06e9733f6729d28 (commit)
via 61ec72fa83146fe30e2165e02639eab082531712 (commit)
via 084f1f59a6da39c03e5de0ef079216ef94e277dc (commit)
via a50e0a4671586daa8142b17d4f1a7d2ecd9e44bf (commit)
via 07326330d220e74b0f84572d601d50d83ce48945 (commit)
via 71e0b2b77c475c41ff73668455d1dc447f01e8e5 (commit)
via ff74508f934161e722d3a9d64158c061b46039fc (commit)
via 53ceb71e36d310a2a4cd5fef7ae9fa5d410f078a (commit)
via f22ba824385dd587b9e5b54491a225c192d823e0 (commit)
via 9e95314aa024cdd3ef97d17d48e68cd638d2be84 (commit)
via 000b191c2224d9117d02636ececcfaa23ba69447 (commit)
via 44851acb14ff52295eaa9f3f68ffedc04c19de84 (commit)
via 371ff753b03a8d5149041076b5732e659cae80ea (commit)
via a622140d411b3f07a68a1451e19df36118a80650 (commit)
via 69bdbf32c693ddae8b595b45b3ae78582211c553 (commit)
via ccf16f3b82314b94b3aaffe2f58eac7464f404a0 (commit)
via 66d1631eddb33c06140fc22f65b974562a8ccadb (commit)
via 96b66c0c79dccf9a0206a45916b9b23fe9b94f74 (commit)
via 7a7dc17a59cb4d66ec4f7f4cf96afa81284b3040 (commit)
via 937635d75665f331226774ca1b7c5bff791b9d69 (commit)
via 1b9e3430ec2b9a378107877e5470bf567ce17601 (commit)
via 0a9abd16730fa7708967c4b17dd1a36b7c75f363 (commit)
via 33bd949ac7288c61ed0a664b7329b50b36d180e5 (commit)
via c9095ff3de68440efa427a536985bb01e0125504 (commit)
via 870cc9bca34feab530b12cb870fd92f365513571 (commit)
via 378613249f14406399304f3464f249933108417b (commit)
via 384a5661d18a3521169bc96d90b9bce087b0c62b (commit)
via 3f7c07ffda7d4435c0bf241396f914cb336ddfcd (commit)
via ee44f96ae1e8556fa04077f708287cb017dbbd0e (commit)
via 1779eb23b4a147af7585f1c7d090f7c00ecaff6d (commit)
via cac02e9290600407bd6f3071c6654c1216278616 (commit)
via eacf5511e4a005c343b78430c9f749ba46a84c3a (commit)
via 3c704fca950603ec97ad259462500fe6fbd549c9 (commit)
via fdb64790c2d2e104630ffe208439048f0abc50d4 (commit)
via 403c7178590825e101e0822715fa557403ff5c33 (commit)
via 68863a7847788b6dfa9de464c5daf7e48db6a273 (commit)
via e744b4b23d47a3467cb0a69a9ace6baa1576ba5f (commit)
via 88e7a72ae29daf1dfd87cd4fb6ae31bbc6fb9684 (commit)
via 30fc969527cf3ae250cc6d2ee0ec0248c4b943d0 (commit)
via 91a0d273da950aac6348bbc900ceca502a6974db (commit)
via 21d0817bae3a1aded59cf3809bff5856dbba792a (commit)
via c7d8adb3fb0903c2b3a8627de14e85742f338d83 (commit)
via 2b7e75de166cb416a60080ad34d8acbed4fd8119 (commit)
via f8205314f85fa56bd7a8b783764c9e4ef050ad6c (commit)
via 9a045959025fb0961c7bf2eb09603f7604d5cbee (commit)
via 82fc64bf97eb7d50e22edcac28cefe0b93b9a7ff (commit)
via e8b5540d1dbc1e80eb98d15e1dab3b9c26fe7ccb (commit)
via 3f9a24a0ddf821e8e16b2c99d2e7335f2c40fa09 (commit)
via 65f68f7b9235307f8af029b37ebc7c2a309ed7e9 (commit)
via 3b1551b28913ae83e62997de0c8c4dbd28c25217 (commit)
via 23af78de3f2162aa27920e0ccc2784d76e0def8d (commit)
via 22eb335a0216d642194450947b28ad2ac3f771f0 (commit)
via dd7864295bd032ad3a832a6ba7d35d13b89dc922 (commit)
via fdb992f0a2699cdad3bcaef79c2ca35f8e700c16 (commit)
via 0d8a7d1e48a1b1df5fc09a65bacd550a4bba6d00 (commit)
via 0e61367a4241444a7465b25ee461adb7e829acc2 (commit)
via e4870b2ea373a314214f1ff575aa675496557b00 (commit)
via 135070d34df3a8c70cfaec68fcdd43704e017fb7 (commit)
via e893a8d8b65af8f4b4d01ddf0388fb2bceb52ea0 (commit)
via ef8e4e281a59bd4e3f5396533f96e54b7ffd1434 (commit)
via bcabe81c90f6e6294b714b4aef589f60f6b6cca1 (commit)
via 8c2655df05e51decb5cb0ff7ec48d484341b5f6e (commit)
via 223e1a93c3a17e229194899151cd728d3f6e72a1 (commit)
via 9ac31c2a7d7d2ee78ef2de4d56d3f57f6f45d75a (commit)
via fa304f6d472075939f45a1e59a45c6a6c82d8ac7 (commit)
via ad304b978c110bee6e97ef3010776bbd8733e813 (commit)
via 44890bb7db4879313473787c726ae25dd6eac335 (commit)
via aef76a4ff2d463f09a2d82e304d6003025646a5a (commit)
via 5305956b8ed2e5983eb4255e959ffc26c6017693 (commit)
via 5629c01345df4fd78a544169fac69e2afc9322a9 (commit)
via 5dc9b2f5c86bab3a7aec205a2f5cf91b2b6398e1 (commit)
via 23fa01352842de0a16314734ac061f87c81b662a (commit)
via 21ef42e572becd4001baa3fcd509b955e077c656 (commit)
via 06602f5a998ac727aadd8e094e633c569487ac8f (commit)
via 493b6d66358602da6281641df2c80833f2b10b94 (commit)
via 1b437a9f90cf46154bd7cc3c0c63315293d8860c (commit)
via 2ca8aec80f6a9dcb4da01a9057e9abb9717ab93e (commit)
via 9a160a14fcb4d4b781ee79fdd4aa60f8e56918b3 (commit)
via 69127fa628cf8cf44362b9334ae1c0e6c6ee5475 (commit)
via 1eb1178479503a5daf0f0ba43bf729d64c6ecd7c (commit)
via 29c3f7f4e82d7e85f0f5fb692345fd55092796b4 (commit)
via d538f9ed878a0abf65a91b1ab70d86d58aaad9aa (commit)
via 3f3bc96b8f17677c90dc7cb049a36666164ef908 (commit)
via 43ad3049d20a10c2a96ae85b167694aaaaadb6be (commit)
via 25b5b6cabd4eb505b0a003718c30b3114440a16d (commit)
via 1032577a333784acde807d02124854a6ca57de71 (commit)
via 53c635403b6bd8a23c6f0b24bab357c6a355f277 (commit)
via c3a8a2254eb9d4226910524936292aa053fa39f7 (commit)
via 3b6cd417ea4ade9d616b12e7cb0577f892bf18b6 (commit)
via 9d00667f84006fc001b04953519e78b1ee5f0010 (commit)
via d7885ecddb52be49de729021a70b42e60aca2cea (commit)
via 63df2f7cdb5672540d176054f5fda0d3a93707e2 (commit)
via 8bc0766533f5f78c4ef8bcd9438f816e58c3aa83 (commit)
via 1f578c9b208b0fb2e58b101d537a9d07f66ad690 (commit)
via 4f63e23b7aac425bafd4e0c6e5c4099b157206c5 (commit)
via a4b1c8d4bb37b273c9027f41b00eeae54ad2f83d (commit)
via 4a8316ddc1bff361a289ed8c406bbe90fde79e16 (commit)
via dd0c9ba580d2b87324e3063de6f69d2bc8be5687 (commit)
via 5c0c1cb8f090edf516c21cf03506075bd866915c (commit)
via f8af3f0612dd39b81283af83b5a930d0754dacf8 (commit)
via 583b148b75060920bf8f394239973caa3868993f (commit)
via 15783473147022e77c16fea2c314abb5b1db0ade (commit)
via 8662231f00b931f1d72c260c1d82fd598caaf840 (commit)
via b8387e4c0167f8521dc34ab14214bc79ac847c4b (commit)
via bf94c3c6175e20a65ff1418e4cba692abbae159b (commit)
via ba37fe42bf0e97653780bddc8ecb47a090c3ccc9 (commit)
via 41a8bec7700a7792e1f24883add4e542977c111e (commit)
via c37383f57aa3d6b6cd4911279a143091912c0e32 (commit)
via d253c3c481194eff6ae52861624d0d0fdf44b4b3 (commit)
via 854d608fd26a2b9c1cee43f5ce48d75ac7223e22 (commit)
via 83652c539b9e0fc04202bef8c6abe6fffad1f58c (commit)
via bb5f0f9677e89c5fa45a6e54c2176a2496cfa6a8 (commit)
via 97f58d9418e14b7076850c529619dcd1efa41145 (commit)
via 4a27055866a0ec36350d20422713174d61543015 (commit)
via a5ec1487b162ef818186ae92b54e5c73d8757bd1 (commit)
via e7971923e90e58921feac425b7ec9073f4a22c95 (commit)
via 962447aa16c4f4accd42fc326d9408cdf75d2dde (commit)
via b7a8ae05f15fb10c0e2215565f160889390c3c8d (commit)
via e346959d031b0ce05939f801e83f653892757e49 (commit)
via 60b242c2d161e30a05236fb6dae3ff21daa0b6b8 (commit)
via 307e3c7a946a593945050b9ea8159df85f5cf28d (commit)
via 2e97db1d7db5a65bc19f6909269ae930d2e8da18 (commit)
via 4d5c20e29fd4a74db31024bdcdaa99d431af3219 (commit)
via 20df0b0ce702241ce8265690388483c5ece3eb16 (commit)
via ff8d95f4e0afc3d2b8fc5f3e75eb41edee1f969e (commit)
via bd56a0715a100c1fc0b643bf14fd0467a5ede80f (commit)
via 1db082c00f5f3760c0959e4f4008ba5e5d0e491e (commit)
via c9de713dfe88ac13976d31dafb28a928d95fb239 (commit)
via 212ccdc84508f1c03b2ae3790067011e1bde0ba2 (commit)
via 613a57b59957e8603286230e8ad92ba8ee317081 (commit)
via 99747ec274d20a83fca6d230d2f384560acbe54a (commit)
via ec52d37b4554e01d70afa67c64d203c445a9aac6 (commit)
via ce1d57a6341ca867ce6624424043b13cfa3d5f39 (commit)
via c3c0784a907aca8b915221c63e9228a81e93c346 (commit)
via 785ade580637cc0d78139edbda05ed13e1675dea (commit)
via 85655172ba34a2d3c44731880f0e439d8933d277 (commit)
via 49316890b86159c3ed72ef0913c5a58d4e8e5657 (commit)
via 2630be5587b6548b0217f16fb78bc80935013a69 (commit)
via 43fa9db5aecf34e77a211c43c4cc2cc6e27e7530 (commit)
via 492b1ef54094f220280d58b42ee0749ef829a045 (commit)
via 7c0534d4b3dcd4b318dca8ebdcb1f1424c14931b (commit)
via ce9fdb302d9d7eac83eb2bb8f03c5b5b239cc23d (commit)
via 4f3d61ec6d64807a8f174c4d028154732c4bad77 (commit)
via d919490c1df6f34147d38e3d0af91d8b836fe25f (commit)
via d9b1da77a397df06221431acaa3ba3caba40bb7a (commit)
via bd65ec46d25bb56ab77f50ebf8d6e431fda2d478 (commit)
via f5f3e572144232518b47dba3ed32a6cded151a47 (commit)
via f5a9aeba7e7b55411834315f192c77d98d3d82d5 (commit)
via f0f75ecda5cb7e51fbebbcf35ca40f3d38b4b535 (commit)
via 70a919f029b159119a50ac6d6fded6b843167ef1 (commit)
via 2b431168cf2961d0a04b07cfb2928b3778057d6c (commit)
via dfd5516cd85bfda7bab56d161082d2ce74330e2b (commit)
via 46168c33b8ff9bdc8b5448c350f2037453e0c2d9 (commit)
via 339e9f7077179abbeeb3a3f16a6f1f95cb22786c (commit)
via 32c1b0777e781501e347ac160e176d2ab6ddcfad (commit)
via 9a8bc5b083049e6d42fe38397ea44375d8634020 (commit)
via dfe737706fed6ca7aac475fb91d988222799b21b (commit)
via 62c273605b092d2c6f8058a31c7fd318eeff0b8b (commit)
via 3c941835b81c1ad7869f38bbf74d8e0a7fd567a7 (commit)
via 4778557e13c231f1bd2cde5d0950b68de920d8cd (commit)
via 7b2ee20fb35a475916a817c30668d31d15fc8907 (commit)
via 61dcaac0cfc5b764e0d5ca144128b1aa7d5a311f (commit)
via 0363b4187fe3c1a148ad424af39e12846610d2d7 (commit)
via 1a11b2d51ccff009c2caf4e132ac18e5bf92b11b (commit)
via 63c8beeb9a4751f512ade6c72e9dc1ce7ed5f158 (commit)
via d9f12c0e4d904fed50e43d5602c4d97563d701d7 (commit)
via 517ca63bd5adda6a2a2f40c889a87f1bf8f7832e (commit)
via 80d60b6b5653d581c6cfe1fb05a4128eacc4e1d6 (commit)
via 879fb8f585a5fe1af737ad41ea81e35a4cc0cfa4 (commit)
via e8137e183ee0b94ccc82d47d1489a470d551bff6 (commit)
via 347f81283182675265cda722d1def0bccedf4c7b (commit)
via 94a17934a24d94741b53857d17fcfb70a0cb1ed6 (commit)
via 766d7f47299cea598fd47bb510b9366de6da4356 (commit)
via 00519912eb96e6fe6aad91376be073977b350f4b (commit)
via 15962b4badfed5d448826d2d55e8542314498dee (commit)
via ff8f84b695de0e5350af325c675ab2bdbab79cc0 (commit)
via 281c6ab13bf40a06055a64b3e5f69c0406d33db0 (commit)
via 8b106d20e3afa459ef9ca13401145395d8a6dcc3 (commit)
via 8117b69a1c1c8c569032951254e5a094763d74e9 (commit)
via ea8936112837ab22263edabe5c44f1f40fa0ef6a (commit)
via e42c4c83835acbb17075759f9dd206dcff4a8d37 (commit)
via 40de1c10db09295fb9c0014c5ff4413b44fe8aa9 (commit)
via 70fd53cf6bbfc536163e70d4621659f840fed83a (commit)
via ce7f53b2de60e2411483b4aa31c714763a36da64 (commit)
via 678965be5a2ca3ba12981884e48911028953f853 (commit)
via 71e772824618ff3399ed6b2c6d1e24103fb8bc29 (commit)
via d47e9e7db10822b6197d7be175a0146e8252ead0 (commit)
via de096122c7d18fae6f0afed28ccdf0bbfcf65395 (commit)
via 2cc9047529f689aa7e1cb08e2d6321c2e1b76049 (commit)
via 8e3eb1576a4f04736aeb7d44d9b13d19729a7062 (commit)
via edd462bc9e8f727b1ebdf14b3f7cb6c844dee35a (commit)
via cd17ffda53e213f39c0e02f38b415b31806c331d (commit)
via 119eed9938b17cbad3a74c823aa9eddb7cd337c2 (commit)
via 7e9c676a0a800322d77ecac81bf223405011442c (commit)
via 147db3ecd8be5a6fe692c07c3d4d7597f8f2fceb (commit)
via 49726757166a3a3e1374091f76f12b4203782c98 (commit)
via 84076d913d1e974e504ddac1c7b8a22c9172bc9b (commit)
via 122da90997b11cee73dd63291216aa4c6c6ecd11 (commit)
via 400ddf1d85976eb07188e6fa20ae0a83274895fc (commit)
via 479c0eeabb8cdb874385fd9ef2160bddf8a0a80f (commit)
via 836fabb60589aaf7b901176892b6c28ab464e8d6 (commit)
via d2805586d949f91b9ca649206a1f4b03d7da4193 (commit)
via b6b3a1a1073b2f674c8ba189635069d3ecadc8bd (commit)
via 29ec8dce0f9312be9cddb3712fbd107fb62e33d3 (commit)
via 5156bc4dbb05a5803b87e57751ffc7ba94e4ac98 (commit)
via 68e9b6e28b981e394f1fd55de00840e861695044 (commit)
via c70bba06b9157645d6493b16ef0ee414277ce7b1 (commit)
via ad6d16107655d744062af1e0570abc2023ee1ee2 (commit)
via b5d7336d2353cd552c2b253a358f612b54b56e54 (commit)
via 745c28556fd0d85718e7228a4957448b2803a348 (commit)
via 905f265367c7b4ee5486886faf2951cb655fa60e (commit)
via 201899adaf91b1c8f7affff546c0c00de7746eab (commit)
via 4d3f1c82605bc45fd390ee3829dff1c3bff0cab6 (commit)
via b59d780f0b9fd66c06eb7d06082e41249b778f94 (commit)
via ad797b98b8ae4f0f003374c13649147aef3c1a83 (commit)
via 8802d414240a4b453b13573e1bbc381eb0d14343 (commit)
via b321b2e7708069b82bae12eb1b4461f4eb9e4a03 (commit)
via fe630ca4be50f0f0bd048eec264b8a21df9c6b38 (commit)
via b41986aa9d6b0d78642082abeb8186ef5d7bf431 (commit)
via 0a2f3129eb9f3c9319a3b203fe1bb8bc386ef212 (commit)
via cdb550b4be0db3a37ca40426d288672615335cee (commit)
via 7f62c19c5d561e755570b8aeab625dd275ecae3a (commit)
via 4c90b6cf817c2087052ea8b22e91a3b7f8ae39f0 (commit)
via eb8aaf5c3b048047cd1cc138ee4951405bb59ce3 (commit)
via c97e9105e1c25d4708d2f153c93c1a29a73c9537 (commit)
via 30c5683babdc96906117dfd677c3313d3a82fea9 (commit)
via aaace1a1f1579d5cb2a657c11115e601ef1d4b7e (commit)
via a852fc05305f5c062ecf9d3c9ff6a5b836370d6d (commit)
via 53e18a35628a7a595d0de637ae4a045e41fe6b64 (commit)
via a1ce8481248f5859f8319ae85d9e25eaa0ace2db (commit)
via 0107d1f09fb6075c9fc2ea6a162c85d4a89a8e85 (commit)
via bcefe1e95cdd61ee4a09b20522c3c56b315a1acc (commit)
via 37583984483d4027f5e2fece88e3f7e12ac42fe8 (commit)
via ad11aede925c7307dd3a6528be0d1e8e4723d162 (commit)
via d72fddedb47c7dfae908e2c5bfc9c6ca1d574420 (commit)
via 37e3ef0278c0f52a2e211b72d9b39e7e25262b43 (commit)
via 75af5cd564de8cec47f2ed5ab3b5cb028acdd0ca (commit)
via 1073338ae5e5ea9fa385abf5b87ff3421f51ab97 (commit)
via 1450d8d486cba3bee8be46e8001d66898edd370c (commit)
via 88d5e6c5a78f2b55fd5518a865bb138c52e910b0 (commit)
via 2d0fc10cb714c3e34bc670a86ba645b42d3bd777 (commit)
via 451d16ebbbf619ad5b420b7a837bf95ec23ab686 (commit)
via 393534ac4698e6dfeee32ab4bdf99e25eade4a76 (commit)
via 954430281cec4d8f6382285f761a1859ca9508f7 (commit)
via 98b10b7662c77074dc26b27a4db1023b18821942 (commit)
via 93cb134fd8ff6fc6c9d63d0a9f6ca3defed43721 (commit)
via da42298dc1ad39a5073bb8b221e05ccf8fb5464c (commit)
via 94aade43c190a9dccca5469009b2604efb7c5664 (commit)
via f3507e17b575821adf1076236e2855c1d7be1f26 (commit)
via f290b63a15ebe4d4f26908c2c9948b43188c4a15 (commit)
via ed58ec0a82ec0b9df0e0f9bd53f1a32123c9fff6 (commit)
via 03b6bd8461d7aee84ac34da1c737fed4a3c16c22 (commit)
via cbaab234b0fe61e09b559c7623e3f29bc89ba1b9 (commit)
via c5f8c4c31d08049b5b8b1e7ab43011d62293f18b (commit)
via 0ecc1e01cde7133bfcb183ced46b011b4eb9558a (commit)
via 09fff7c3f6d538b80e8a1760673bc4a3ac9f62d2 (commit)
via 2045020b58739c4f1ba8c6d39ca118513f8b2e35 (commit)
via d3a0ac2204c90e1bb6b3ccb474f7224f1154a0db (commit)
via 945103ccf34fe1140877c1dda05a98eb4ee79d98 (commit)
via acbf4d8c9e3deb825a7c193a26738b12ef870166 (commit)
via b1a6659c841d75b8e918dc3a1e165188d97d56fe (commit)
via bc9d924de6ba3c7ddf25e3c77b2470b17667d068 (commit)
via 9181c52cd0d827a7c62538b1590e1a061a11c07f (commit)
via 89d7de8e2f809aef2184b450e7dee1bfec98ad14 (commit)
via 4660c86fe6aeb2c77427ca8cacd1b7f840d4e554 (commit)
via 946126b36db6b9cd21c194a84a61417dd6bd567c (commit)
via 8c6afd880d47f33174b656ed12a2e5e4c821e14b (commit)
via 22407c48db5999e77a6fbd6e4f42ba2a3fe36762 (commit)
via bc87d1113832e7900d3627663c0a1203c3e7b2da (commit)
via 6fadd98cb5a9beeb79c58ba0a2768d9ad895c210 (commit)
via 9a3d9604b0fd9dd3dcc5f00b06ebf08e4e4f68a5 (commit)
via 029681ebd000727c546f8d387df270e9c1415bd4 (commit)
via 9a551ccda02b841bf296ecbce5c0bbf04721024a (commit)
via 2f3d1fe8c582c75c98b739e0f7b1486b0a9141ca (commit)
via 506fdf871f529b20b295c8b3a18286c5f295a806 (commit)
via fbfd9a49b562e3f5532e87ad801baf82c6b7a912 (commit)
via 14ee53dc65a150d46f51e4cf6d666c38187ce075 (commit)
via 9344c41c186e020cddf191880ff5bef4e653b461 (commit)
via b448dabbe9de31366ae5a20c0aae423f6257d953 (commit)
via c11f99307c27f07ad5b8ab9abb80f6ee5be32efc (commit)
via 0eec3b5ce47348b8fdf8eae9ffc61dcb4db0ccb0 (commit)
via c37fd56ccccd540bde166eb7f29c2c691bbff154 (commit)
via be426ec46dfd7aa3573872059ac9420dd4fd7d75 (commit)
via 208c318edbba5b98ebbd865f15c31bd77626328c (commit)
via bae4e4960422f2f86af1fc48d1f1d1a8f23a1b95 (commit)
via b0f2acac75969ee9773b3ec3eb584e017cbfb117 (commit)
via 5ac7de770cd112d0bc3842dd79c612bae3acc611 (commit)
via 716006fe1be4edaf841b94d488cd1d8cbbda116e (commit)
via ba20d4a58b6d7ea93c69681372bd6f1064491d1a (commit)
via 459cdc4a0dd14c3c21a8ce1e51bd99fd53026a21 (commit)
via 11cdfbb065c969dc5638c8c146666da4b3195677 (commit)
via 522bc3b62d92f30094428d580392ff427725cb0c (commit)
via 43c537178a4024702196d28c6408917c611c3a66 (commit)
via 0b8dd6b36af411f1aac1f48b099e36282c51da98 (commit)
via 48054d5a0cbb379af956c32330f919467b9d6b23 (commit)
via b058641f53581df0131c18e0e521d74d71790a23 (commit)
via 08fbb8a87decb5d7b9cd1568e515eec22e17e5e3 (commit)
via af2ca1df688ca14a0f58bb7151b19ee7afb88776 (commit)
via 5b1c236c65180291b33f01b8a0f7f1bc46a4d1a3 (commit)
via db06972d3798e67c5d581234c673cb4b9cdffb7c (commit)
via 47b58321cd15762b9cdd0f09e66e9f5b29e67eec (commit)
via e1c60036dfd2d4e1fcd372807e4a3f0f201028cb (commit)
via 9bd788945d172e66939dc6d8fdb3016803696cee (commit)
via 2118111e02bb90ffc92e14d37681acf3c5f52b98 (commit)
via 7f6ff763142c2a503f06410eaf4f8fc8e9259f02 (commit)
via df1c5d5232a2ab551cd98b77ae388ad568a683ad (commit)
via d66a48434bce9de43f371bb37811c820634f9937 (commit)
via c178521c5c7133093866a8403b3d1d8af2cb6684 (commit)
via 60635a58548ae680058d67837e6ce1696c3b7faa (commit)
via 660990cbc4df062e7814f86040f7cf87662190c2 (commit)
via 8c4c6e96fb6289bbacc2e4d15b8a7b131c8fadd8 (commit)
via 27e9cf518daa0a9f668b744b56b57add5c4e10d5 (commit)
via 191f569b711f30366f584045e4cefd9211929ad8 (commit)
via f687f77020bc9c4619915e7bb1d7f5a7c6c90ccc (commit)
via 6e0725b08ad12bafcc939622daa5a2f7b734ecbe (commit)
via 8377d0dcf3c758cc50111a98a7a9dafdc3237c44 (commit)
via 5e9811640dc3e514ee012b16287522f7993ade46 (commit)
via d007842f66f874ad8ceacf664f8c0645d2ec4226 (commit)
via d25f0e1e01a6cc3991ae63f0957c61ec171395ce (commit)
via dc2960e9845fe08a27d49dcf9d6474ad8b825fb1 (commit)
via dfce83e671afebb518b21733bc67e3627320e568 (commit)
via 7a8781d9f723a1783f9e027909f4e117ce532a83 (commit)
via be9f19012418391c0652a7f76e3bd2eddf72a351 (commit)
via d1d37e198212833b5ca1e5c943f3f8aeef805a17 (commit)
via ad8e7ea08e814913ff6fadbf593a3fba44d4bbbc (commit)
via 26a9ee9fd3b8d2cf0c9643ac5441bcedfa3c2ab3 (commit)
via ba046cd24c29b6b91cb49492c4dafa72af9b4a18 (commit)
via 02fb3337a339f2043183ae66062a774ae6375e83 (commit)
via ab624f466e084edecb4a6299a56f8d33282a1ba0 (commit)
via 0be5bed016c3b12abf5e7049ab7b63d2d474efc0 (commit)
via 2e6d5f8d602a632d8a770c13d7ccbc09a25825ad (commit)
via 7642e09748501d041309b84de9d46b3aeab74a63 (commit)
via 9dd3b10a7ce79fb1b1289136987f82c2bf9c2310 (commit)
via 739735c08bb30957be78506bc4cf0d6c5f01745d (commit)
via 0eb1e5059c26a083708f6d765035aba8aa4c6d2d (commit)
via abf4296a7becc39021bc31594b29fa540a86efff (commit)
via 0840c03b4269e5fb478905cb98f0bb04a5555bb8 (commit)
via d3752093f7cca1ade17038ec16e52e9a86f31115 (commit)
via 3afd0a061c6c24433c858142c91746d2702f0d04 (commit)
via 4813e06cf4e0a9d9f453890557b639715e081eca (commit)
via 01e052f372b1878b9bc40bfcea80d37e408a58ca (commit)
via e85c7ea5983687a583f57964694acce71a4a72c9 (commit)
via 25b99148984082fd818624cd74cde2d86d428c2b (commit)
via 97a7be5ddb8f5a89206912eae174bacf3d230a8e (commit)
via f37600d5ae721b626b9e9323dbff17a404908e46 (commit)
via 6fc763dca0e5c617de20cf49353364219987db63 (commit)
via bbed8c3d28fb5a8cecd3c6d2682a8fa4b01c6566 (commit)
via 55fe56a1ce1c5bb0c359942f37d912a18041a2ba (commit)
via 23eca85ebf0a8a6f749984504cba553e2c4d38e8 (commit)
via 09ed404f9b47807c74e0c6e59077e95a7b28b953 (commit)
via 46cfb5cccadfaaea4568d8478c3ba349c9b877dd (commit)
via b6bd57985bbd40a4cd76e0b37c3e5ae7d3563051 (commit)
via 8ee676bcecbdc959c24fce66ae32ddf94fe999af (commit)
via 0318fc199d2d653198294eed8bbfd4ee69b340de (commit)
via b47eafefecf38d32013bc7377aa4eebf531c49f6 (commit)
via 0239399581611def5ec0029ff48babb818cc7eb8 (commit)
via 416bae7d607e43d00b3661a201c3162bfe7e8270 (commit)
via e974876675bbddc9e2f75999f7902d0d0c8e10c8 (commit)
via 36e6a75b004f79c320c8d6cc128c9f40dcd1099e (commit)
via d6d04321180adf453ecb64c28e03b2c43591e521 (commit)
via be67ff2306c8aa98efd9ae3b017a2926dd3fd46e (commit)
via cb5eb7ddeb879f1658e446d06e9556aa075d4e3a (commit)
via d69fb646ed39dafe4277334a28b52d948e6348ae (commit)
via 655179cfb251cd07a03469524e91c4ed81b57803 (commit)
via aca3456d32de52b95c0ec0986c041cc9ab63e132 (commit)
via 55a49c67cdff76b0dca55d425ae977fb3feda9c2 (commit)
via 85aef712019c4b0293e1726c705b6bfc749ad537 (commit)
via fcf8b9df5fb8511e9bd78f42bd797f2a8718c6e6 (commit)
via 7fe200c92eb411c554a82fedb9b4126686961f0c (commit)
via 0ef3b7adcd45bbd3127fc3aef69cf6798e69c807 (commit)
via 936b3ffaaaca125fe6a87ea3ad3910aa2d45c3d5 (commit)
via 695a3119423907a059435465f98669976a969f28 (commit)
via 4d94e850cc95e491ce268c9782c0eae335e7f7ea (commit)
via 2cfdda6be3038b9318c9f72da1b7c20488f2ae99 (commit)
via 01b2a2fa9d86fbc5796626461b282581e46d2979 (commit)
via e642b05a1f39d86f19bcca83ab1da8bbb8acad80 (commit)
via 84bf517629617cd0e5569ab36fae83c4df9c82bb (commit)
via f8dc48154ee1ac174b8d9cfddf302e579057e6e5 (commit)
via b9d570e44c4ee74c55e73371f0b63e89f2392ad7 (commit)
via 2caf9ff64737b348fdf6da3ac6d3183c8b66e74a (commit)
via d124a5b6f1fb3fb55918e3b75343c6bb70023980 (commit)
via 50b124aea4fc6c367ee64acb81041384c169906e (commit)
via c8b7d82c25958cdae8bb02e3a93b2b8d6dd078d1 (commit)
via e8e45c21b3a6a6c8169259ea5d66d8d475bfbfa6 (commit)
via 16e8be506f32de668699e6954f5de60ca9d14ddf (commit)
via 3c243b229dd01343e387ee9a729bce54711c5a50 (commit)
via 4de51f893dc92b863a9d3baa747153c2a011923c (commit)
via a1b3cfd353802b33b1ed51b51c9d98f6fe56d2bc (commit)
via 75445a1c6e208d27ab8d85166ebd079be947f98b (commit)
via 72922ff97062fbb154821827932d8a8952c371fc (commit)
via aa95c10d7631603afd110b43cc6f12da22fda7f0 (commit)
via 4e379f979dede89254759e40974d9c04d90ed451 (commit)
via fb7adf34e96efc9c2e0986ab3f567c6d38500a89 (commit)
via 0c8f4051f57e4e1bcc29436bf50a1f7d4ff9e79d (commit)
via 3c1fc036f79481a10628e3f841666c35a0b7b823 (commit)
via 5e66a7a9ecddb8e00f5ed44fa22a94d02a177e8d (commit)
via 95218da8e5bfd78889639e7cac68b4afcd44d264 (commit)
via c32bfa79b248fb9351dafc853ecc0a18ca2e3a66 (commit)
via 926dd635c3bca2100bd6a9975e2368183623c8ac (commit)
via 863be53af8c369067f33c4b5688b44ebb380f789 (commit)
via 86f31f3156e151a9446396aa04105c8dbfb6b592 (commit)
via f909b685377ab7c3d16f5903ccc54d0465721046 (commit)
via 9926f9b8f9f9ed697975752c62a46e34bf45e3ca (commit)
via 947d85d0e851a6fee3c34c734e3bd21065b57ca4 (commit)
via 61068e227821fa32305d4edd142b2dcc6ca74eb4 (commit)
via 2f41159cd685809c1dea5f079e99c5ecb90e1893 (commit)
via 8a91e6ec6c257a9e630e0408ac1a5f464d769a55 (commit)
via 240e5333012a0a60e9b42f8741c44450eae22ea2 (commit)
via 22362bbf67afbd7851f731f6bbd337333267a939 (commit)
via fdfe820d25fc0ac6b8258a448fd9188d4e1f7090 (commit)
via 6067533e5c4ebded8260ef4b97f2709868bb2799 (commit)
via 8d178815af6a1eda06930288f3bcb4b261e25bbd (commit)
via 77249210b9ea779e45b35271a915a50250e00a00 (commit)
via 72b8ac61724514b5b8b8807aac6d9e2c9da7268b (commit)
via a45069dffcd932ef9ed38935db9f1cd54188a093 (commit)
via be41be890f1349ae4c870a887f7acd99ba1eaac5 (commit)
via 282af2bfa3dacd3b8145460dfed2cd9ac294983e (commit)
via 1c004d95a8b715500af448683e4a07e9b66ea926 (commit)
via fb2e8b3a65c9e36b745c81766b8c3973c8bd5737 (commit)
via 9f0f84c089904c5e544a21bfb4f4def60c9eb0fe (commit)
via 17c0037b8ab08e7aaa1a9eee9e1a0753308b2ce2 (commit)
via 4b74e2550ba3030f666d03bf57f65f1d6d1a30f7 (commit)
via e42a6c969cda1e3676c9884e9697349e18db92b7 (commit)
via f4486e178f04944792e169c1a7a0c5098dd2c223 (commit)
via 857b8cbf4ad057e50232eb152bd1ec4903da807f (commit)
via e4cf4a7222b006c5534edaecb23614fb224585ff (commit)
via b11b84e52dd4670259d7c0a59d2e7cee4550b16a (commit)
via 1090b92f9a37d021c445b046b9245861df6ca36f (commit)
via 38faa03d1c37053d1c4ffe5e689716d42191a59e (commit)
via d27e68b1e1a5f124be2c02e0e6976afaabf0520b (commit)
via e58c2fd32cb85d37cd96ff8bb6a7cdf0f653a5a1 (commit)
via a043d8ecda6aff57922fe98a33c7c3f6155d5d64 (commit)
via 4700bdc96332953deebe6b6c4204177a994e4930 (commit)
via 8bab20ff2358dfcc7d7f09520688b9a07bbc1ea3 (commit)
via 167b544c41361a7cf316b042baf057afb9002d3b (commit)
via 00d53a32412c50c20c573f40d895e8ad7f74ad4f (commit)
via 8674224a8d108e92075436de2656e8ba912f7df6 (commit)
via 3de4ecaf232d8a75e9da6e54384da948807a0659 (commit)
via f43b44eea132aa6ed092a787d0933d5b119a4025 (commit)
via 7c41946c022b31c44c9e8b76b4c511c195c6743a (commit)
via 85a61f11135e898164d5e41e949bb6ba6929531b (commit)
via 0f0c175daf770bf863f32a4798657625704a0c54 (commit)
via 0a55e2f325a76a08c3feec9911c146bbf9e017a1 (commit)
via a2108be0aee261fe1046f96a94543c25937b55a4 (commit)
via 859e0b3e82d4cc5270d8fb557f0120dc35edd1a3 (commit)
via 835c041058405743d746a6cc02746018774540e4 (commit)
via e8757a6861d8328cf7f8f2bd815af3a6461cfdc9 (commit)
via f4d68d5f8042f235514afd7ce27ff7cf43785726 (commit)
via 99fbbc37fe2c3e465af2e1e5990b89602b02e8d4 (commit)
via ee109e14bb0c0409c689ca73fb9ddd5f2435e29b (commit)
via 6312ca7fc3a9cd0ed0a7b15583161a814162a7c4 (commit)
via de8d385ff22a2e60e26667a2138dce54677b59e4 (commit)
via 83d8c2ef2fa7b9e4b9a71f9f7ad54c1fa945696d (commit)
via c1233a28b0dfd3f287897a17542a3874f5401ddb (commit)
via f31569d8589dc7dbbcd46a423e67ba3ba9132e87 (commit)
via 5ace1ac933c66fea6c727d062c402677584c1f5b (commit)
via e2e93cd2354580d7707c07f8f73e07e0e27f18c0 (commit)
via 60aa5f582f596951f0e14e73164f53edc4f8b8e7 (commit)
via 7c623395e4217f10a57a7c312c2e4066ac7c77bd (commit)
via fc40317d31b6c76327ff83d81c9ee3598e05cf3d (commit)
via 63808a73aa582e3bac75c70bc332e3eaca8152ba (commit)
via 986925540226c118c63b33803914f9e5d44c0576 (commit)
via 68a52f5e6e31895b350bb049b7eee9f262efbb1a (commit)
via df40284b1ee1bd833f9cc79f0da0c1202d38d951 (commit)
via feae5f317ee4c0940fb49c92f08228e21b720a8e (commit)
via 90251f717001cf297ea3e203777918b8614b2f0d (commit)
via e07f341d540ce584b3edc424a8cf9a8f1b9a542c (commit)
via c533897b58690ab30d12dd9837de04e08d840ee3 (commit)
via 4ea6a9c51663de36095fa119698ded5f42d70690 (commit)
via cef5bd992ace60f4e7af50ba9e46d7e27aa9e39e (commit)
via 6c13b0c102f7716cb70b381b5f615e1b29a05ce3 (commit)
via 8add7a15c72e218d7a954c4cb2765206806e1237 (commit)
via 29f51829c08e4248ee79f073c466bad761d07f5d (commit)
via 0d7421b938c0017f364daf2bd4ef5d27aa1c66fa (commit)
via 605921ab9a037b9361ac4eafeefe82c9b0d83a9d (commit)
via 35edc2d075c899f9da26bf6644c8c113826aa16b (commit)
via e2f1735572f26c3f8003ec17f6b9be333ac4320d (commit)
via d9749e1f6d660696c07c2e3474fce9f291d5b959 (commit)
via 6cd568fb20e3714a77e98d31f0387e941702c327 (commit)
via 64cdf20648e3a3aa50334c4d555838213901602e (commit)
via 6655139a125d4f9032bef3dbbc0a49f9ae2b87d7 (commit)
via 0e92ed020eecca48e4fb045221d732d591d1afad (commit)
via 3511c6e6512c0004d9332ea85d1d3d4c03a414e0 (commit)
via cfc683caf3aa9c700a7b10ab689d949ffae2d207 (commit)
via 4317f74e10dbaa6a03a2bba9993fa2c79aa241bb (commit)
via b312e9ab66e413ee0708fc2db23d27e7f4b7b251 (commit)
via 27dc1e7a87eb35dfa6a67f44b8ddf2d66d26abab (commit)
via f75fcf0b19faa026baba075f8e76dc5aef4d31da (commit)
via 21dae0aa029e8d549c8ba4d5bffeb10ef341f17d (commit)
via 83966d1e3ede61d6e71a26d179e2d2d30b4655df (commit)
via 1e0c42db90fa4f02586da442501cf1669a93db04 (commit)
via e589380dcc531fb2c3d8bf87e0e2fed8ba09270f (commit)
via 28e4d9aaa305d597c533f43c8eaaae37567d6896 (commit)
via 6cbb6ed662f7ee7b959865eb741c6c0029d0afd8 (commit)
via 845ffd35d10646345dc1f534ad725cada568fa5c (commit)
via ce19b150c67834a91d7742f362a175904172a7d7 (commit)
via 80b1a680ca8b5ac8eda1b0b6ccb665e243e34182 (commit)
via 77af0818a39b800189928ae950839a43f3d8f2d9 (commit)
via e86811d1938f1f16fc7ecf44d09b08ad9058a756 (commit)
via 1e653cb517d99f96a7eed74a8ca457fe2332a15e (commit)
via 2b9c6a45464d5c064012984665bd3141c4aded27 (commit)
via 77520e9dda0fc9cac5bbd8bdf7225ecc52bd8ed5 (commit)
via 0abfd1c9e17568e36411cfc30e5e084fa84c92df (commit)
via 788685826e01b05cdb12c78bf367192d0139745c (commit)
via 221bb9df74bd6b827ea7dbc13cf07abb3ba939c9 (commit)
via ada6840f0a21dfb6368a22d59e2a24c815eaf997 (commit)
via e363285144493f03d8ba898cf762fc8bccf2f4a9 (commit)
via e1725e2609322cd2c87b0d90bf85b6c5b647dd66 (commit)
via 2133c6ae39aef257ad8c46bbffd0765de83bdce8 (commit)
via f597ce3956c82c48cd5c3623c18bfd01b506047c (commit)
via b6d51351a7935aeddcf3520e33dc5bf5e58a84a7 (commit)
via 544b01dd9fe18dd5ddb914239e5435854042b0d5 (commit)
via 10c4a13810da974778a046128414e392d9395d02 (commit)
via 97d4ccea5736cdc872913ba633154323c17a0e49 (commit)
via 21e0ea894b81e5b6ac02c369b89456b8d8bae9cb (commit)
via 2fb30723fa35fa5a9ffae03b0715650a76a5f20a (commit)
from 4df6d1597b68281fee3999542219c18029b168eb (commit)
Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.
- Log -----------------------------------------------------------------
commit b9c101fd307c164f7551d00068358828af4ed549
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date: Wed May 15 11:04:17 2013 +0200
[2836] Set the segment holder separately
Only allocate the memory for segment holder in the constructor, set it
in separate method. The allocation might throw.
commit 025659c532f8521d8a36476d2f1a14593c9690b5
Merge: 4df6d15 066c983
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date: Wed May 15 09:54:44 2013 +0200
Merge branch 'master' into work/grow
Conflicts:
src/lib/datasrc/memory/zone_writer.cc
src/lib/datasrc/tests/memory/Makefile.am
src/lib/datasrc/tests/memory/zone_data_updater_unittest.cc
src/lib/datasrc/tests/memory/zone_finder_unittest.cc
-----------------------------------------------------------------------
Summary of changes:
AUTHORS | 1 +
ChangeLog | 217 +++
Makefile.am | 9 +-
README | 6 +
configure.ac | 59 +-
doc/design/ipc-high.txt | 382 ++++
doc/devel/mainpage.dox | 1 +
doc/guide/bind10-guide.xml | 112 +-
examples/host/host.cc | 2 +-
m4macros/Makefile.am | 2 +
m4macros/ax_sqlite3_for_bind10.m4 | 25 +
src/bin/auth/auth_messages.mes | 38 +-
src/bin/auth/auth_srv.cc | 58 +-
src/bin/auth/datasrc_clients_mgr.h | 11 +-
src/bin/auth/query.h | 4 +-
src/bin/auth/tests/auth_srv_unittest.cc | 267 +--
src/bin/auth/tests/config_unittest.cc | 2 +-
.../auth/tests/datasrc_clients_builder_unittest.cc | 55 +-
src/bin/bind10/init.py.in | 7 +-
src/bin/bind10/init_messages.mes | 4 +-
src/bin/bind10/run_bind10.sh.in | 2 +-
src/bin/bind10/tests/Makefile.am | 2 +-
src/bin/bind10/tests/init_test.py.in | 12 +-
src/bin/bindctl/bindcmd.py | 65 +-
src/bin/bindctl/run_bindctl.sh.in | 2 +-
src/bin/bindctl/tests/Makefile.am | 2 +-
src/bin/bindctl/tests/bindctl_test.py | 60 +-
src/bin/cfgmgr/plugins/datasrc.spec.pre.in | 17 +-
src/bin/cfgmgr/plugins/tests/Makefile.am | 5 +-
src/bin/cfgmgr/plugins/tests/datasrc_test.py | 97 +-
src/bin/cfgmgr/plugins/tests/tsig_keys_test.py | 2 +-
src/bin/cfgmgr/tests/Makefile.am | 2 +-
src/bin/cfgmgr/tests/b10-cfgmgr_test.py.in | 2 +-
src/bin/cmdctl/Makefile.am | 6 +-
src/bin/cmdctl/cmdctl.py.in | 99 +-
src/bin/cmdctl/run_b10-cmdctl.sh.in | 2 +-
src/bin/cmdctl/tests/Makefile.am | 4 +-
src/bin/cmdctl/tests/b10-certgen_test.py | 5 +-
src/bin/cmdctl/tests/cmdctl_test.py | 244 ++-
src/bin/dbutil/run_dbutil.sh.in | 2 +-
src/bin/dbutil/tests/Makefile.am | 6 +
src/bin/dbutil/tests/dbutil_test.sh.in | 42 +-
src/bin/ddns/ddns.py.in | 4 +-
src/bin/ddns/ddns_messages.mes | 2 +-
src/bin/ddns/tests/Makefile.am | 2 +-
src/bin/dhcp4/config_parser.cc | 1760 +++----------------
src/bin/dhcp4/config_parser.h | 24 +-
src/bin/dhcp4/ctrl_dhcp4_srv.h | 2 +-
src/bin/dhcp4/dhcp4_messages.mes | 2 +-
src/bin/dhcp4/dhcp4_srv.cc | 34 +-
src/bin/dhcp4/dhcp4_srv.h | 6 +-
src/bin/dhcp4/tests/Makefile.am | 2 +-
src/bin/dhcp4/tests/config_parser_unittest.cc | 22 +-
src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc | 26 +-
src/bin/dhcp4/tests/dhcp4_srv_unittest.cc | 426 ++---
src/bin/dhcp6/config_parser.cc | 1833 +++-----------------
src/bin/dhcp6/config_parser.h | 13 +-
src/bin/dhcp6/ctrl_dhcp6_srv.h | 2 +-
src/bin/dhcp6/dhcp6.spec | 6 +
src/bin/dhcp6/dhcp6_srv.cc | 46 +-
src/bin/dhcp6/dhcp6_srv.h | 4 +-
src/bin/dhcp6/tests/Makefile.am | 2 +-
src/bin/dhcp6/tests/config_parser_unittest.cc | 110 +-
src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc | 18 +-
src/bin/dhcp6/tests/dhcp6_srv_unittest.cc | 393 ++++-
src/bin/loadzone/run_loadzone.sh.in | 2 +-
src/bin/loadzone/tests/Makefile.am | 2 +-
src/bin/loadzone/tests/correct/Makefile.am | 2 +-
src/bin/loadzone/tests/correct/correct_test.sh.in | 2 +-
src/bin/loadzone/tests/correct/ttlext.db | 2 +-
src/bin/msgq/Makefile.am | 1 +
src/bin/msgq/msgq.py.in | 31 +-
src/bin/msgq/run_msgq.sh.in | 2 +-
src/bin/msgq/tests/Makefile.am | 8 +-
src/bin/msgq/tests/msgq_run_test.py | 278 +++
src/bin/msgq/tests/msgq_test.py | 6 +-
src/bin/resolver/Makefile.am | 2 +-
src/bin/resolver/bench/Makefile.am | 25 +
.../logger.cc => bin/resolver/bench/dummy_work.cc} | 13 +-
.../resolver/bench/dummy_work.h} | 27 +-
src/bin/resolver/bench/fake_resolution.cc | 172 ++
src/bin/resolver/bench/fake_resolution.h | 228 +++
.../resolver/bench/main.cc} | 25 +-
src/bin/resolver/bench/naive_resolver.cc | 66 +
.../resolver/bench/naive_resolver.h} | 50 +-
src/bin/sockcreator/README | 2 +-
src/bin/sockcreator/sockcreator.h | 2 +-
src/bin/stats/stats.py.in | 2 +-
src/bin/stats/stats_httpd.py.in | 11 +-
src/bin/stats/stats_httpd_messages.mes | 4 +-
src/bin/stats/tests/Makefile.am | 4 +-
...b10-stats-httpd_test.py => stats-httpd_test.py} | 229 ++-
.../tests/{b10-stats_test.py => stats_test.py} | 350 ++--
src/bin/stats/tests/test_utils.py | 365 ++--
src/bin/tests/Makefile.am | 2 +-
src/bin/usermgr/b10-cmdctl-usermgr.py.in | 2 +-
src/bin/xfrin/b10-xfrin.xml | 126 ++
src/bin/xfrin/tests/Makefile.am | 2 +-
src/bin/xfrin/tests/xfrin_test.py | 216 ++-
src/bin/xfrin/xfrin.py.in | 64 +-
src/bin/xfrin/xfrin.spec | 113 ++
src/bin/xfrin/xfrin_messages.mes | 3 +
src/bin/xfrout/tests/Makefile.am | 2 +-
src/bin/xfrout/xfrout.py.in | 2 +-
src/bin/xfrout/xfrout_messages.mes | 2 +-
src/bin/zonemgr/tests/Makefile.am | 2 +-
src/bin/zonemgr/tests/zonemgr_test.py | 71 +-
src/bin/zonemgr/zonemgr.py.in | 67 +-
src/bin/zonemgr/zonemgr_messages.mes | 17 +-
src/lib/acl/loader.h | 4 +-
src/lib/asiodns/asiodns_messages.mes | 89 +
src/lib/asiodns/dns_answer.h | 2 +-
src/lib/asiodns/dns_server.h | 2 +-
src/lib/asiodns/dns_service.cc | 22 +-
src/lib/asiodns/sync_udp_server.cc | 100 +-
src/lib/asiodns/sync_udp_server.h | 70 +-
src/lib/asiodns/tcp_server.cc | 133 +-
src/lib/asiodns/tests/dns_server_unittest.cc | 106 +-
src/lib/asiodns/udp_server.cc | 30 +-
src/lib/asiolink/io_service.cc | 28 +-
src/lib/asiolink/io_service.h | 15 +-
src/lib/asiolink/tcp_endpoint.h | 2 +-
src/lib/asiolink/tests/Makefile.am | 1 +
.../tests/io_service_unittest.cc} | 52 +-
src/lib/asiolink/tests/tcp_socket_unittest.cc | 2 +-
src/lib/asiolink/tests/udp_socket_unittest.cc | 2 +-
src/lib/asiolink/udp_endpoint.h | 2 +-
src/lib/bench/benchmark.h | 4 +-
.../tests/testdata/message_nxdomain_large_ttl.wire | 2 +-
src/lib/cc/cc_messages.mes | 2 +-
src/lib/cc/data.h | 2 +-
src/lib/cc/proto_defs.cc | 12 +-
src/lib/cc/session.cc | 50 +-
src/lib/cc/tests/session_unittests.cc | 2 +-
src/lib/config/ccsession.cc | 71 +-
src/lib/config/ccsession.h | 111 +-
src/lib/config/config_messages.mes | 4 +
src/lib/config/tests/ccsession_unittests.cc | 54 +-
src/lib/config/tests/fake_session.cc | 9 +-
src/lib/config/tests/fake_session.h | 3 +-
src/lib/cryptolink/cryptolink.h | 2 +-
src/lib/datasrc/.gitignore | 2 +
src/lib/datasrc/Makefile.am | 22 +-
src/lib/datasrc/cache_config.cc | 198 +++
src/lib/datasrc/cache_config.h | 219 +++
src/lib/datasrc/client.h | 10 +-
src/lib/datasrc/client_list.cc | 371 ++--
src/lib/datasrc/client_list.h | 182 +-
src/lib/datasrc/data_source.h | 68 -
src/lib/datasrc/database.cc | 26 +-
src/lib/datasrc/database.h | 51 +-
src/lib/datasrc/datasrc_messages.mes | 196 +--
src/lib/datasrc/exceptions.h | 36 +
src/lib/datasrc/factory.cc | 2 +-
src/lib/datasrc/factory.h | 2 +-
src/lib/datasrc/memory/Makefile.am | 8 +-
src/lib/datasrc/memory/domaintree.h | 2 +-
src/lib/datasrc/memory/memory_client.cc | 124 +-
src/lib/datasrc/memory/memory_client.h | 79 +-
src/lib/datasrc/memory/memory_messages.mes | 152 +-
src/lib/datasrc/memory/rdata_serialization.h | 2 +-
src/lib/datasrc/memory/rdataset.cc | 10 +-
src/lib/datasrc/memory/segment_object_holder.h | 24 +-
src/lib/datasrc/memory/zone_data.cc | 8 +-
src/lib/datasrc/memory/zone_data_loader.cc | 9 +-
src/lib/datasrc/memory/zone_data_updater.cc | 2 +-
src/lib/datasrc/memory/zone_data_updater.h | 2 +-
src/lib/datasrc/memory/zone_finder.cc | 2 +-
src/lib/datasrc/memory/zone_table.cc | 23 +-
src/lib/datasrc/memory/zone_table.h | 7 +
src/lib/datasrc/memory/zone_table_segment.cc | 28 +-
src/lib/datasrc/memory/zone_table_segment.h | 298 +++-
src/lib/datasrc/memory/zone_table_segment_local.cc | 37 +-
src/lib/datasrc/memory/zone_table_segment_local.h | 69 +-
.../datasrc/memory/zone_table_segment_mapped.cc | 386 +++++
src/lib/datasrc/memory/zone_table_segment_mapped.h | 150 ++
.../{zone_writer_local.cc => zone_writer.cc} | 42 +-
src/lib/datasrc/memory/zone_writer.h | 61 +-
src/lib/datasrc/memory/zone_writer_local.h | 95 -
src/lib/datasrc/sqlite3_accessor.cc | 23 +-
src/lib/datasrc/sqlite3_accessor.h | 6 +-
src/lib/datasrc/sqlite3_accessor_link.cc | 5 +
src/lib/datasrc/sqlite3_datasrc_messages.mes | 142 ++
src/lib/datasrc/static_datasrc.h | 50 -
src/lib/datasrc/static_datasrc_link.cc | 68 -
src/lib/datasrc/tests/Makefile.am | 7 +-
src/lib/datasrc/tests/cache_config_unittest.cc | 312 ++++
src/lib/datasrc/tests/client_list_unittest.cc | 614 ++++---
src/lib/datasrc/tests/database_unittest.cc | 18 +-
src/lib/datasrc/tests/factory_unittest.cc | 54 +-
src/lib/datasrc/tests/memory/Makefile.am | 11 +-
.../datasrc/tests/memory/memory_client_unittest.cc | 290 ++--
...memory_segment_test.h => memory_segment_mock.h} | 4 +-
src/lib/datasrc/tests/memory/rdataset_unittest.cc | 58 +-
.../tests/memory/rrset_collection_unittest.cc | 24 +-
.../tests/memory/segment_object_holder_unittest.cc | 21 +-
.../tests/memory/zone_data_loader_unittest.cc | 8 +-
src/lib/datasrc/tests/memory/zone_data_unittest.cc | 7 +-
.../tests/memory/zone_data_updater_unittest.cc | 7 +-
.../datasrc/tests/memory/zone_finder_unittest.cc | 29 +-
src/lib/datasrc/tests/memory/zone_loader_util.cc | 88 +
src/lib/datasrc/tests/memory/zone_loader_util.h | 57 +
.../memory/zone_table_segment_mapped_unittest.cc | 590 +++++++
...le_segment_test.h => zone_table_segment_mock.h} | 79 +-
.../tests/memory/zone_table_segment_unittest.cc | 61 +-
.../datasrc/tests/memory/zone_table_unittest.cc | 45 +-
.../datasrc/tests/memory/zone_writer_unittest.cc | 108 +-
src/lib/datasrc/tests/mock_client.cc | 197 +++
src/lib/datasrc/tests/mock_client.h | 83 +
src/lib/datasrc/tests/sqlite3_accessor_unittest.cc | 16 +-
.../datasrc/tests/zone_finder_context_unittest.cc | 26 +-
src/lib/datasrc/tests/zone_loader_unittest.cc | 64 +-
.../datasrc/tests/zone_table_accessor_unittest.cc | 112 ++
src/lib/datasrc/zone_finder.cc | 3 +-
src/lib/datasrc/zone_loader.cc | 2 +-
src/lib/datasrc/zone_loader.h | 2 +-
src/lib/datasrc/zone_table_accessor.h | 212 +++
src/lib/datasrc/zone_table_accessor_cache.cc | 60 +
src/lib/datasrc/zone_table_accessor_cache.h | 76 +
src/lib/dhcp/Makefile.am | 3 +
src/lib/dhcp/dhcp6.h | 2 +-
src/lib/dhcp/duid.cc | 17 +-
src/lib/dhcp/duid.h | 12 +
src/lib/dhcp/hwaddr.cc | 11 +-
src/lib/dhcp/hwaddr.h | 2 +
src/lib/dhcp/iface_mgr.cc | 287 ++-
src/lib/dhcp/iface_mgr.h | 504 +++---
src/lib/dhcp/iface_mgr_bsd.cc | 7 +-
src/lib/dhcp/iface_mgr_linux.cc | 70 +-
src/lib/dhcp/iface_mgr_sun.cc | 7 +-
src/lib/dhcp/libdhcp++.cc | 19 +-
src/lib/dhcp/libdhcp++.dox | 47 +
src/lib/dhcp/libdhcp++.h | 21 +-
src/lib/dhcp/option_custom.cc | 11 +-
src/lib/dhcp/option_custom.h | 2 +-
src/lib/dhcp/option_definition.h | 4 +-
src/lib/dhcp/pkt4.h | 2 +-
src/lib/dhcp/pkt6.cc | 319 +++-
src/lib/dhcp/pkt6.h | 155 +-
src/lib/dhcp/pkt_filter.h | 84 +
src/lib/dhcp/pkt_filter_inet.cc | 264 +++
src/lib/dhcp/pkt_filter_inet.h | 76 +
src/lib/dhcp/pkt_filter_lpf.cc | 45 +
src/lib/dhcp/pkt_filter_lpf.h | 72 +
src/lib/dhcp/tests/duid_unittest.cc | 79 +-
src/lib/dhcp/tests/hwaddr_unittest.cc | 11 +-
src/lib/dhcp/tests/iface_mgr_unittest.cc | 327 ++--
src/lib/dhcp/tests/libdhcp++_unittest.cc | 13 +-
src/lib/dhcp/tests/option4_addrlst_unittest.cc | 90 +-
src/lib/dhcp/tests/option6_addrlst_unittest.cc | 65 +-
src/lib/dhcp/tests/option6_ia_unittest.cc | 61 +-
src/lib/dhcp/tests/option6_iaaddr_unittest.cc | 23 +-
src/lib/dhcp/tests/option_custom_unittest.cc | 18 +-
src/lib/dhcp/tests/option_int_unittest.cc | 12 +-
src/lib/dhcp/tests/option_unittest.cc | 264 ++-
src/lib/dhcp/tests/pkt4_unittest.cc | 164 +-
src/lib/dhcp/tests/pkt6_unittest.cc | 433 ++++-
src/lib/dhcpsrv/Makefile.am | 11 +-
src/lib/dhcpsrv/alloc_engine.cc | 18 +-
src/lib/dhcpsrv/cfgmgr.cc | 30 +-
src/lib/dhcpsrv/cfgmgr.h | 1 -
src/lib/dhcpsrv/dhcp_config_parser.h | 40 +-
src/lib/dhcpsrv/dhcp_parsers.cc | 957 ++++++++++
src/lib/dhcpsrv/dhcp_parsers.h | 765 ++++++++
src/lib/dhcpsrv/dhcpsrv_messages.mes | 9 +-
src/lib/dhcpsrv/lease_mgr.h | 8 +-
src/lib/dhcpsrv/mysql_lease_mgr.cc | 136 +-
src/lib/dhcpsrv/mysql_lease_mgr.h | 55 +-
src/lib/dhcpsrv/pool.cc | 11 +-
src/lib/dhcpsrv/pool.h | 5 +-
src/lib/dhcpsrv/subnet.cc | 65 +-
src/lib/dhcpsrv/subnet.h | 37 +-
src/lib/dhcpsrv/tests/Makefile.am | 1 +
src/lib/dhcpsrv/tests/alloc_engine_unittest.cc | 145 +-
src/lib/dhcpsrv/tests/cfgmgr_unittest.cc | 222 +++
src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc | 519 ++++++
src/lib/dhcpsrv/tests/lease_mgr_unittest.cc | 27 +-
src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc | 191 +-
src/lib/dhcpsrv/tests/run_unittests.cc | 1 +
src/lib/dhcpsrv/tests/schema_copy.h | 6 +
src/lib/dhcpsrv/tests/subnet_unittest.cc | 16 +
src/lib/dhcpsrv/tests/test_utils.cc | 18 +-
src/lib/dns/gen-rdatacode.py.in | 12 +-
src/lib/dns/master_loader.cc | 2 +-
src/lib/dns/nsec3hash.cc | 2 +-
src/lib/dns/python/pydnspp_common.h | 2 +-
src/lib/dns/python/rrset_python.cc | 10 +-
src/lib/dns/python/tests/Makefile.am | 2 +-
src/lib/dns/python/tests/nsec3hash_python_test.py | 2 +-
src/lib/dns/python/tests/rrset_python_test.py | 7 +-
src/lib/dns/rdata/generic/afsdb_18.cc | 67 +-
src/lib/dns/rdata/generic/afsdb_18.h | 4 +-
.../dns/rdata/generic/detail/nsec3param_common.cc | 36 +-
.../dns/rdata/generic/detail/nsec3param_common.h | 25 +-
src/lib/dns/rdata/generic/detail/nsec_bitmap.cc | 66 +-
src/lib/dns/rdata/generic/detail/nsec_bitmap.h | 32 +-
src/lib/dns/rdata/generic/dnskey_48.cc | 187 +-
src/lib/dns/rdata/generic/dnskey_48.h | 9 +
src/lib/dns/rdata/generic/mx_15.cc | 17 +-
src/lib/dns/rdata/generic/mx_15.h | 3 +
src/lib/dns/rdata/generic/nsec3_50.cc | 104 +-
src/lib/dns/rdata/generic/nsec3_50.h | 3 +
src/lib/dns/rdata/generic/nsec3param_51.cc | 81 +-
src/lib/dns/rdata/generic/nsec3param_51.h | 4 +
src/lib/dns/rdata/generic/nsec_47.cc | 6 +-
src/lib/dns/rdata/generic/opt_41.cc | 19 +-
src/lib/dns/rdata/generic/rrsig_46.cc | 142 +-
src/lib/dns/rdata/generic/rrsig_46.h | 12 +-
src/lib/dns/rdata/in_1/dhcid_49.cc | 90 +-
src/lib/dns/rdata/in_1/dhcid_49.h | 5 +-
src/lib/dns/tests/labelsequence_unittest.cc | 2 +-
src/lib/dns/tests/master_loader_unittest.cc | 16 +-
src/lib/dns/tests/masterload_unittest.cc | 24 +-
src/lib/dns/tests/message_unittest.cc | 4 +-
src/lib/dns/tests/rdata_afsdb_unittest.cc | 37 +-
src/lib/dns/tests/rdata_dhcid_unittest.cc | 93 +-
src/lib/dns/tests/rdata_dnskey_unittest.cc | 166 +-
src/lib/dns/tests/rdata_ds_like_unittest.cc | 53 +-
src/lib/dns/tests/rdata_nsec3_unittest.cc | 140 +-
src/lib/dns/tests/rdata_nsec3param_unittest.cc | 129 +-
src/lib/dns/tests/rdata_opt_unittest.cc | 2 +-
src/lib/dns/tests/rdata_rrsig_unittest.cc | 233 ++-
src/lib/dns/tests/rrcollator_unittest.cc | 6 +-
src/lib/dns/tests/rrset_unittest.cc | 4 +-
src/lib/dns/tests/testdata/.gitignore | 2 +
src/lib/dns/tests/testdata/Makefile.am | 4 +-
src/lib/dns/tests/testdata/example.org | 2 +-
.../rdata_dnskey_empty_keydata_fromWire.spec | 7 +
src/lib/dns/tests/testdata/rdata_dnskey_fromWire | 24 -
.../dns/tests/testdata/rdata_dnskey_fromWire.spec | 7 +
src/lib/dns/tsigrecord.cc | 7 +-
src/lib/log/Makefile.am | 4 +-
src/lib/log/log_dbglevels.h | 2 +-
src/lib/log/logger_impl.cc | 6 +-
src/lib/log/logger_manager.cc | 22 +-
src/lib/log/logger_manager.h | 9 +
src/lib/log/logger_manager_impl.h | 2 +-
src/lib/log/logger_unittest_support.cc | 2 +-
src/lib/log/message_initializer.cc | 25 +-
src/lib/log/message_initializer.h | 16 +-
src/lib/log/tests/Makefile.am | 21 +-
src/lib/log/tests/logger_lock_test.cc | 11 +
src/lib/log/tests/logger_lock_test.sh.in | 1 +
src/lib/log/tests/message_dictionary_unittest.cc | 5 +-
.../log/tests/message_initializer_1_unittest.cc | 34 +
src/lib/nsas/nameserver_entry.cc | 4 +-
src/lib/nsas/tests/nameserver_entry_unittest.cc | 6 +-
src/lib/nsas/tests/zone_entry_unittest.cc | 4 +-
src/lib/nsas/zone_entry.cc | 8 +-
src/lib/nsas/zone_entry.h | 4 +-
src/lib/python/bind10_config.py.in | 2 +-
src/lib/python/isc/acl/tests/Makefile.am | 2 +-
src/lib/python/isc/bind10/component.py | 2 +-
src/lib/python/isc/bind10/sockcreator.py | 2 +-
src/lib/python/isc/bind10/tests/Makefile.am | 2 +-
.../python/isc/bind10/tests/sockcreator_test.py | 2 +-
.../python/isc/bind10/tests/socket_cache_test.py | 4 +-
src/lib/python/isc/cc/Makefile.am | 1 +
src/lib/python/isc/cc/cc_generated/Makefile.am | 1 +
src/lib/python/isc/cc/data.py | 4 +-
src/lib/python/isc/cc/session.py | 47 +-
src/lib/python/isc/cc/tests/Makefile.am | 2 +-
src/lib/python/isc/config/ccsession.py | 37 +-
src/lib/python/isc/config/cfgmgr.py | 5 +-
src/lib/python/isc/config/tests/Makefile.am | 2 +-
src/lib/python/isc/config/tests/cfgmgr_test.py | 6 +-
src/lib/python/isc/datasrc/client_inc.cc | 2 +-
src/lib/python/isc/datasrc/client_python.cc | 4 +-
src/lib/python/isc/datasrc/datasrc.cc | 4 +
src/lib/python/isc/datasrc/finder_python.cc | 2 +-
src/lib/python/isc/datasrc/tests/Makefile.am | 2 +-
src/lib/python/isc/datasrc/tests/datasrc_test.py | 25 +-
.../python/isc/datasrc/tests/zone_loader_test.py | 26 +-
src/lib/python/isc/datasrc/updater_python.cc | 2 +-
src/lib/python/isc/ddns/session.py | 2 +-
src/lib/python/isc/ddns/tests/Makefile.am | 2 +-
src/lib/python/isc/ddns/tests/session_tests.py | 2 +-
src/lib/python/isc/log/tests/Makefile.am | 2 +-
src/lib/python/isc/net/tests/Makefile.am | 2 +-
src/lib/python/isc/notify/notify_out.py | 99 +-
src/lib/python/isc/notify/notify_out_messages.mes | 27 +-
src/lib/python/isc/notify/tests/Makefile.am | 2 +-
src/lib/python/isc/notify/tests/notify_out_test.py | 137 +-
src/lib/python/isc/server_common/dns_tcp.py | 6 +-
src/lib/python/isc/server_common/tests/Makefile.am | 2 +-
src/lib/python/isc/statistics/counters.py | 8 +-
src/lib/python/isc/statistics/tests/Makefile.am | 2 +-
.../python/isc/statistics/tests/counters_test.py | 4 +-
.../isc/statistics/tests/testdata/test_spec3.spec | 16 +-
src/lib/python/isc/sysinfo/sysinfo.py | 2 +-
src/lib/python/isc/sysinfo/tests/Makefile.am | 2 +-
src/lib/python/isc/util/Makefile.am | 3 +-
src/lib/python/isc/util/address_formatter.py | 82 +
src/lib/python/isc/util/cio/tests/Makefile.am | 2 +-
src/lib/python/isc/util/socketserver_mixin.py | 2 +-
src/lib/python/isc/util/tests/Makefile.am | 5 +-
.../isc/util/tests/address_formatter_test.py | 68 +
src/lib/python/isc/xfrin/diff.py | 2 +-
src/lib/python/isc/xfrin/tests/Makefile.am | 2 +-
src/lib/resolve/recursive_query.cc | 2 +-
src/lib/resolve/tests/recursive_query_unittest.cc | 2 +-
src/lib/server_common/client.cc | 10 +-
src/lib/server_common/client.h | 2 +-
src/lib/server_common/portconfig.cc | 10 +-
src/lib/server_common/server_common_messages.mes | 2 +-
src/lib/server_common/tests/client_unittest.cc | 4 +-
.../server_common/tests/socket_requestor_test.cc | 2 +-
src/lib/testutils/mockups.h | 17 +-
src/lib/util/encode/base_n.cc | 23 +-
src/lib/util/filename.h | 4 +-
src/lib/util/memory_segment.h | 20 +-
src/lib/util/memory_segment_local.cc | 13 +-
src/lib/util/memory_segment_local.h | 4 +-
src/lib/util/memory_segment_mapped.cc | 75 +-
src/lib/util/memory_segment_mapped.h | 13 +-
src/lib/util/python/gen_wiredata.py.in | 47 +-
src/lib/util/random/random_number_generator.h | 10 +-
src/lib/util/tests/fd_share_tests.cc | 2 +-
src/lib/util/tests/io_utilities_unittest.cc | 2 +-
.../util/tests/memory_segment_common_unittest.cc | 24 +-
.../util/tests/memory_segment_mapped_unittest.cc | 66 +-
.../util/tests/random_number_generator_unittest.cc | 34 +-
src/lib/util/threads/Makefile.am | 3 +-
src/lib/util/threads/sync.cc | 20 +
src/lib/util/threads/sync.h | 45 +-
src/lib/util/threads/tests/lock_unittest.cc | 38 +
src/lib/util/unittests/resolver.h | 2 +-
src/lib/xfr/tests/client_test.cc | 2 +-
tests/Makefile.am | 2 +-
tests/lettuce/README | 5 +
.../{no_db_file.config => glue.config} | 12 +-
tests/lettuce/configurations/xfrin/.gitignore | 2 +
.../xfrin/retransfer_master.conf.orig | 3 -
...ster.conf.orig => retransfer_master_diffs.conf} | 7 +-
...er.conf.orig => retransfer_master_v4.conf.orig} | 9 +-
...ave_notify.conf => retransfer_slave_diffs.conf} | 25 +-
...tify.conf => retransfer_slave_notify.conf.orig} | 1 +
...notify.conf => retransfer_slave_notify_v4.conf} | 7 +-
tests/lettuce/data/.gitignore | 1 +
.../lettuce/data/glue.sqlite3 | Bin 13312 -> 17408 bytes
.../lettuce/data/xfrin-before-diffs.sqlite3.orig | Bin 15360 -> 15360 bytes
tests/lettuce/data/xfrin-diffs.sqlite3 | Bin 0 -> 407552 bytes
tests/lettuce/features/auth_badzone.feature | 6 +-
tests/lettuce/features/bindctl_commands.feature | 15 +
tests/lettuce/features/example.feature | 8 +-
tests/lettuce/features/nsec3_auth.feature | 2 +-
tests/lettuce/features/queries.feature | 72 +
tests/lettuce/features/terrain/bind10_control.py | 138 +-
tests/lettuce/features/terrain/steps.py | 12 +-
tests/lettuce/features/terrain/terrain.py | 83 +-
tests/lettuce/features/xfrin_bind10.feature | 57 +-
.../lettuce/features/xfrin_notify_handling.feature | 581 +++++--
tests/lettuce/setup_intree_bind10.sh.in | 2 +-
tests/system/.gitignore | 2 -
tests/system/Makefile.am | 16 -
tests/system/README | 64 -
tests/system/bindctl/clean.sh | 20 -
tests/system/bindctl/nsx1/.gitignore | 3 -
.../system/bindctl/nsx1/b10-config.db.template.in | 29 -
tests/system/bindctl/nsx1/example-normalized.db | 3 -
tests/system/bindctl/nsx1/root.db | 25 -
tests/system/bindctl/setup.sh | 26 -
tests/system/bindctl/tests.sh | 238 ---
tests/system/cleanall.sh | 36 -
tests/system/common/default_user.csv | 1 -
tests/system/common/rndc.conf | 25 -
tests/system/common/rndc.key | 22 -
tests/system/conf.sh.in | 73 -
tests/system/glue/.gitignore | 1 -
tests/system/glue/auth.good | 15 -
tests/system/glue/clean.sh | 23 -
tests/system/glue/example.good | 22 -
tests/system/glue/noglue.good | 14 -
tests/system/glue/nsx1/.gitignore | 3 -
tests/system/glue/nsx1/b10-config.db.in | 36 -
tests/system/glue/nsx1/com.db | 31 -
tests/system/glue/nsx1/net.db | 32 -
tests/system/glue/nsx1/root-servers.nil.db | 26 -
tests/system/glue/nsx1/root.db | 53 -
tests/system/glue/setup.sh.in | 25 -
tests/system/glue/test.good | 19 -
tests/system/glue/tests.sh | 68 -
tests/system/ifconfig.sh | 226 ---
tests/system/ixfr/.gitignore | 8 -
tests/system/ixfr/README | 86 -
tests/system/ixfr/b10-config.db.in | 51 -
tests/system/ixfr/clean_ns.sh | 28 -
tests/system/ixfr/common_tests.sh.in | 78 -
tests/system/ixfr/db.example.common | 1556 -----------------
tests/system/ixfr/db.example.n0.in | 29 -
tests/system/ixfr/db.example.n2.in | 28 -
tests/system/ixfr/db.example.n2.refresh.in | 28 -
tests/system/ixfr/db.example.n4.in | 31 -
tests/system/ixfr/db.example.n6.in | 29 -
tests/system/ixfr/in-1/.gitignore | 1 -
tests/system/ixfr/in-1/clean.sh | 1 -
tests/system/ixfr/in-1/ns1/README | 3 -
tests/system/ixfr/in-1/nsx2/README | 3 -
tests/system/ixfr/in-1/setup.sh.in | 30 -
tests/system/ixfr/in-1/tests.sh | 37 -
tests/system/ixfr/in-2/.gitignore | 1 -
tests/system/ixfr/in-2/clean.sh | 1 -
tests/system/ixfr/in-2/ns1/.gitignore | 1 -
tests/system/ixfr/in-2/ns1/README | 3 -
tests/system/ixfr/in-2/nsx2/.gitignore | 1 -
tests/system/ixfr/in-2/nsx2/README | 3 -
tests/system/ixfr/in-2/setup.sh.in | 29 -
tests/system/ixfr/in-2/tests.sh | 81 -
tests/system/ixfr/in-3/.gitignore | 1 -
tests/system/ixfr/in-3/clean.sh | 1 -
tests/system/ixfr/in-3/ns1/README | 3 -
tests/system/ixfr/in-3/nsx2/README | 3 -
tests/system/ixfr/in-3/setup.sh.in | 29 -
tests/system/ixfr/in-3/tests.sh | 66 -
tests/system/ixfr/in-4/.gitignore | 1 -
tests/system/ixfr/in-4/clean.sh | 1 -
tests/system/ixfr/in-4/ns1/README | 3 -
tests/system/ixfr/in-4/nsx2/README | 3 -
tests/system/ixfr/in-4/setup.sh.in | 30 -
tests/system/ixfr/in-4/tests.sh | 53 -
tests/system/ixfr/ixfr_init.sh.in | 330 ----
tests/system/ixfr/named_noixfr.conf | 42 -
tests/system/ixfr/named_nonotify.conf | 40 -
tests/system/ixfr/named_notify.conf | 41 -
tests/system/run.sh.in | 125 --
tests/system/runall.sh | 44 -
tests/system/start.pl | 229 ---
tests/system/stop.pl | 188 --
tests/tools/badpacket/option_info.h | 2 +-
tests/tools/dhcp-ubench/dhcp-perf-guide.xml | 6 +-
tests/tools/perfdhcp/command_options.cc | 4 +-
tests/tools/perfdhcp/pkt_transform.h | 2 +-
tests/tools/perfdhcp/stats_mgr.h | 21 +-
tests/tools/perfdhcp/test_control.cc | 36 +-
tests/tools/perfdhcp/test_control.h | 14 +-
.../perfdhcp/tests/command_options_unittest.cc | 2 +-
.../tools/perfdhcp/tests/test_control_unittest.cc | 12 +
tools/system_messages.py | 2 +-
538 files changed, 19121 insertions(+), 13671 deletions(-)
create mode 100644 doc/design/ipc-high.txt
create mode 100644 m4macros/Makefile.am
create mode 100644 m4macros/ax_sqlite3_for_bind10.m4
create mode 100644 src/bin/msgq/tests/msgq_run_test.py
create mode 100644 src/bin/resolver/bench/Makefile.am
copy src/{lib/datasrc/memory/logger.cc => bin/resolver/bench/dummy_work.cc} (78%)
copy src/{lib/util/tests/interprocess_util.h => bin/resolver/bench/dummy_work.h} (64%)
create mode 100644 src/bin/resolver/bench/fake_resolution.cc
create mode 100644 src/bin/resolver/bench/fake_resolution.h
copy src/{lib/datasrc/memory/segment_object_holder.cc => bin/resolver/bench/main.cc} (67%)
create mode 100644 src/bin/resolver/bench/naive_resolver.cc
copy src/{lib/util/tests/interprocess_util.cc => bin/resolver/bench/naive_resolver.h} (57%)
rename src/bin/stats/tests/{b10-stats-httpd_test.py => stats-httpd_test.py} (87%)
rename src/bin/stats/tests/{b10-stats_test.py => stats_test.py} (87%)
copy src/lib/{util/tests/interprocess_util.cc => asiolink/tests/io_service_unittest.cc} (51%)
create mode 100644 src/lib/datasrc/cache_config.cc
create mode 100644 src/lib/datasrc/cache_config.h
delete mode 100644 src/lib/datasrc/data_source.h
create mode 100644 src/lib/datasrc/memory/zone_table_segment_mapped.cc
create mode 100644 src/lib/datasrc/memory/zone_table_segment_mapped.h
rename src/lib/datasrc/memory/{zone_writer_local.cc => zone_writer.cc} (71%)
delete mode 100644 src/lib/datasrc/memory/zone_writer_local.h
create mode 100644 src/lib/datasrc/sqlite3_datasrc_messages.mes
delete mode 100644 src/lib/datasrc/static_datasrc.h
delete mode 100644 src/lib/datasrc/static_datasrc_link.cc
create mode 100644 src/lib/datasrc/tests/cache_config_unittest.cc
rename src/lib/datasrc/tests/memory/{memory_segment_test.h => memory_segment_mock.h} (95%)
create mode 100644 src/lib/datasrc/tests/memory/zone_loader_util.cc
create mode 100644 src/lib/datasrc/tests/memory/zone_loader_util.h
create mode 100644 src/lib/datasrc/tests/memory/zone_table_segment_mapped_unittest.cc
rename src/lib/datasrc/tests/memory/{zone_table_segment_test.h => zone_table_segment_mock.h} (51%)
create mode 100644 src/lib/datasrc/tests/mock_client.cc
create mode 100644 src/lib/datasrc/tests/mock_client.h
create mode 100644 src/lib/datasrc/tests/zone_table_accessor_unittest.cc
create mode 100644 src/lib/datasrc/zone_table_accessor.h
create mode 100644 src/lib/datasrc/zone_table_accessor_cache.cc
create mode 100644 src/lib/datasrc/zone_table_accessor_cache.h
create mode 100644 src/lib/dhcp/pkt_filter.h
create mode 100644 src/lib/dhcp/pkt_filter_inet.cc
create mode 100644 src/lib/dhcp/pkt_filter_inet.h
create mode 100644 src/lib/dhcp/pkt_filter_lpf.cc
create mode 100644 src/lib/dhcp/pkt_filter_lpf.h
create mode 100644 src/lib/dhcpsrv/dhcp_parsers.cc
create mode 100644 src/lib/dhcpsrv/dhcp_parsers.h
create mode 100644 src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc
create mode 100644 src/lib/dns/tests/testdata/rdata_dnskey_empty_keydata_fromWire.spec
delete mode 100644 src/lib/dns/tests/testdata/rdata_dnskey_fromWire
create mode 100644 src/lib/dns/tests/testdata/rdata_dnskey_fromWire.spec
create mode 100644 src/lib/python/isc/util/address_formatter.py
create mode 100644 src/lib/python/isc/util/tests/address_formatter_test.py
copy tests/lettuce/configurations/{no_db_file.config => glue.config} (74%)
copy tests/lettuce/configurations/xfrin/{retransfer_master.conf.orig => retransfer_master_diffs.conf} (81%)
copy tests/lettuce/configurations/xfrin/{retransfer_master.conf.orig => retransfer_master_v4.conf.orig} (89%)
copy tests/lettuce/configurations/xfrin/{retransfer_slave_notify.conf => retransfer_slave_diffs.conf} (73%)
copy tests/lettuce/configurations/xfrin/{retransfer_slave_notify.conf => retransfer_slave_notify.conf.orig} (94%)
rename tests/lettuce/configurations/xfrin/{retransfer_slave_notify.conf => retransfer_slave_notify_v4.conf} (85%)
copy src/bin/dbutil/tests/testdata/too_many_version.sqlite3 => tests/lettuce/data/glue.sqlite3 (56%)
copy src/bin/dbutil/tests/testdata/v2_1.sqlite3 => tests/lettuce/data/xfrin-before-diffs.sqlite3.orig (72%)
create mode 100644 tests/lettuce/data/xfrin-diffs.sqlite3
delete mode 100644 tests/system/.gitignore
delete mode 100644 tests/system/Makefile.am
delete mode 100644 tests/system/README
delete mode 100755 tests/system/bindctl/clean.sh
delete mode 100644 tests/system/bindctl/nsx1/.gitignore
delete mode 100644 tests/system/bindctl/nsx1/b10-config.db.template.in
delete mode 100644 tests/system/bindctl/nsx1/example-normalized.db
delete mode 100644 tests/system/bindctl/nsx1/root.db
delete mode 100755 tests/system/bindctl/setup.sh
delete mode 100755 tests/system/bindctl/tests.sh
delete mode 100755 tests/system/cleanall.sh
delete mode 100644 tests/system/common/default_user.csv
delete mode 100644 tests/system/common/rndc.conf
delete mode 100644 tests/system/common/rndc.key
delete mode 100755 tests/system/conf.sh.in
delete mode 100644 tests/system/glue/.gitignore
delete mode 100644 tests/system/glue/auth.good
delete mode 100755 tests/system/glue/clean.sh
delete mode 100644 tests/system/glue/example.good
delete mode 100644 tests/system/glue/noglue.good
delete mode 100644 tests/system/glue/nsx1/.gitignore
delete mode 100644 tests/system/glue/nsx1/b10-config.db.in
delete mode 100644 tests/system/glue/nsx1/com.db
delete mode 100644 tests/system/glue/nsx1/net.db
delete mode 100644 tests/system/glue/nsx1/root-servers.nil.db
delete mode 100644 tests/system/glue/nsx1/root.db
delete mode 100755 tests/system/glue/setup.sh.in
delete mode 100644 tests/system/glue/test.good
delete mode 100755 tests/system/glue/tests.sh
delete mode 100755 tests/system/ifconfig.sh
delete mode 100644 tests/system/ixfr/.gitignore
delete mode 100644 tests/system/ixfr/README
delete mode 100644 tests/system/ixfr/b10-config.db.in
delete mode 100644 tests/system/ixfr/clean_ns.sh
delete mode 100644 tests/system/ixfr/common_tests.sh.in
delete mode 100644 tests/system/ixfr/db.example.common
delete mode 100644 tests/system/ixfr/db.example.n0.in
delete mode 100644 tests/system/ixfr/db.example.n2.in
delete mode 100644 tests/system/ixfr/db.example.n2.refresh.in
delete mode 100644 tests/system/ixfr/db.example.n4.in
delete mode 100644 tests/system/ixfr/db.example.n6.in
delete mode 100644 tests/system/ixfr/in-1/.gitignore
delete mode 120000 tests/system/ixfr/in-1/clean.sh
delete mode 100644 tests/system/ixfr/in-1/ns1/README
delete mode 100644 tests/system/ixfr/in-1/nsx2/README
delete mode 100644 tests/system/ixfr/in-1/setup.sh.in
delete mode 100644 tests/system/ixfr/in-1/tests.sh
delete mode 100644 tests/system/ixfr/in-2/.gitignore
delete mode 120000 tests/system/ixfr/in-2/clean.sh
delete mode 100644 tests/system/ixfr/in-2/ns1/.gitignore
delete mode 100644 tests/system/ixfr/in-2/ns1/README
delete mode 100644 tests/system/ixfr/in-2/nsx2/.gitignore
delete mode 100644 tests/system/ixfr/in-2/nsx2/README
delete mode 100644 tests/system/ixfr/in-2/setup.sh.in
delete mode 100644 tests/system/ixfr/in-2/tests.sh
delete mode 100644 tests/system/ixfr/in-3/.gitignore
delete mode 120000 tests/system/ixfr/in-3/clean.sh
delete mode 100644 tests/system/ixfr/in-3/ns1/README
delete mode 100644 tests/system/ixfr/in-3/nsx2/README
delete mode 100644 tests/system/ixfr/in-3/setup.sh.in
delete mode 100644 tests/system/ixfr/in-3/tests.sh
delete mode 100644 tests/system/ixfr/in-4/.gitignore
delete mode 120000 tests/system/ixfr/in-4/clean.sh
delete mode 100644 tests/system/ixfr/in-4/ns1/README
delete mode 100644 tests/system/ixfr/in-4/nsx2/README
delete mode 100644 tests/system/ixfr/in-4/setup.sh.in
delete mode 100644 tests/system/ixfr/in-4/tests.sh
delete mode 100644 tests/system/ixfr/ixfr_init.sh.in
delete mode 100644 tests/system/ixfr/named_noixfr.conf
delete mode 100644 tests/system/ixfr/named_nonotify.conf
delete mode 100644 tests/system/ixfr/named_notify.conf
delete mode 100755 tests/system/run.sh.in
delete mode 100755 tests/system/runall.sh
delete mode 100755 tests/system/start.pl
delete mode 100755 tests/system/stop.pl
-----------------------------------------------------------------------
diff --git a/AUTHORS b/AUTHORS
index 67cb090..b915e25 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -14,6 +14,7 @@ Michael Graff
Michal Vaner
Mukund Sivaraman
Naoki Kambe
+Paul Selkirk
Shane Kerr
Shen Tingting
Stephen Morris
diff --git a/ChangeLog b/ChangeLog
index 22912d4..88e630a 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,220 @@
+613. [func] jinmei
+ datasrc: Error handling in loading zones into memory is now more
+ consistent and convenient: data source configuration does not fail
+ due to zones configured to be loaded into memory but not available
+ in the data source, just like the case of missing zone file for
+ the MasterFiles type of data source. Also, zones that aren't
+ loaded into memory due to errors can now be reloaded for b10-auth
+ using the bindctl Auth loadzone command after fixing the error,
+ without reconfiguring the entire data source.
+ (Trac #2851, git a3d4fe8a32003534150ed076ea0bbf80e1fcc43c)
+
+612. [func] tomek
+ b10-dhcp6: Support for relayed DHCPv6 traffic has been added.
+
+611. [func] naokikambe
+ Added Xfrin statistics items such as the number of successful
+ transfers. These are per-zone type counters. Their values can be
+ obtained with zone names by invoking "Stats show Xfrin" via bindctl
+ while Xfrin is running.
+ (Trac #2252, git e1a0ea8ef5c51b9b25afa111fbfe9347afbe5413)
+
+bind10-1.0.0beta2 released on May 10, 2013
+
+610. [bug] muks
+ When the sqlite3 program is not available on the system (in
+ PATH), we no longer attempt to run some tests which depend
+ on it.
+ (Trac #1909, git f85b274b85b57a094d33ca06dfbe12ae67bb47df)
+
+609. [bug] jinmei
+ Handled some rare error cases in DNS server classes correctly.
+ This fix specifically solves occasional crash of b10-auth due to
+ errors caused by TCP DNS clients. Also, as a result of cleanups
+ with the fix, b10-auth should now be a little bit faster in
+ handling UDP queries: in some local experiments it ran about 5%
+ faster.
+ (Trac #2903, git 6d3e0f4b36a754248f8a03a29e2c36aef644cdcc)
+
+608. [bug] jinmei
+ b10-cmdctl: fixed a hangup problem on receiving the shutdown
+ command from bindctl. Note, however, that cmdctl is defined as
+ a "needed" module by default, so shutting down cmdctl would cause
+ shutdown of the entire BIND 10 system anyway, and is therefore
+ still not very useful in practice.
+ (Trac #2712, git fa392e8eb391a17d30550d4b290c975710651d98)
+
+607. [bug] jinmei
+ Worked around some unit test regressions on FreeBSD 9.1 due to
+ a binary compatibility issue between standard and system
+ libraries (http://www.freebsd.org/cgi/query-pr.cgi?pr=175453).
+ While not all tests still pass, main BIND 10 programs should
+ generally work correctly. Still, there can be odd run time
+ behavior such as abrupt crash instead of graceful shutdown
+ when some fatal event happens, so it's generally discouraged to
+ use BIND 10 on FreeBSD 9.1 RELEASE. According to the above
+ bug report for FreeBSD, it seems upgrading or downgrading the
+ FreeBSD version will solve this problem.
+ (Trac #2887, git 69dfb4544d9ded3c10cffbbfd573ae05fdeb771f)
+
+606. [bug] jinmei
+ b10-xfrout now correctly stops sending notify requests once it
+ receives a valid response. It previously handled it as if the
+ requests are timed out and resent it a few times in a short
+ period.
+ (Trac #2879, git 4c45f29f28ae766a9f7dc3142859f1d0000284e1)
+
+605. [bug] tmark
+ Modified perfdhcp to calculate the times displayed for packet sent
+ and received as time elapsed since perfdhcp process start time.
+ Previously these were times since the start of the epoch.
+ However the large numbers involved caused loss of precision
+ in the calculation of the test statistics.
+ (Trac #2785, git e9556924dcd1cf285dc358c47d65ed7c413e02cf)
+
+604. [func] marcin
+ libdhcp++: abstracted methods which open sockets and send/receive
+ DHCP4 packets to a separate class. Other classes will be derived
+ from it to implement OS-specific methods of DHCPv4 packets filtering.
+ The primary purpose for this change is to add support for Direct
+ DHCPv4 response to a client which doesn't have an address yet on
+ different OSes.
+ (Trac #991, git 33ffc9a750cd3fb34158ef676aab6b05df0302e2)
+
+603. [func] tmark
+ The directory in which the b10-dchp4 and b10-dhcp6 server id files has
+ been changed from the local state directory (set by the "configure"
+ --localstatedir switch) to the "bind10" subdirectory of it. After an
+ upgrade, server id files in the former location will be orphaned and
+ should be manually removed.
+ (Trac #2770, git a622140d411b3f07a68a1451e19df36118a80650)
+
+602. [bug] tmark
+ Perfdhcp will now exit gracefully if the command line argument for
+ IP version (-4 or -6) does not match the command line argument
+ given for the server. Prior to this perfdhcp would core when given
+ an IP version of -6 but a valid IPv4 address for server.
+ (Trac #2784, git 96b66c0c79dccf9a0206a45916b9b23fe9b94f74)
+
+601. [bug]* jinmei, vorner
+ The "delete record" interface of the database based data source
+ was extended so that the parameter includes reversed name in
+ addition to the actual name. This may help the underlying
+ accessor implementation if reversed names are more convenient
+ for the delete operation. This was the case for the SQLite3
+ accessor implementation, and it now performs delete operations
+ much faster. At a higher level, this means IXFR and DDNS Updates
+ to the sqlite3 database are no longer so slow on large zones as
+ they were before.
+ (Trac #2877, git 33bd949ac7288c61ed0a664b7329b50b36d180e5)
+
+600. [bug] tmark
+ Changed mysql_lease_mgr to set the SQL mode option to STRICT. This
+ causes mysql it to treat invalid input data as an error. Rather than
+ "successfully" inserting a too large value by truncating it, the
+ insert will fail, and the lease manager will throw an exception.
+ Also, attempts to create a HWAddr (hardware address) object with
+ too long an array of data now throw an exception.
+ (Trac #2387, git cac02e9290600407bd6f3071c6654c1216278616)
+
+599. [func] tomek
+ libdhcp++: Pkt6 class is now able to parse and build relayed DHCPv6
+ messages.
+ (Trac #2827, git 29c3f7f4e82d7e85f0f5fb692345fd55092796b4)
+
+bind10-1.0.0beta1 released on April 4, 2013
+
+598. [func]* jinmei
+ The separate "static" data source is now deprecated as it can be
+ served in the more generic "MasterFiles" type of data source.
+ This means existing configuration may not work after an update.
+ If "config show data_sources/classes/CH[0]" on bindctl contains a
+ "static" type of data source, you'll need to update it as follows:
+ > config set data_sources/classes/CH[0]/type MasterFiles
+ > config set data_sources/classes/CH[0]/params {"BIND": =>
+ "<the value of current data_sources/classes/CH[0]/params>"}
+ > config set data_sources/classes/CH[0]/cache-enable true
+ > config commit
+ (Same for CH[1], CH[2], IN[0], etc, if applicable, although it
+ should be very unlikely in practice. Also note: '=>' above
+ indicates the next line is actually part of the command. Do
+ not type in this "arrow").
+ (Part of Trac #2833, git 0363b4187fe3c1a148ad424af39e12846610d2d7)
+
+597. [func] tmark
+ b10-dhcp6: Added unit tests for handling requests when no
+ IPv6 subnets are configured/defined. Testing these conditions
+ was overlooked during implementation of Trac #2719.
+ (Trac #2721, git ce7f53b2de60e2411483b4aa31c714763a36da64)
+
+596. [bug] jinmei
+ Added special handling for the case where b10-auth receives a
+ NOTIFY message, but zonemgr isn't running. Previously this was
+ logged as a communications problem at the ERROR level, resulting
+ in increasing noise when zonemgr is intentionally stopped. Other
+ than the log level there is no change in externally visible
+ behavior.
+ (Trac #2562, git 119eed9938b17cbad3a74c823aa9eddb7cd337c2)
+
+595. [bug] tomek
+ All DHCP components now gracefully refuse to handle too short
+ DUIDs and client-id.
+ (Trac #2723, git a043d8ecda6aff57922fe98a33c7c3f6155d5d64)
+
+594. [func] muks, pselkirk
+ libdns++: the NSEC, DS, DLV, and AFSDB Rdata classes now use the
+ generic lexer in constructors from text. This means that the name
+ fields in such RRs in a zone file can now be non-absolute (the
+ origin name in that context will be used), e.g., when loaded by
+ b10-loadzone.
+ (Trac #2386, git dc0f34afb1eccc574421a802557198e6cd2363fa)
+ (Trac #2391, git 1450d8d486cba3bee8be46e8001d66898edd370c)
+
+593. [func] jelte
+ Address + port output and logs is now consistent according to our
+ coding guidelines, e.g. <address>:<port> in the case of IPv4, and
+ [<address>]:<port> in the case of IPv6, instead of <address>#<port>
+ (Trac #1086, git bcefe1e95cdd61ee4a09b20522c3c56b315a1acc)
+
+592. [bug] jinmei
+ b10-auth and zonemgr now handle some uncommon NOTIFY messages more
+ gracefully: auth immediately returns a NOTAUTH response if the
+ server does not have authority for the zone (the behavior
+ compatible with BIND 9) without bothering zonemgr; zonemgr now
+ simply skips retransfer if the specified zone is not in its
+ secondary zone list, instead of producing noisy error logs.
+ (Trac #1938, git 89d7de8e2f809aef2184b450e7dee1bfec98ad14)
+
+591. [func] vorner
+ Ported the remaining tests from the old shell/perl based system to
+ lettuce. Make target `systest' is now gone. Currently, the lettuce
+ tests are in git only, not part of the release tarball.
+ (Trac #2624, git df1c5d5232a2ab551cd98b77ae388ad568a683ad)
+
+590. [bug] tmark
+ Modified "include" statements in DHCP MySQL lease manager code to
+ fix build problems if MySQL is installed in a non-standard location.
+ (Trac #2825, git 4813e06cf4e0a9d9f453890557b639715e081eca)
+
+589. [bug] jelte
+ b10-cmdctl now automatically re-reads the user accounts file when
+ it is updated.
+ (Trac #2710, git 16e8be506f32de668699e6954f5de60ca9d14ddf)
+
+588. [bug]* jreed
+ b10-xfrout: Log message id XFROUT_QUERY_QUOTA_EXCCEEDED
+ changed to XFROUT_QUERY_QUOTA_EXCEEDED.
+ (git be41be890f1349ae4c870a887f7acd99ba1eaac5)
+
+587. [bug] jelte
+ When used from python, the dynamic datasource factory now
+ explicitely loads the logging messages dictionary, so that correct
+ logging messages does not depend on incidental earlier import
+ statements. Also, the sqlite3-specific log messages have been moved
+ from the general datasource library to the sqlite3 datasource
+ (which also explicitely loads its messages).
+ (Trac #2746, git 1c004d95a8b715500af448683e4a07e9b66ea926)
+
586. [func] marcin
libdhcp++: Removed unnecesary calls to the function which
validates option definitions used to create instances of options
diff --git a/Makefile.am b/Makefile.am
index fe995a7..10ef321 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -2,7 +2,7 @@ ACLOCAL_AMFLAGS = -I m4macros -I examples/m4 ${ACLOCAL_FLAGS}
# ^^^^^^^^ This has to be the first line and cannot come later in this
# Makefile.am due to some bork in some versions of autotools.
-SUBDIRS = compatcheck doc . src tests
+SUBDIRS = compatcheck doc . src tests m4macros
USE_LCOV=@USE_LCOV@
LCOV=@LCOV@
GENHTML=@GENHTML@
@@ -46,7 +46,7 @@ endif
clean-cpp-coverage:
@if [ $(USE_LCOV) = yes ] ; then \
$(LCOV) --directory . --zerocounters; \
- rm -rf coverage/; \
+ rm -rf $(abs_top_srcdir)/coverage-cpp-html/; \
else \
echo "C++ code coverage not enabled at configuration time." ; \
echo "Use: ./configure --with-lcov" ; \
@@ -115,11 +115,6 @@ cppcheck:
--template '{file}:{line}: check_fail: {message} ({severity},{id})' \
src
-# system tests
-systest:
- cd tests/system; \
- sh $(abs_srcdir)/tests/system/runall.sh
-
### include tool to generate documentation from log message specifications
### in the distributed tarball:
EXTRA_DIST = tools/system_messages.py
diff --git a/README b/README
index 70c6bee..f9b24c5 100644
--- a/README
+++ b/README
@@ -25,6 +25,12 @@ the DHCPv4 and DHCPv6 servers must be considered experimental.
Limitations and known issues with this DHCP release can be found
at http://bind10.isc.org/wiki/KeaKnownIssues
+NOTE: The API/ABI provided by libraries in BIND 10 may change in future
+point releases. So please do not assume currently that any code that you
+compile for a particular version of a BIND 10 library will work in
+future versions of the library. We aim to stabilize the public API/ABI
+interface of BIND 10 libraries in future releases.
+
Documentation is included with the source. See doc/guide/bind10-guide.txt
(or bind10-guide.html) for installation instructions. The
documentation is also available via the BIND 10 website at
diff --git a/configure.ac b/configure.ac
index 066dcb2..159841f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2,7 +2,7 @@
# Process this file with autoconf to produce a configure script.
AC_PREREQ([2.59])
-AC_INIT(bind10, 20130221, bind10-dev at isc.org)
+AC_INIT(bind10, 20130510, bind10-dev at isc.org)
AC_CONFIG_SRCDIR(README)
# serial-tests is not available in automake version before 1.13. In
# automake 1.13 and higher, AM_PROG_INSTALL is undefined, so we'll check
@@ -323,9 +323,9 @@ if test -x ${PYTHON}-config; then
# so we only go through the flag if it's contained; also, protecting
# the output with [] seems necessary for environment to avoid getting
# an empty output accidentally.
- python_config_ldflags=[`${PYTHON}-config --ldflags | sed -ne 's/\([ \t]*-L\)[ ]*\([^ \t]*[ \t]*\)/\1\2/pg'`]
+ python_config_ldflags=[`${PYTHON}-config --ldflags | ${SED} -ne 's/\([ \t]*-L\)[ ]*\([^ \t]*[ \t]*\)/\1\2/gp'`]
for flag in $python_config_ldflags; do
- flag=`echo $flag | sed -ne 's/^\(\-L.*\)$/\1/p'`
+ flag=`echo $flag | ${SED} -ne 's/^\(\-L.*\)$/\1/p'`
if test "X${flag}" != X; then
PYTHON_LDFLAGS="$PYTHON_LDFLAGS ${flag}"
fi
@@ -351,7 +351,7 @@ fi
if test "x$ISC_RPATH_FLAG" != "x"; then
python_rpath=
for flag in ${PYTHON_LDFLAGS}; do
- python_rpath="${python_rpath} `echo $flag | sed -ne "s/^\(\-L\)/${ISC_RPATH_FLAG}/p"`"
+ python_rpath="${python_rpath} `echo $flag | ${SED} -ne "s/^\(\-L\)/${ISC_RPATH_FLAG}/p"`"
done
PYTHON_LDFLAGS="${PYTHON_LDFLAGS} ${python_rpath}"
fi
@@ -388,8 +388,6 @@ In this case we will continue, but naming of python processes will not work.])
fi
fi
-# TODO: check for _sqlite3.py module
-
# (g++ only check)
# Python 3.2 has an unused parameter in one of its headers. This
# has been reported, but not fixed as of yet, so we check if we need
@@ -536,7 +534,7 @@ if test "$lcov" != "no"; then
AC_MSG_ERROR([Cannot find lcov.])
fi
# is genhtml always in the same directory?
- GENHTML=`echo "$LCOV" | sed s/lcov$/genhtml/`
+ GENHTML=`echo "$LCOV" | ${SED} s/lcov$/genhtml/`
if test ! -x $GENHTML; then
AC_MSG_ERROR([genhtml not found, needed for lcov])
fi
@@ -712,15 +710,15 @@ fi
BOTAN_LDFLAGS=
BOTAN_NEWLIBS=
for flag in ${BOTAN_LIBS}; do
- BOTAN_LDFLAGS="${BOTAN_LDFLAGS} `echo $flag | sed -ne '/^\(\-L\)/p'`"
- BOTAN_LIBS="${BOTAN_LIBS} `echo $flag | sed -ne '/^\(\-l\)/p'`"
+ BOTAN_LDFLAGS="${BOTAN_LDFLAGS} `echo $flag | ${SED} -ne '/^\(\-L\)/p'`"
+ BOTAN_LIBS="${BOTAN_LIBS} `echo $flag | ${SED} -ne '/^\(\-l\)/p'`"
done
# See python_rpath for some info on why we do this
if test "x$ISC_RPATH_FLAG" != "x"; then
BOTAN_RPATH=
for flag in ${BOTAN_LIBS}; do
- BOTAN_RPATH="${BOTAN_RPATH} `echo $flag | sed -ne "s/^\(\-L\)/${ISC_RPATH_FLAG}/p"`"
+ BOTAN_RPATH="${BOTAN_RPATH} `echo $flag | ${SED} -ne "s/^\(\-L\)/${ISC_RPATH_FLAG}/p"`"
done
AC_SUBST(BOTAN_RPATH)
@@ -890,9 +888,12 @@ AC_ARG_WITH(shared-memory,
[Build with Boost shared memory support; for large scale authoritative DNS servers]),
[use_shared_memory=$withval])
if test X$use_shared_memory = Xyes -a "$BOOST_MAPPED_FILE_WOULDFAIL" = "yes"; then
- AC_MSG_ERROR([Boost shared memory does not compile on this system. If you don't need it (most normal users won't) build without it; using a different compiler or a different version of Boost may also help.])
+ AC_MSG_ERROR([Boost shared memory does not compile on this system. If you don't need it (most normal users won't) build without it by rerunning this script with --without-shared-memory; using a different compiler or a different version of Boost may also help.])
fi
AM_CONDITIONAL([USE_SHARED_MEMORY], [test x$use_shared_memory = xyes])
+if test "x$use_shared_memory" = "xyes"; then
+ AC_DEFINE(USE_SHARED_MEMORY, 1, [Define to 1 if shared memory support is enabled])
+fi
AC_SUBST(BOOST_MAPPED_FILE_CXXFLAG)
# Add some default CPP flags needed for Boost, identified by the AX macro.
@@ -1043,12 +1044,16 @@ AC_SUBST(GTEST_LDFLAGS)
AC_SUBST(GTEST_LDADD)
AC_SUBST(GTEST_SOURCE)
-dnl check for pkg-config itself so we don't try the m4 macro without pkg-config
+dnl check for pkg-config itself
AC_CHECK_PROG(HAVE_PKG_CONFIG, pkg-config, yes, no)
if test "x$HAVE_PKG_CONFIG" = "xno" ; then
AC_MSG_ERROR(Please install pkg-config)
fi
-PKG_CHECK_MODULES(SQLITE, sqlite3 >= 3.3.9, enable_features="$enable_features SQLite3")
+
+AX_SQLITE3_FOR_BIND10
+if test "x$have_sqlite" = "xyes" ; then
+ enable_features="$enable_features SQLite3"
+fi
#
# ASIO: we extensively use it as the C++ event management module.
@@ -1201,6 +1206,7 @@ AC_CONFIG_FILES([Makefile
src/bin/dhcp4/tests/Makefile
src/bin/resolver/Makefile
src/bin/resolver/tests/Makefile
+ src/bin/resolver/bench/Makefile
src/bin/sysinfo/Makefile
src/bin/sockcreator/Makefile
src/bin/sockcreator/tests/Makefile
@@ -1316,13 +1322,13 @@ AC_CONFIG_FILES([Makefile
src/lib/statistics/Makefile
src/lib/statistics/tests/Makefile
tests/Makefile
- tests/system/Makefile
tests/tools/Makefile
tests/tools/badpacket/Makefile
tests/tools/badpacket/tests/Makefile
tests/tools/perfdhcp/Makefile
tests/tools/perfdhcp/tests/Makefile
tests/tools/perfdhcp/tests/testdata/Makefile
+ m4macros/Makefile
dns++.pc
])
AC_OUTPUT([doc/version.ent
@@ -1400,23 +1406,6 @@ AC_OUTPUT([doc/version.ent
src/lib/util/python/gen_wiredata.py
src/lib/server_common/tests/data_path.h
tests/lettuce/setup_intree_bind10.sh
- tests/system/conf.sh
- tests/system/run.sh
- tests/system/glue/setup.sh
- tests/system/glue/nsx1/b10-config.db
- tests/system/bindctl/nsx1/b10-config.db.template
- tests/system/ixfr/db.example.n0
- tests/system/ixfr/db.example.n2
- tests/system/ixfr/db.example.n2.refresh
- tests/system/ixfr/db.example.n4
- tests/system/ixfr/db.example.n6
- tests/system/ixfr/ixfr_init.sh
- tests/system/ixfr/b10-config.db
- tests/system/ixfr/common_tests.sh
- tests/system/ixfr/in-1/setup.sh
- tests/system/ixfr/in-2/setup.sh
- tests/system/ixfr/in-3/setup.sh
- tests/system/ixfr/in-4/setup.sh
], [
chmod +x src/bin/cmdctl/run_b10-cmdctl.sh
chmod +x src/bin/xfrin/run_b10-xfrin.sh
@@ -1447,14 +1436,6 @@ AC_OUTPUT([doc/version.ent
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
- chmod +x tests/system/conf.sh
- chmod +x tests/system/run.sh
- chmod +x tests/system/ixfr/ixfr_init.sh
- chmod +x tests/system/ixfr/common_tests.sh
- chmod +x tests/system/ixfr/in-1/setup.sh
- chmod +x tests/system/ixfr/in-2/setup.sh
- chmod +x tests/system/ixfr/in-3/setup.sh
- chmod +x tests/system/ixfr/in-4/setup.sh
])
AC_OUTPUT
diff --git a/doc/design/ipc-high.txt b/doc/design/ipc-high.txt
new file mode 100644
index 0000000..3f46b5c
--- /dev/null
+++ b/doc/design/ipc-high.txt
@@ -0,0 +1,382 @@
+The IPC protocol
+================
+
+While the cc-protocol.txt describes the low-level primitives, here we
+describe how the whole IPC should work and how to use it.
+
+Definitions
+-----------
+
+system::
+ The system that moves data between the users and does bookkeeping.
+ In our current implementation, it is implemented as the MsgQ daemon,
+ which the users connect to and it routes the data.
+user::
+ Usually a process; generally an entity that wants to communicate
+ with the other users.
+session::
+ Session is the interface by which the user communicates with the
+ system. Single user may have multiple sessions, a session belongs to
+ single user.
+message::
+ A data blob sent by one user. The recipient might be the system
+ itself, other session or set of sessions (called group, see below,
+ it is possibly empty). Message is either a response or an original
+ message (TODO: Better name?).
+group::
+ A named set of sessions. Conceptually, all the possible groups
+ exist, there's no explicit creation and deletion of groups.
+session id::
+ Unique identifier of a session. It is not reused for the whole
+ lifetime of the system. Historically called `lname` in the code.
+undelivery signal::
+ While sending an original message, a client may request an
+ undelivery signal. If the recipient specification yields no
+ sessions to deliver the message to, the system informs user about
+ the situation.
+sequence number::
+ Each message sent through the system carries a sequence number. The
+ number should be unique per sender. It can be used to pair a
+ response to the original message, since the response specifies which
+ sequence number had the message it response to. Even responses and
+ messages not expecting answer have their sequence number, but it is
+ generally unused.
+non-blocking operation::
+ Operation that will complete without waiting for anything.
+fast operation::
+ Operation that may wait for other process, but only for a very short
+ time. Generally, this includes communication between the user and
+ system, but not between two clients. It can be expected to be fast
+ enough to use this inside an interactive session, but may be too
+ heavy in the middle of query processing, for example. Every
+ non-blocking operation is considered fast.
+
+The session
+-----------
+
+The session interface allows for several operations interacting with
+the system. In the code, it is represented by a class.
+
+Possible operations include:
+
+Opening a session::
+ The session is created and connects to the system. This operation is
+ fast. The session receives session id from the system.
+
+Group management::
+ A user may subscribe (become member) of a group, or unsubscribe from
+ a group. These are fast operations.
+
+Send::
+ A user may send a message, addressed to the system, or other
+ session(s). This operation is expected to be non-blocking
+ (current implementation is based on assumption of how OS handles the
+ sends, which may need to be revisited if it turns out to be false).
+
+Receive synchronously::
+ User may wait for an incoming message in blocking mode. It is
+ possible to specify the kind of message to wait for, either original
+ message or response to a message. This interface has a timeout.
+
+Receive asynchronously::
+ Similar to previous, but non-blocking. It terminates immediately.
+ The user provides a callback that is invoked when the requested
+ message arrives.
+
+Terminate::
+ A session may be terminated. No more messages are sent or received
+ over it, the session is automatically unsubscribed from all the
+ groups. This operation is non-blocking. A session is terminated
+ automatically if the user exits.
+
+Assumptions
+-----------
+
+We assume reliability and order of delivery. Messages sent from user A
+to B are all delivered unchanged in original order as long as B
+exists.
+
+All above operations are expected to always succeed. If there's an
+error reported, it should be considered fatal and user should
+exit. In case a user still wants to continue, the session must be
+considered terminated and a new one must be created. Care must be
+taken not to use any information obtained from the previous session,
+since the state in other users and the system may have changed during
+the reconnect.
+
+Addressing
+----------
+
+Addressing happens in three ways:
+
+By group name::
+ The message is routed to all the sessions subscribed to this group.
+ It is legal to address an empty group; such message is then
+ delivered to no sessions.
+By session ID::
+ The message is sent to the single session, if it is still alive.
+By an alias::
+ A session may have any number of aliases - well known names. Only
+ single session may hold given alias (but it is not yet enforced by
+ the system). The message is delivered to the one session owning the
+ alias, if any. Internally, the aliases are implemented as groups
+ with single subscribed session, so it is the same as the first
+ option on the protocol level, but semantically it is different.
+
+The system
+----------
+
+The system performs these goals:
+
+ * Maintains the open sessions and allows creating new ones.
+ * Keeps information about groups and which sessions are subscribed to
+ which group.
+ * Routes the messages between users.
+
+Also, the system itself is a user of the system. It can be reached by
+the alias `Msgq` and provides following high-level services (see
+below):
+
+Notifications about sessions::
+ When a session is opened to the system or when a session is
+ terminated, a notification is sent to interested users. The
+ notification contains the session ID of the session in question.
+ The termination notification is probably more useful (if a user
+ communicated with a given session before, it might be interested it
+ is no longer available), the opening notification is provided mostly
+ for completeness.
+Notifications about group subscriptions::
+ When a session subscribes to a group or unsubscribes from a group, a
+ notification is sent to interested users. The notification contains
+ both the session ID of the session subscribing/unsubscribing and
+ name of the group. This includes notifications about aliases (since
+ aliases are groups internally).
+Commands to list sessions::
+ There's a command to list session IDs of all currently opened sessions
+ and a command to list session IDs of all sessions subscribed to a
+ given group. Note that using these lists might need some care, as
+ the information might be outdated at the time it is delivered to the
+ user.
+
+User shows interest in notifications about sessions and group
+subscriptions by subscribing to a group with well-known name (as with
+any notification).
+
+Note that due to implementation details, the `Msgq` alias is not yet
+available during early stage of the bootstrap of bind10 system. This
+means some very core services can't rely on the above services of the
+system. The alias is guaranteed to be working before the first
+non-core module is started.
+
+Higher-level services
+---------------------
+
+While the system is able to send any kind of data, the payload sent by
+users in bind10 is structured data encoded as JSON. The messages sent
+are of three general types:
+
+Command::
+ A message sent to single destination, with the undeliverable
+ signal turned on and expecting an answer. This is a request
+ to perform some operation on the recipient (it can have side effects
+ or not). The command is identified by a name and it can have
+ parameters. A command with the same name may behave differently (or
+ have different parameters) on different receiving users.
+Reply::
+ An answer to the `Command`. It is sent directly to the session where
+ the command originated from, does not expect further answer and the
+ undeliverable notification is not set. It either confirms the
+ command was run successfully and contains an optional result, or
+ notifies the sender of failure to run the command. Success and
+ failure differ only in the payload sent through the system, not in
+ the way it is sent. The undeliverable signal is failure
+ reply sent by the system on behalf of the missing recipient.
+Notification::
+ A message sent to any number of destinations (eg. sent to a group),
+ not expecting an answer. It notifies other users about an event or
+ change of state.
+
+Details of the higher-level
+---------------------------
+
+While there are libraries implementing the communication in convenient
+way, it is useful to know what happens inside.
+
+The notifications are probably the simplest. Users interested in
+receiving notifications of some family subscribe to corresponding
+group. Then, a client sends a message to the group. For example, if
+clients `receiver-A` and `receiver-B` want to receive notifications
+about changes to zone data, they'd subscribe to the
+`Notifications/ZoneUpdates` group. Then, other client (let's say
+`XfrIn`, with session ID `s12345`) would send something like:
+
+ s12345 -> Notifications/ZoneUpdates
+ {"notification": ["zone-update", {
+ "class": "IN",
+ "origin": "example.org.",
+ "serial": 123456
+ }]}
+
+Both receivers would receive the message and know that the
+`example.org` zone is now at version 123456. Note that multiple users
+may produce the same kind of notification. Also, single group may be
+used to send multiple notification names (but they should be related;
+in our example, the `Notifications/ZoneUpdates` could be used for
+`zone-update`, `zone-available` and `zone-unavailable` notifications
+for change in zone data, configuration of new zone in the system and
+removal of a zone from configuration).
+
+Sending a command to single recipient is slightly more complex. The
+sending user sends a message to the receiving one, addressed either by
+session ID or by an alias (group to which at most one session may be
+subscribed). The message contains the name of the command and
+parameters. It is sent with the undeliverable signals turned on.
+The user also starts a timer (with reasonably long timeout). The
+sender also subscribes to notifications about terminated sessions or
+unsubscription from the alias group.
+
+The receiving user gets the message, runs the command and sends a
+response back, with the result. The response has the undeliverable
+signal turned off and it is marked as response to the message
+containing the command. The sending user receives the answer and pairs
+it with the command.
+
+There are several things that may go wrong.
+
+* There might be an error on the receiving user (bad parameters, the
+ operation failed, the recipient doesn't know command of that name).
+ The receiving side sends the response as previous, the only
+ difference is the content of the payload. The sending user is
+ notified about it, without delays.
+* The recipient user doesn't exist (either the session ID is wrong or
+ terminated already, or the alias is empty). The system sends a
+ failure response and the sending user knows immediately the command
+ failed.
+* The recipient disconnects while processing the command (possibly
+ crashes). The sender gets a notification about disconnection or
+ unsubscription from the alias group and knows the answer won't come.
+* The recipient ``blackholes'' the command. It receives it, but never
+ answers. The timeout in sender times out. As this is a serious
+ programmer error in the recipient and should be rare, the sender
+ should at least log an error to notify about the case.
+
+One example would be asking the question of life, universe and
+everything (all the examples assume the sending user is already
+subscribed to the notifications):
+
+ s12345 -> DeepThought
+ {"command": ["question", {
+ "what": ["Life", "Universe", "*"]
+ }]}
+ s23456 -> s12345
+ {"reply": [0, 42]}
+
+The deep thought had an alias. But the answer is sent from its session
+ID. The `0` in the reply means ``success''.
+
+Another example might be asking for some data at a bureau and getting
+an error:
+
+ s12345 -> Burreau
+ {"command": ["provide-information", {
+ "about": "me",
+ "topic": "taxes"
+ }]}
+ s23456 -> s12345
+ {"reply": [1, "You need to fill in other form"]}
+
+And, in this example, the sender is trying to reach an non-existent
+session. The `msgq` here is not the alias `Msgq`, but a special
+``phantom'' session ID that is not listed anywhere.
+
+ s12345 -> s0
+ {"command": ["ping"]}
+ msgq -> s12345
+ {"reply": [-1, "No such recipient"]}
+
+Last, an example when the other user disconnects while processing the
+command.
+
+ s12345 -> s23456
+ {"command": ["shutdown"]}
+ msgq -> s12345
+ {"notification": ["disconnected", {
+ "lname": "s23456"
+ }]}
+
+The system does not support sending a command to multiple users
+directly. It can be accomplished as this:
+
+* The sending user calls a command on the system to get list of
+ sessions in given group. This is command to alias, so it can be done
+ by the previous way.
+* After receiving the list of session IDs, multiple copies of the
+ command are sent by the sending user, one to each of the session
+ IDs.
+* Successes and failures are handled the same as above, since these
+ are just single-recipient commands.
+
+So, this would be an example with unhelpful war council.
+
+ s12345 -> Msgq
+ {"command": ["get-subscriptions", {
+ "group": "WarCouncil"
+ }]}
+ msgq -> s12345
+ {"reply": [0, ["s1", "s2", "s3"]]}
+ s12345 -> s1
+ {"command": ["advice", {
+ "topic": "Should we attack?"
+ }]}
+ s12345 -> s2
+ {"command": ["advice", {
+ "topic": "Should we attack?"
+ }]}
+ s12345 -> s3
+ {"command": ["advice", {
+ "topic": "Should we attack?"
+ }]}
+ s1 -> s12345
+ {"reply": [0, true]}
+ s2 -> s12345
+ {"reply": [0, false]}
+ s3 -> s12345
+ {"reply": [1, "Advice feature not implemented"]}
+
+Users
+-----
+
+While there's a lot of flexibility for the behaviour of a user, it
+usually comes to something like this (during the lifetime of the
+user):
+
+* The user starts up.
+* Then it creates one or more sessions (there may be technical reasons
+ to have more than one session, such as threads, but it is not
+ required by the system).
+* It subscribes to some groups to receive notifications in future.
+* It binds to some aliases if it wants to be reachable by others by a
+ nice name.
+* It invokes some start-up commands (to get the configuration, for
+ example).
+* During the lifetime, it listens for notifications and answers
+ commands. It also invokes remote commands and sends notifications
+ about things that are happening.
+* Eventually, the user terminates, closing all the sessions it had
+ opened.
+
+Known limitations
+-----------------
+
+It is meant mostly as signalling protocol. Sending millions of
+messages or messages of several tens of megabytes is probably a bad
+idea. While there's no architectural limitation with regards of the
+number of transferred messages and the maximum size of message is 4GB,
+the code is not optimised and it would probably be very slow.
+
+We currently expect the system not to be at heavy load. Therefore, we
+expect the system to keep up with users sending messages. The
+libraries write in blocking mode, which is no problem if the
+expectation is true, as the write buffers will generally be empty and
+the write wouldn't block, but if it turns out it is not the case, we
+might need to reconsider.
diff --git a/doc/devel/mainpage.dox b/doc/devel/mainpage.dox
index 295fd03..92f86eb 100644
--- a/doc/devel/mainpage.dox
+++ b/doc/devel/mainpage.dox
@@ -30,6 +30,7 @@
* - @subpage dhcpv6ConfigInherit
* - @subpage libdhcp
* - @subpage libdhcpIntro
+ * - @subpage libdhcpRelay
* - @subpage libdhcpIfaceMgr
* - @subpage libdhcpsrv
* - @subpage leasemgr
diff --git a/doc/guide/bind10-guide.xml b/doc/guide/bind10-guide.xml
index 583ee60..568bbea 100644
--- a/doc/guide/bind10-guide.xml
+++ b/doc/guide/bind10-guide.xml
@@ -772,6 +772,16 @@ as a dependency earlier -->
</listitem>
</varlistentry>
+ <varlistentry>
+ <term>--without-werror</term>
+ <listitem>
+ <simpara>Disable the default use of the
+ <option>-Werror</option> compiler flag so that
+ compiler warnings aren't build failures.
+ </simpara>
+ </listitem>
+ </varlistentry>
+
</variablelist>
<note>
<para>
@@ -2487,8 +2497,8 @@ can use various data source backends.
<para>
The configuration is located in data_sources/classes. Each item there
represents one RR class and a list used to answer queries for that
- class. The default contains two classes. The CH class contains a static
- data source — one that serves things like
+ class. The default contains two classes. The CH class contains a
+ built-in data source — one that serves things like
<quote>AUTHORS.BIND.</quote>. The IN class contains single SQLite3
data source with database file located at
<filename>/usr/local/var/bind10/zone.sqlite3</filename>.
@@ -2555,7 +2565,7 @@ can use various data source backends.
</para>
<para>
- First, let's disable the static data source
+ First, let's disable the built-in data source
(<quote>VERSION.BIND</quote> and friends). As it is the only
data source in the CH class, we can remove the whole class.
@@ -4100,7 +4110,7 @@ Dhcp4/subnet4 [] list (default)
> <userinput>config commit</userinput>
</screen>
Even though the "container" option does not carry any data except
- sub-options, the "data" field must be explictly set to an empty value.
+ sub-options, the "data" field must be explicitly set to an empty value.
This is required because in the current version of BIND 10 DHCP, the
default configuration values are not propagated to the configuration parsers:
if the "data" is not set the parser will assume that this
@@ -4812,7 +4822,7 @@ should include options from the isc option space:
> <userinput>config commit</userinput>
</screen>
Even though the "container" option does not carry any data except
- sub-options, the "data" field must be explictly set to an empty value.
+ sub-options, the "data" field must be explicitly set to an empty value.
This is required because in the current version of BIND 10 DHCP, the
default configuration values are not propagated to the configuration parsers:
if the "data" is not set the parser will assume that this
@@ -4832,29 +4842,22 @@ should include options from the isc option space:
<section id="dhcp6-config-subnets">
<title>Subnet Selection</title>
<para>
- The DHCPv6 server may receive requests from local (connected
- to the same subnet as the server) and remote (connecting via
- relays) clients.
- <note>
- <para>
- Currently relayed DHCPv6 traffic is not supported. The server will
- only respond to local DHCPv6 requests - see <xref linkend="dhcp6-limit"/>
- </para>
- </note>
- As it may have many subnet configurations defined, it
- must select appropriate subnet for a given request. To do this, the server first
+ The DHCPv6 server may receive requests from local (connected to the
+ same subnet as the server) and remote (connecting via relays) clients.
+ As server may have many subnet configurations defined, it must select
+ appropriate subnet for a given request. To do this, the server first
checks if there is only one subnet defined and source of the packet is
- link-local. If this is the case, the server assumes that the only subnet
- defined is local and client is indeed connected to it. This check
- simplifies small deployments.
+ link-local. If this is the case, the server assumes that the only
+ subnet defined is local and client is indeed connected to it. This
+ check simplifies small deployments.
</para>
<para>
If there are two or more subnets defined, the server can not assume
which of those (if any) subnets are local. Therefore an optional
- "interface" parameter is available within a subnet definition to designate that a given subnet
- is local, i.e. reachable directly over specified interface. For example
- the server that is intended to serve a local subnet over eth0 may be configured
- as follows:
+ "interface" parameter is available within a subnet definition to
+ designate that a given subnet is local, i.e. reachable directly over
+ specified interface. For example the server that is intended to serve
+ a local subnet over eth0 may be configured as follows:
<screen>
> <userinput>config add Dhcp6/subnet6</userinput>
> <userinput>config set Dhcp6/subnet6[1]/subnet "2001:db8:beef::/48"</userinput>
@@ -4865,6 +4868,66 @@ should include options from the isc option space:
</para>
</section>
+ <section id="dhcp6-relays">
+ <title>DHCPv6 Relays</title>
+ <para>
+ A DHCPv6 server with multiple subnets defined must select the
+ appropriate subnet when it receives a request from client. For clients
+ connected via relays, two mechanisms are used:
+ </para>
+ <para>
+ The first uses the linkaddr field in the RELAY_FORW message. The name
+ of this field is somewhat misleading in that it does not contain a link-layer
+ address: instead, it holds an address (typically a global address) that is
+ used to identify a link. The DHCPv6 server checks if the address belongs
+ to a defined subnet and, if it does, that subnet is selected for the client's
+ request.
+ </para>
+ <para>
+ The second mechanism is based on interface-id options. While forwarding a client's
+ message, relays may insert an interface-id option into the message that
+ identifies the interface on the relay that received the message. (Some
+ relays allow configuration of that parameter, but it is sometimes
+ hardcoded and may range from the very simple (e.g. "vlan100") to the very cryptic:
+ one example seen on real hardware was "ISAM144|299|ipv6|nt:vp:1:110"). The
+ server can use this information to select the appropriate subnet.
+ The information is also returned to the relay which then knows the
+ interface to use to transmit the response to the client. In order for
+ this to work successfully, the relay interface IDs must be unique within
+ the network and the server configuration must match those values.
+ </para>
+ <para>
+ When configuring the DHCPv6 server, it should be noted that two
+ similarly-named parameters can be configured for a subnet:
+ <itemizedlist>
+ <listitem><simpara>
+ "interface" defines which local network interface can be used
+ to access a given subnet.
+ </simpara></listitem>
+ <listitem><simpara>
+ "interface-id" specifies the content of the interface-id option
+ used by relays to identify the interface on the relay to which
+ the response packet is sent.
+ </simpara></listitem>
+ </itemizedlist>
+ The two are mutually exclusive: a subnet cannot be both reachable locally
+ (direct traffic) and via relays (remote traffic). Specifying both is a
+ configuration error and the DHCPv6 server will refuse such a configuration.
+ </para>
+
+ <para>
+ To specify interface-id with value "vlan123", the following commands can
+ be used:
+ <screen>
+> <userinput>config add Dhcp6/subnet6</userinput>
+> <userinput>config set Dhcp6/subnet6[0]/subnet "2001:db8:beef::/48"</userinput>
+> <userinput>config set Dhcp6/subnet6[0]/pool [ "2001:db8:beef::/48" ]</userinput>
+> <userinput>config set Dhcp6/subnet6[0]/interface-id "vland123"</userinput>
+> <userinput>config commit</userinput>
+</screen>
+ </para>
+ </section>
+
</section>
<section id="dhcp6-serverid">
@@ -4931,9 +4994,6 @@ Dhcp6/renew-timer 1000 integer (default)
</para>
</listitem>
<listitem>
- <simpara>Relayed traffic is not supported.</simpara>
- </listitem>
- <listitem>
<simpara>Temporary addresses are not supported.</simpara>
</listitem>
<listitem>
diff --git a/examples/host/host.cc b/examples/host/host.cc
index a5c6522..c5dd604 100644
--- a/examples/host/host.cc
+++ b/examples/host/host.cc
@@ -95,7 +95,7 @@ host_lookup(const char* const name, const char* const dns_class,
cout << "Name: " << server << "\n";
// TODO: I guess I have to do a lookup to get that address and aliases
// too
- //cout << "Address: " << address << "\n" ; // "#" << port << "\n";
+ //cout << "Address: " << address << "\n" ;
//cout << "Aliases: " << server << "\n";
}
diff --git a/m4macros/Makefile.am b/m4macros/Makefile.am
new file mode 100644
index 0000000..eeae7f9
--- /dev/null
+++ b/m4macros/Makefile.am
@@ -0,0 +1,2 @@
+EXTRA_DIST = ax_boost_for_bind10.m4
+EXTRA_DIST += ax_sqlite3_for_bind10.m4
diff --git a/m4macros/ax_sqlite3_for_bind10.m4 b/m4macros/ax_sqlite3_for_bind10.m4
new file mode 100644
index 0000000..4eb7f94
--- /dev/null
+++ b/m4macros/ax_sqlite3_for_bind10.m4
@@ -0,0 +1,25 @@
+dnl @synopsis AX_SQLITE3_FOR_BIND10
+dnl
+dnl Test for the sqlite3 library and program, intended to be used within
+dnl BIND 10, and to test BIND 10.
+dnl
+dnl We use pkg-config to look for the sqlite3 library, so the sqlite3
+dnl development package with the .pc file must be installed.
+dnl
+dnl This macro sets SQLITE_CFLAGS and SQLITE_LIBS. It also sets
+dnl SQLITE3_PROGRAM to the path of the sqlite3 program, if it is found
+dnl in PATH.
+
+AC_DEFUN([AX_SQLITE3_FOR_BIND10], [
+
+PKG_CHECK_MODULES(SQLITE, sqlite3 >= 3.3.9,
+ have_sqlite="yes",
+ have_sqlite="no (sqlite3 not detected)")
+
+# Check for sqlite3 program
+AC_PATH_PROG(SQLITE3_PROGRAM, sqlite3, no)
+AM_CONDITIONAL(HAVE_SQLITE3_PROGRAM, test "x$SQLITE3_PROGRAM" != "xno")
+
+# TODO: check for _sqlite3.py module
+
+])dnl AX_SQLITE3_FOR_BIND10
diff --git a/src/bin/auth/auth_messages.mes b/src/bin/auth/auth_messages.mes
index 77b20b1..f0e9e46 100644
--- a/src/bin/auth/auth_messages.mes
+++ b/src/bin/auth/auth_messages.mes
@@ -266,9 +266,16 @@ bug ticket for this issue.
This is a debug message issued when the authoritative server has received
a command on the command channel.
-% AUTH_RECEIVED_NOTIFY received incoming NOTIFY for zone name %1, zone class %2
+% AUTH_RECEIVED_NOTIFY received incoming NOTIFY for zone %1/%2 from %3
This is a debug message reporting that an incoming NOTIFY was received.
+% AUTH_RECEIVED_NOTIFY_NOTAUTH received bad NOTIFY for zone %1/%2 from %3
+The authoritative server received a NOTIFY message, but the specified zone
+doesn't match any of the zones served by the server. The server doesn't
+process the message further, and returns a response with the Rcode being
+NOTAUTH. Note: RFC 1996 does not specify the server behavior in this case;
+responding with Rcode of NOTAUTH follows BIND 9's behavior.
+
% AUTH_RESPONSE_FAILURE exception while building response to query: %1
This is a debug message, generated by the authoritative server when an
attempt to create a response to a received DNS packet has failed. The
@@ -367,12 +374,33 @@ XFRIN (Transfer-in) process. It is issued during server startup is an
indication that the initialization is proceeding normally.
% AUTH_ZONEMGR_COMMS error communicating with zone manager: %1
-This is a debug message output during the processing of a NOTIFY request.
+This is an internal error during the processing of a NOTIFY request.
An error (listed in the message) has been encountered whilst communicating
with the zone manager. The NOTIFY request will not be honored.
+This may be some temporary failure, but is generally an unexpected
+event and is quite likely a bug. It's probably worth filing a report.
% AUTH_ZONEMGR_ERROR received error response from zone manager: %1
-This is a debug message output during the processing of a NOTIFY
-request. The zone manager component has been informed of the request,
+The zone manager component has been informed of the request,
but has returned an error response (which is included in the message). The
-NOTIFY request will not be honored.
+NOTIFY request will not be honored. As of this writing, this can only
+happen due to a bug inside the Zonemgr implementation. Zonemgr itself
+may log more detailed cause of this, and these are probably worth
+filing a bug report.
+
+% AUTH_ZONEMGR_NOTEXIST received NOTIFY but Zonemgr does not exist
+This is a debug message produced by the authoritative server when it
+receives a NOTIFY message but the Zonemgr component is not running at
+that time. Not running Zonemgr is completely valid for, e.g., primary
+only servers, so this is not necessarily a problem. If this message
+is logged even if Zonemgr is supposed to be running, it's encouraged
+to check other logs to identify why that happens. It may or may not
+be a real problem (for example, if it's immediately after the system
+startup, it's possible that Auth has started up and is running but
+Zonemgr is not yet). Even if this is indeed an unexpected case,
+Zonemgr should normally be restarted by the Init process, so unless
+this repeats too often it may be negligible in practice (still it's
+worth filing a bug report). In any case, the authoritative server
+simply drops the NOTIFY message; if it's a temporary failure or
+delayed startup, subsequently resent messages will eventually reach
+Zonemgr.
diff --git a/src/bin/auth/auth_srv.cc b/src/bin/auth/auth_srv.cc
index 30875e5..90efee7 100644
--- a/src/bin/auth/auth_srv.cc
+++ b/src/bin/auth/auth_srv.cc
@@ -22,6 +22,7 @@
#include <config/ccsession.h>
#include <cc/data.h>
+#include <cc/proto_defs.h>
#include <exceptions/exceptions.h>
@@ -41,7 +42,7 @@
#include <asiodns/dns_service.h>
-#include <datasrc/data_source.h>
+#include <datasrc/exceptions.h>
#include <datasrc/client_list.h>
#include <xfr/xfrout_client.h>
@@ -747,6 +748,8 @@ AuthSrvImpl::processNotify(const IOMessage& io_message, Message& message,
std::auto_ptr<TSIGContext> tsig_context,
MessageAttributes& stats_attrs)
{
+ const IOEndpoint& remote_ep = io_message.getRemoteEndpoint(); // for logs
+
// The incoming notify must contain exactly one question for SOA of the
// zone name.
if (message.getRRCount(Message::SECTION_QUESTION) != 1) {
@@ -769,23 +772,34 @@ AuthSrvImpl::processNotify(const IOMessage& io_message, Message& message,
// on, but we don't check these conditions. This behavior is compatible
// with BIND 9.
- // TODO check with the conf-mgr whether current server is the auth of the
- // zone
-
- // In the code that follows, we simply ignore the notify if any internal
- // error happens rather than returning (e.g.) SERVFAIL. RFC 1996 is
- // silent about such cases, but there doesn't seem to be anything we can
- // improve at the primary server side by sending an error anyway.
- if (xfrin_session_ == NULL) {
- LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_NO_XFRIN);
- return (false);
+ // See if we have the specified zone in our data sources; if not return
+ // NOTAUTH, following BIND 9 (this is not specified in RFC 1996).
+ bool is_auth = false;
+ {
+ auth::DataSrcClientsMgr::Holder datasrc_holder(datasrc_clients_mgr_);
+ const shared_ptr<datasrc::ClientList> dsrc_clients =
+ datasrc_holder.findClientList(question->getClass());
+ is_auth = dsrc_clients &&
+ dsrc_clients->find(question->getName(), true, false).exact_match_;
+ }
+ if (!is_auth) {
+ LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_RECEIVED_NOTIFY_NOTAUTH)
+ .arg(question->getName()).arg(question->getClass()).arg(remote_ep);
+ makeErrorMessage(renderer_, message, buffer, Rcode::NOTAUTH(),
+ stats_attrs, tsig_context);
+ return (true);
}
LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_RECEIVED_NOTIFY)
- .arg(question->getName()).arg(question->getClass());
+ .arg(question->getName()).arg(question->getClass()).arg(remote_ep);
- const string remote_ip_address =
- io_message.getRemoteEndpoint().getAddress().toText();
+ // xfrin_session_ should have been set and never be replaced except in
+ // tests; otherwise it's an internal bug. assert() may be too strong,
+ // but processMessage() will catch all exceptions, so there's no better
+ // way.
+ assert(xfrin_session_);
+
+ const string remote_ip_address = remote_ep.getAddress().toText();
static const string command_template_start =
"{\"command\": [\"notify\", {\"zone_name\" : \"";
static const string command_template_master = "\", \"master\" : \"";
@@ -800,12 +814,24 @@ AuthSrvImpl::processNotify(const IOMessage& io_message, Message& message,
command_template_end);
const unsigned int seq =
xfrin_session_->group_sendmsg(notify_command, "Zonemgr",
- "*", "*");
+ CC_INSTANCE_WILDCARD,
+ CC_INSTANCE_WILDCARD, true);
ConstElementPtr env, answer, parsed_answer;
xfrin_session_->group_recvmsg(env, answer, false, seq);
int rcode;
parsed_answer = parseAnswer(rcode, answer);
- if (rcode != 0) {
+ if (rcode == CC_REPLY_NO_RECPT) {
+ // This can happen when Zonemgr is not running. When we support
+ // notification-based membership framework, we should check if it's
+ // supposed to be running and shouldn't even send the command if
+ // not. Until then, we log this event at the debug level as we
+ // don't know whether it's a real trouble or intentional
+ // configuration. (Also, when it's done, maybe we should simply
+ // propagate the exception and return SERVFAIL to suppress further
+ // NOTIFY).
+ LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_ZONEMGR_NOTEXIST);
+ return (false);
+ } else if (rcode != CC_REPLY_SUCCESS) {
LOG_ERROR(auth_logger, AUTH_ZONEMGR_ERROR)
.arg(parsed_answer->str());
return (false);
diff --git a/src/bin/auth/datasrc_clients_mgr.h b/src/bin/auth/datasrc_clients_mgr.h
index 5bbdb99..7b40e1f 100644
--- a/src/bin/auth/datasrc_clients_mgr.h
+++ b/src/bin/auth/datasrc_clients_mgr.h
@@ -25,7 +25,7 @@
#include <cc/data.h>
-#include <datasrc/data_source.h>
+#include <datasrc/exceptions.h>
#include <datasrc/client_list.h>
#include <datasrc/memory/zone_writer.h>
@@ -639,12 +639,21 @@ DataSrcClientsBuilderBase<MutexType, CondVarType>::getZoneWriter(
AUTH_DATASRC_CLIENTS_BUILDER_LOAD_ZONE_NOCACHE)
.arg(origin).arg(rrclass);
break; // return NULL below
+ case datasrc::ConfigurableClientList::CACHE_NOT_WRITABLE:
+ // This is an internal error. Auth server should skip reloading zones
+ // on non writable caches.
+ isc_throw(InternalCommandError, "failed to load zone " << origin
+ << "/" << rrclass << ": internal failure, in-memory cache "
+ "is not writable");
case datasrc::ConfigurableClientList::CACHE_DISABLED:
// This is an internal error. Auth server must have the cache
// enabled.
isc_throw(InternalCommandError, "failed to load zone " << origin
<< "/" << rrclass << ": internal failure, in-memory cache "
"is somehow disabled");
+ default: // other cases can really never happen
+ isc_throw(Unexpected, "Impossible result in getting data source "
+ "ZoneWriter: " << writerpair.first);
}
return (boost::shared_ptr<datasrc::memory::ZoneWriter>());
diff --git a/src/bin/auth/query.h b/src/bin/auth/query.h
index 5af2361..2c08312 100644
--- a/src/bin/auth/query.h
+++ b/src/bin/auth/query.h
@@ -329,8 +329,8 @@ public:
/// \short Bad zone data encountered.
///
- /// This is thrown when process encounteres misconfigured zone in a way
- /// it can't continue. This throws, not sets the Rcode, because such
+ /// This is thrown when a process encounters a misconfigured zone in a
+ /// way it can't continue. This throws, not sets the Rcode, because such
/// misconfigured zone should not be present in the data source and
/// should have been rejected sooner.
struct BadZone : public isc::Exception {
diff --git a/src/bin/auth/tests/auth_srv_unittest.cc b/src/bin/auth/tests/auth_srv_unittest.cc
index 0a77a11..d63dc63 100644
--- a/src/bin/auth/tests/auth_srv_unittest.cc
+++ b/src/bin/auth/tests/auth_srv_unittest.cc
@@ -26,6 +26,8 @@
#include <dns/rdataclass.h>
#include <dns/tsig.h>
+#include <cc/proto_defs.h>
+
#include <server_common/portconfig.h>
#include <server_common/keyring.h>
@@ -76,7 +78,6 @@ using namespace isc::asiolink;
using namespace isc::testutils;
using namespace isc::server_common::portconfig;
using namespace isc::auth::unittest;
-using isc::datasrc::memory::ZoneTableSegment;
using isc::UnitTestUtil;
using boost::scoped_ptr;
using isc::auth::statistics::Counters;
@@ -244,6 +245,60 @@ createBuiltinVersionResponse(const qid_t qid, vector<uint8_t>& data) {
renderer.getLength());
}
+void
+installDataSrcClientLists(AuthSrv& server, ClientListMapPtr lists) {
+ // For now, we use explicit swap than reconfigure() because the latter
+ // involves a separate thread and cannot guarantee the new config is
+ // available for the subsequent test.
+ server.getDataSrcClientsMgr().setDataSrcClientLists(lists);
+}
+
+void
+updateDatabase(AuthSrv& server, const char* params) {
+ const ConstElementPtr config(Element::fromJSON("{"
+ "\"IN\": [{"
+ " \"type\": \"sqlite3\","
+ " \"params\": " + string(params) +
+ "}]}"));
+ installDataSrcClientLists(server, configureDataSource(config));
+}
+
+void
+updateInMemory(AuthSrv& server, const char* origin, const char* filename,
+ bool with_static = true)
+{
+ string spec_txt = "{"
+ "\"IN\": [{"
+ " \"type\": \"MasterFiles\","
+ " \"params\": {"
+ " \"" + string(origin) + "\": \"" + string(filename) + "\""
+ " },"
+ " \"cache-enable\": true"
+ "}]";
+ if (with_static) {
+ spec_txt += ", \"CH\": [{"
+ " \"type\": \"MasterFiles\","
+ " \"cache-enable\": true,"
+ " \"params\": {\"BIND\": \"" + string(STATIC_DSRC_FILE) + "\"}"
+ "}]";
+ }
+ spec_txt += "}";
+
+ const ConstElementPtr config(Element::fromJSON(spec_txt));
+ installDataSrcClientLists(server, configureDataSource(config));
+}
+
+void
+updateBuiltin(AuthSrv& server) {
+ const ConstElementPtr config(Element::fromJSON("{"
+ "\"CH\": [{"
+ " \"type\": \"MasterFiles\","
+ " \"cache-enable\": true,"
+ " \"params\": {\"BIND\": \"" + string(STATIC_DSRC_FILE) + "\"}"
+ "}]}"));
+ installDataSrcClientLists(server, configureDataSource(config));
+}
+
// We did not configure any client lists. Therefore it should be REFUSED
TEST_F(AuthSrvTest, noClientList) {
UnitTestUtil::createRequestMessage(request_message, Opcode::QUERY(),
@@ -647,8 +702,10 @@ TEST_F(AuthSrvTest, IXFRDisconnectFail) {
}
TEST_F(AuthSrvTest, notify) {
+ updateInMemory(server, "example.", CONFIG_INMEMORY_EXAMPLE, false);
+
UnitTestUtil::createRequestMessage(request_message, Opcode::NOTIFY(),
- default_qid, Name("example.com"),
+ default_qid, Name("example"),
RRClass::IN(), RRType::SOA());
request_message.setHeaderFlag(Message::HEADERFLAG_AA);
createRequestPacket(request_message, IPPROTO_UDP);
@@ -664,7 +721,7 @@ TEST_F(AuthSrvTest, notify) {
stringValue());
ConstElementPtr notify_args =
notify_session.getSentMessage()->get("command")->get(1);
- EXPECT_EQ("example.com.", notify_args->get("zone_name")->stringValue());
+ EXPECT_EQ("example.", notify_args->get("zone_name")->stringValue());
EXPECT_EQ(DEFAULT_REMOTE_ADDRESS,
notify_args->get("master")->stringValue());
EXPECT_EQ("IN", notify_args->get("zone_class")->stringValue());
@@ -675,7 +732,7 @@ TEST_F(AuthSrvTest, notify) {
// The question must be identical to that of the received notify
ConstQuestionPtr question = *parse_message->beginQuestion();
- EXPECT_EQ(Name("example.com"), question->getName());
+ EXPECT_EQ(Name("example"), question->getName());
EXPECT_EQ(RRClass::IN(), question->getClass());
EXPECT_EQ(RRType::SOA(), question->getType());
@@ -691,9 +748,12 @@ TEST_F(AuthSrvTest, notify) {
}
TEST_F(AuthSrvTest, notifyForCHClass) {
- // Same as the previous test, but for the CH RRClass.
+ // Same as the previous test, but for the CH RRClass (so we install the
+ // builtin (static) data source.
+ updateBuiltin(server);
+
UnitTestUtil::createRequestMessage(request_message, Opcode::NOTIFY(),
- default_qid, Name("example.com"),
+ default_qid, Name("bind"),
RRClass::CH(), RRType::SOA());
request_message.setHeaderFlag(Message::HEADERFLAG_AA);
createRequestPacket(request_message, IPPROTO_UDP);
@@ -773,9 +833,11 @@ TEST_F(AuthSrvTest, notifyNonSOAQuestion) {
}
TEST_F(AuthSrvTest, notifyWithoutAA) {
+ updateInMemory(server, "example.", CONFIG_INMEMORY_EXAMPLE, false);
+
// implicitly leave the AA bit off. our implementation will accept it.
UnitTestUtil::createRequestMessage(request_message, Opcode::NOTIFY(),
- default_qid, Name("example.com"),
+ default_qid, Name("example"),
RRClass::IN(), RRType::SOA());
createRequestPacket(request_message, IPPROTO_UDP);
server.processMessage(*io_message, *parse_message, *response_obuffer,
@@ -786,8 +848,10 @@ TEST_F(AuthSrvTest, notifyWithoutAA) {
}
TEST_F(AuthSrvTest, notifyWithErrorRcode) {
+ updateInMemory(server, "example.", CONFIG_INMEMORY_EXAMPLE, false);
+
UnitTestUtil::createRequestMessage(request_message, Opcode::NOTIFY(),
- default_qid, Name("example.com"),
+ default_qid, Name("example"),
RRClass::IN(), RRType::SOA());
request_message.setHeaderFlag(Message::HEADERFLAG_AA);
request_message.setRcode(Rcode::SERVFAIL());
@@ -799,11 +863,15 @@ TEST_F(AuthSrvTest, notifyWithErrorRcode) {
Opcode::NOTIFY().getCode(), QR_FLAG | AA_FLAG, 1, 0, 0, 0);
}
-TEST_F(AuthSrvTest, notifyWithoutSession) {
- server.setXfrinSession(NULL);
+TEST_F(AuthSrvTest, notifyWithoutRecipient) {
+ updateInMemory(server, "example.", CONFIG_INMEMORY_EXAMPLE, false);
+
+ // Emulate the case where msgq tells auth there's no Zonemgr module.
+ notify_session.setMessage(isc::config::createAnswer(CC_REPLY_NO_RECPT,
+ "no recipient"));
UnitTestUtil::createRequestMessage(request_message, Opcode::NOTIFY(),
- default_qid, Name("example.com"),
+ default_qid, Name("example"),
RRClass::IN(), RRType::SOA());
request_message.setHeaderFlag(Message::HEADERFLAG_AA);
createRequestPacket(request_message, IPPROTO_UDP);
@@ -812,14 +880,19 @@ TEST_F(AuthSrvTest, notifyWithoutSession) {
// happens.
server.processMessage(*io_message, *parse_message, *response_obuffer,
&dnsserv);
+ // want_answer should have been set to true so auth can catch it if zonemgr
+ // is not running.
+ EXPECT_TRUE(notify_session.wasAnswerWanted());
EXPECT_FALSE(dnsserv.hasAnswer());
}
TEST_F(AuthSrvTest, notifySendFail) {
+ updateInMemory(server, "example.", CONFIG_INMEMORY_EXAMPLE, false);
+
notify_session.disableSend();
UnitTestUtil::createRequestMessage(request_message, Opcode::NOTIFY(),
- default_qid, Name("example.com"),
+ default_qid, Name("example"),
RRClass::IN(), RRType::SOA());
request_message.setHeaderFlag(Message::HEADERFLAG_AA);
createRequestPacket(request_message, IPPROTO_UDP);
@@ -830,10 +903,12 @@ TEST_F(AuthSrvTest, notifySendFail) {
}
TEST_F(AuthSrvTest, notifyReceiveFail) {
+ updateInMemory(server, "example.", CONFIG_INMEMORY_EXAMPLE, false);
+
notify_session.disableReceive();
UnitTestUtil::createRequestMessage(request_message, Opcode::NOTIFY(),
- default_qid, Name("example.com"),
+ default_qid, Name("example"),
RRClass::IN(), RRType::SOA());
request_message.setHeaderFlag(Message::HEADERFLAG_AA);
createRequestPacket(request_message, IPPROTO_UDP);
@@ -843,10 +918,12 @@ TEST_F(AuthSrvTest, notifyReceiveFail) {
}
TEST_F(AuthSrvTest, notifyWithBogusSessionMessage) {
+ updateInMemory(server, "example.", CONFIG_INMEMORY_EXAMPLE, false);
+
notify_session.setMessage(Element::fromJSON("{\"foo\": 1}"));
UnitTestUtil::createRequestMessage(request_message, Opcode::NOTIFY(),
- default_qid, Name("example.com"),
+ default_qid, Name("example"),
RRClass::IN(), RRType::SOA());
request_message.setHeaderFlag(Message::HEADERFLAG_AA);
createRequestPacket(request_message, IPPROTO_UDP);
@@ -856,11 +933,13 @@ TEST_F(AuthSrvTest, notifyWithBogusSessionMessage) {
}
TEST_F(AuthSrvTest, notifyWithSessionMessageError) {
+ updateInMemory(server, "example.", CONFIG_INMEMORY_EXAMPLE, false);
+
notify_session.setMessage(
Element::fromJSON("{\"result\": [1, \"FAIL\"]}"));
UnitTestUtil::createRequestMessage(request_message, Opcode::NOTIFY(),
- default_qid, Name("example.com"),
+ default_qid, Name("example"),
RRClass::IN(), RRType::SOA());
request_message.setHeaderFlag(Message::HEADERFLAG_AA);
createRequestPacket(request_message, IPPROTO_UDP);
@@ -869,58 +948,55 @@ TEST_F(AuthSrvTest, notifyWithSessionMessageError) {
EXPECT_FALSE(dnsserv.hasAnswer());
}
-void
-installDataSrcClientLists(AuthSrv& server, ClientListMapPtr lists) {
- // For now, we use explicit swap than reconfigure() because the latter
- // involves a separate thread and cannot guarantee the new config is
- // available for the subsequent test.
- server.getDataSrcClientsMgr().setDataSrcClientLists(lists);
-}
+TEST_F(AuthSrvTest, notifyNotAuth) {
+ // If the server doesn't have authority of the specified zone in NOTIFY,
+ // it will return NOTAUTH
+ updateInMemory(server, "example.", CONFIG_INMEMORY_EXAMPLE, false);
-void
-updateDatabase(AuthSrv& server, const char* params) {
- const ConstElementPtr config(Element::fromJSON("{"
- "\"IN\": [{"
- " \"type\": \"sqlite3\","
- " \"params\": " + string(params) +
- "}]}"));
- installDataSrcClientLists(server, configureDataSource(config));
+ UnitTestUtil::createRequestMessage(request_message, Opcode::NOTIFY(),
+ default_qid, Name("example.com"),
+ RRClass::IN(), RRType::SOA());
+ request_message.setHeaderFlag(Message::HEADERFLAG_AA);
+ createRequestPacket(request_message, IPPROTO_UDP);
+ server.processMessage(*io_message, *parse_message, *response_obuffer,
+ &dnsserv);
+ EXPECT_TRUE(dnsserv.hasAnswer());
+ headerCheck(*parse_message, default_qid, Rcode::NOTAUTH(),
+ Opcode::NOTIFY().getCode(), QR_FLAG /* no AA */, 1, 0, 0, 0);
}
-void
-updateInMemory(AuthSrv& server, const char* origin, const char* filename) {
- const ConstElementPtr config(Element::fromJSON("{"
- "\"IN\": [{"
- " \"type\": \"MasterFiles\","
- " \"params\": {"
- " \"" + string(origin) + "\": \"" + string(filename) + "\""
- " },"
- " \"cache-enable\": true"
- "}],"
- "\"CH\": [{"
- " \"type\": \"static\","
- " \"params\": \"" + string(STATIC_DSRC_FILE) + "\""
- "}]}"));
- installDataSrcClientLists(server, configureDataSource(config));
+TEST_F(AuthSrvTest, notifyNotAuthSubDomain) {
+ // Similar to the previous case, but checking partial match doesn't confuse
+ // the processing.
+ updateInMemory(server, "example.", CONFIG_INMEMORY_EXAMPLE, false);
+
+ UnitTestUtil::createRequestMessage(request_message, Opcode::NOTIFY(),
+ default_qid, Name("child.example"),
+ RRClass::IN(), RRType::SOA());
+ createRequestPacket(request_message, IPPROTO_UDP);
+ server.processMessage(*io_message, *parse_message, *response_obuffer,
+ &dnsserv);
+ headerCheck(*parse_message, default_qid, Rcode::NOTAUTH(),
+ Opcode::NOTIFY().getCode(), QR_FLAG, 1, 0, 0, 0);
}
-void
-updateBuiltin(AuthSrv& server) {
- const ConstElementPtr config(Element::fromJSON("{"
- "\"CH\": [{"
- " \"type\": \"static\","
- " \"params\": \"" + string(STATIC_DSRC_FILE) + "\""
- "}]}"));
- installDataSrcClientLists(server, configureDataSource(config));
+TEST_F(AuthSrvTest, notifyNotAuthNoClass) {
+ // Likewise, and there's not even a data source in the specified class.
+ updateInMemory(server, "example.", CONFIG_INMEMORY_EXAMPLE, false);
+
+ UnitTestUtil::createRequestMessage(request_message, Opcode::NOTIFY(),
+ default_qid, Name("example"),
+ RRClass::CH(), RRType::SOA());
+ createRequestPacket(request_message, IPPROTO_UDP);
+ server.processMessage(*io_message, *parse_message, *response_obuffer,
+ &dnsserv);
+ headerCheck(*parse_message, default_qid, Rcode::NOTAUTH(),
+ Opcode::NOTIFY().getCode(), QR_FLAG, 1, 0, 0, 0);
}
// Try giving the server a TSIG signed request and see it can anwer signed as
// well
-#ifdef USE_STATIC_LINK
-TEST_F(AuthSrvTest, DISABLED_TSIGSigned) { // Needs builtin
-#else
TEST_F(AuthSrvTest, TSIGSigned) {
-#endif
// Prepare key, the client message, etc
updateBuiltin(server);
const TSIGKey key("key:c2VjcmV0Cg==:hmac-sha1");
@@ -978,11 +1054,7 @@ TEST_F(AuthSrvTest, TSIGSigned) {
// authoritative only server in terms of performance, and it's quite likely
// we need to drop it for the authoritative server implementation.
// At that point we can drop this test, too.
-#ifdef USE_STATIC_LINK
-TEST_F(AuthSrvTest, DISABLED_builtInQueryViaDNSServer) {
-#else
TEST_F(AuthSrvTest, builtInQueryViaDNSServer) {
-#endif
updateBuiltin(server);
UnitTestUtil::createRequestMessage(request_message, Opcode::QUERY(),
default_qid, Name("VERSION.BIND."),
@@ -1010,11 +1082,7 @@ TEST_F(AuthSrvTest, builtInQueryViaDNSServer) {
// The most primitive check: checking the result of the processMessage()
// method
-#ifdef USE_STATIC_LINK
-TEST_F(AuthSrvTest, DISABLED_builtInQuery) {
-#else
TEST_F(AuthSrvTest, builtInQuery) {
-#endif
updateBuiltin(server);
UnitTestUtil::createRequestMessage(request_message, Opcode::QUERY(),
default_qid, Name("VERSION.BIND."),
@@ -1031,11 +1099,7 @@ TEST_F(AuthSrvTest, builtInQuery) {
}
// Same type of test as builtInQueryViaDNSServer but for an error response.
-#ifdef USE_STATIC_LINK
-TEST_F(AuthSrvTest, DISABLED_iqueryViaDNSServer) { // Needs builtin
-#else
-TEST_F(AuthSrvTest, iqueryViaDNSServer) { // Needs builtin
-#endif
+TEST_F(AuthSrvTest, iqueryViaDNSServer) {
updateBuiltin(server);
createDataFromFile("iquery_fromWire.wire");
(*server.getDNSLookupProvider())(*io_message, parse_message,
@@ -1146,11 +1210,7 @@ TEST_F(AuthSrvTest, updateWithInMemoryClient) {
opcode.getCode(), QR_FLAG, 1, 0, 0, 0);
}
-#ifdef USE_STATIC_LINK
-TEST_F(AuthSrvTest, DISABLED_queryWithInMemoryClientNoDNSSEC) {
-#else
TEST_F(AuthSrvTest, queryWithInMemoryClientNoDNSSEC) {
-#endif
// In this example, we do simple check that query is handled from the
// query handler class, and confirm it returns no error and a non empty
// answer section. Detailed examination on the response content
@@ -1166,11 +1226,7 @@ TEST_F(AuthSrvTest, queryWithInMemoryClientNoDNSSEC) {
opcode.getCode(), QR_FLAG | AA_FLAG, 1, 1, 2, 1);
}
-#ifdef USE_STATIC_LINK
-TEST_F(AuthSrvTest, DISABLED_queryWithInMemoryClientDNSSEC) {
-#else
TEST_F(AuthSrvTest, queryWithInMemoryClientDNSSEC) {
-#endif
// Similar to the previous test, but the query has the DO bit on.
// The response should contain RRSIGs, and should have more RRs than
// the previous case.
@@ -1185,14 +1241,7 @@ TEST_F(AuthSrvTest, queryWithInMemoryClientDNSSEC) {
opcode.getCode(), QR_FLAG | AA_FLAG, 1, 2, 3, 3);
}
-TEST_F(AuthSrvTest,
-#ifdef USE_STATIC_LINK
- DISABLED_chQueryWithInMemoryClient
-#else
- chQueryWithInMemoryClient
-#endif
- )
-{
+TEST_F(AuthSrvTest, chQueryWithInMemoryClient) {
// Set up the in-memory
updateInMemory(server, "example.", CONFIG_INMEMORY_EXAMPLE);
@@ -1665,9 +1714,7 @@ public:
real_list, ThrowWhen throw_when, bool isc_exception,
ConstRRsetPtr fake_rrset = ConstRRsetPtr()) :
ConfigurableClientList(RRClass::IN()),
- real_(real_list),
- config_(Element::fromJSON("{}")),
- ztable_segment_(ZoneTableSegment::create(*config_, RRClass::IN()))
+ real_(real_list)
{
BOOST_FOREACH(const DataSourceInfo& info, real_->getDataSources()) {
const isc::datasrc::DataSourceClientPtr
@@ -1679,13 +1726,13 @@ public:
data_sources_.push_back(
DataSourceInfo(client.get(),
isc::datasrc::DataSourceClientContainerPtr(),
- false, RRClass::IN(), ztable_segment_));
+ boost::shared_ptr<
+ isc::datasrc::internal::CacheConfig>(),
+ RRClass::IN(), ""));
}
}
private:
const boost::shared_ptr<isc::datasrc::ConfigurableClientList> real_;
- const ConstElementPtr config_;
- boost::shared_ptr<ZoneTableSegment> ztable_segment_;
vector<isc::datasrc::DataSourceClientPtr> clients_;
};
@@ -1695,14 +1742,7 @@ private:
//
// Set the proxies to never throw, this should have the same result as
// queryWithInMemoryClientNoDNSSEC, and serves to test the two proxy classes
-TEST_F(AuthSrvTest,
-#ifdef USE_STATIC_LINK
- DISABLED_queryWithInMemoryClientProxy
-#else
- queryWithInMemoryClientProxy
-#endif
- )
-{
+TEST_F(AuthSrvTest, queryWithInMemoryClientProxy) {
// Set real inmem client to proxy
updateInMemory(server, "example.", CONFIG_INMEMORY_EXAMPLE);
boost::shared_ptr<isc::datasrc::ConfigurableClientList> list;
@@ -1749,14 +1789,7 @@ setupThrow(AuthSrv& server, ThrowWhen throw_when, bool isc_exception,
mgr.setDataSrcClientLists(lists);
}
-TEST_F(AuthSrvTest,
-#ifdef USE_STATIC_LINK
- DISABLED_queryWithThrowingProxyServfails
-#else
- queryWithThrowingProxyServfails
-#endif
- )
-{
+TEST_F(AuthSrvTest, queryWithThrowingProxyServfails) {
// Test the common cases, all of which should simply return SERVFAIL
// Use THROW_NEVER as end marker
ThrowWhen throws[] = { THROW_AT_FIND_ZONE,
@@ -1780,14 +1813,7 @@ TEST_F(AuthSrvTest,
// Throw isc::Exception in getClass(). (Currently?) getClass is not called
// in the processMessage path, so this should result in a normal answer
-TEST_F(AuthSrvTest,
-#ifdef USE_STATIC_LINK
- DISABLED_queryWithInMemoryClientProxyGetClass
-#else
- queryWithInMemoryClientProxyGetClass
-#endif
- )
-{
+TEST_F(AuthSrvTest, queryWithInMemoryClientProxyGetClass) {
createDataFromFile("nsec3query_nodnssec_fromWire.wire");
setupThrow(server, THROW_AT_GET_CLASS, true);
@@ -1800,14 +1826,7 @@ TEST_F(AuthSrvTest,
opcode.getCode(), QR_FLAG | AA_FLAG, 1, 1, 2, 1);
}
-TEST_F(AuthSrvTest,
-#ifdef USE_STATIC_LINK
- DISABLED_queryWithThrowingInToWire
-#else
- queryWithThrowingInToWire
-#endif
- )
-{
+TEST_F(AuthSrvTest, queryWithThrowingInToWire) {
// Set up a faked data source. It will return an empty RRset for the
// query.
ConstRRsetPtr empty_rrset(new RRset(Name("foo.example"),
diff --git a/src/bin/auth/tests/config_unittest.cc b/src/bin/auth/tests/config_unittest.cc
index 0d6cbf8..65b6539 100644
--- a/src/bin/auth/tests/config_unittest.cc
+++ b/src/bin/auth/tests/config_unittest.cc
@@ -21,7 +21,7 @@
#include <cc/data.h>
-#include <datasrc/data_source.h>
+#include <datasrc/exceptions.h>
#include <xfr/xfrout_client.h>
diff --git a/src/bin/auth/tests/datasrc_clients_builder_unittest.cc b/src/bin/auth/tests/datasrc_clients_builder_unittest.cc
index 01ac47e..5a24b30 100644
--- a/src/bin/auth/tests/datasrc_clients_builder_unittest.cc
+++ b/src/bin/auth/tests/datasrc_clients_builder_unittest.cc
@@ -406,6 +406,22 @@ TEST_F(DataSrcClientsBuilderTest,
EXPECT_EQ(orig_lock_count + 1, map_mutex.lock_count);
EXPECT_EQ(orig_unlock_count + 1, map_mutex.unlock_count);
+ // zone doesn't exist in the data source
+ const ConstElementPtr config_nozone(Element::fromJSON("{"
+ "\"IN\": [{"
+ " \"type\": \"sqlite3\","
+ " \"params\": {\"database_file\": \"" + test_db + "\"},"
+ " \"cache-enable\": true,"
+ " \"cache-zones\": [\"nosuchzone.example\"]"
+ "}]}"));
+ clients_map = configureDataSource(config_nozone);
+ EXPECT_THROW(
+ builder.handleCommand(
+ Command(LOADZONE, Element::fromJSON(
+ "{\"class\": \"IN\","
+ " \"origin\": \"nosuchzone.example\"}"))),
+ TestDataSrcClientsBuilder::InternalCommandError);
+
// basically impossible case: in-memory cache is completely disabled.
// In this implementation of manager-builder, this should never happen,
// but it catches it like other configuration error and keeps going.
@@ -503,14 +519,6 @@ TEST_F(DataSrcClientsBuilderTest, loadZoneInvalidParams) {
}, "");
}
- // zone doesn't exist in the data source
- EXPECT_THROW(
- builder.handleCommand(
- Command(LOADZONE,
- Element::fromJSON(
- "{\"class\": \"IN\", \"origin\": \"xx\"}"))),
- TestDataSrcClientsBuilder::InternalCommandError);
-
// origin is bogus
EXPECT_THROW(builder.handleCommand(
Command(LOADZONE,
@@ -524,4 +532,35 @@ TEST_F(DataSrcClientsBuilderTest, loadZoneInvalidParams) {
isc::data::TypeError);
}
+// This works only if mapped memory segment is compiled.
+// Note also that this test case may fail as we make b10-auth more aware
+// of shared-memory cache.
+TEST_F(DataSrcClientsBuilderTest,
+#ifdef USE_SHARED_MEMORY
+ loadInNonWritableCache
+#else
+ DISABLED_loadInNonWritableCache
+#endif
+ )
+{
+ const ConstElementPtr config = Element::fromJSON(
+ "{"
+ "\"IN\": [{"
+ " \"type\": \"MasterFiles\","
+ " \"params\": {"
+ " \"test1.example\": \"" +
+ std::string(TEST_DATA_BUILDDIR "/test1.zone.copied") + "\"},"
+ " \"cache-enable\": true,"
+ " \"cache-type\": \"mapped\""
+ "}]}");
+ clients_map = configureDataSource(config);
+
+ EXPECT_THROW(builder.handleCommand(
+ Command(LOADZONE,
+ Element::fromJSON(
+ "{\"origin\": \"test1.example\","
+ " \"class\": \"IN\"}"))),
+ TestDataSrcClientsBuilder::InternalCommandError);
+}
+
} // unnamed namespace
diff --git a/src/bin/bind10/init.py.in b/src/bin/bind10/init.py.in
index f47de31..efc0b04 100755
--- a/src/bin/bind10/init.py.in
+++ b/src/bin/bind10/init.py.in
@@ -38,6 +38,7 @@ __main__.
import sys; sys.path.append ('@@PYTHONPATH@@')
import os
+from isc.util.address_formatter import AddressFormatter
# If B10_FROM_SOURCE is set in the environment, we use data files
# from a directory relative to that, otherwise we use the ones
@@ -151,7 +152,7 @@ class ProcessInfo:
"""Function used before running a program that needs to run as a
different user."""
# First, put us into a separate process group so we don't get
- # SIGINT signals on Ctrl-C (b10-init will shut everthing down by
+ # SIGINT signals on Ctrl-C (b10-init will shut everything down by
# other means).
os.setpgrp()
@@ -222,7 +223,7 @@ 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 inapropriate. But as the code isn't probably
+ # 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
@@ -428,7 +429,7 @@ class Init:
port)
else:
logger.info(BIND10_STARTING_PROCESS_PORT_ADDRESS,
- self.curproc, address, port)
+ self.curproc, AddressFormatter((address, port)))
def log_started(self, pid = None):
"""
diff --git a/src/bin/bind10/init_messages.mes b/src/bin/bind10/init_messages.mes
index e4e354c..267790d 100644
--- a/src/bin/bind10/init_messages.mes
+++ b/src/bin/bind10/init_messages.mes
@@ -285,9 +285,9 @@ The b10-init module is starting the given process.
The b10-init module is starting the given process, which will listen on the
given port number.
-% BIND10_STARTING_PROCESS_PORT_ADDRESS starting process %1 (to listen on %2#%3)
+% BIND10_STARTING_PROCESS_PORT_ADDRESS starting process %1 (to listen on %2)
The b10-init module is starting the given process, which will listen on the
-given address and port number (written as <address>#<port>).
+given address and port number (written as <address>:<port>).
% BIND10_STARTUP_COMPLETE BIND 10 started
All modules have been successfully started, and BIND 10 is now running.
diff --git a/src/bin/bind10/run_bind10.sh.in b/src/bin/bind10/run_bind10.sh.in
index bb00e23..a22f300 100755
--- a/src/bin/bind10/run_bind10.sh.in
+++ b/src/bin/bind10/run_bind10.sh.in
@@ -30,7 +30,7 @@ export PYTHONPATH
# required by loadable python modules.
SET_ENV_LIBRARY_PATH=@SET_ENV_LIBRARY_PATH@
if test $SET_ENV_LIBRARY_PATH = yes; then
- @ENV_LIBRARY_PATH@=@abs_top_builddir@/src/lib/dns/.libs:@abs_top_builddir@/src/lib/dns/python/.libs:@abs_top_builddir@/src/lib/cryptolink/.libs:@abs_top_builddir@/src/lib/cc/.libs:@abs_top_builddir@/src/lib/config/.libs:@abs_top_builddir@/src/lib/log/.libs:@abs_top_builddir@/src/lib/acl/.libs:@abs_top_builddir@/src/lib/util/.libs:@abs_top_builddir@/src/lib/util/io/.libs:@abs_top_builddir@/src/lib/exceptions/.libs:@abs_top_builddir@/src/lib/datasrc/.libs:$@ENV_LIBRARY_PATH@
+ @ENV_LIBRARY_PATH@=@abs_top_builddir@/src/lib/dns/.libs:@abs_top_builddir@/src/lib/dns/python/.libs:@abs_top_builddir@/src/lib/cryptolink/.libs:@abs_top_builddir@/src/lib/cc/.libs:@abs_top_builddir@/src/lib/config/.libs:@abs_top_builddir@/src/lib/log/.libs:@abs_top_builddir@/src/lib/acl/.libs:@abs_top_builddir@/src/lib/util/.libs:@abs_top_builddir@/src/lib/util/threads/.libs:@abs_top_builddir@/src/lib/util/io/.libs:@abs_top_builddir@/src/lib/exceptions/.libs:@abs_top_builddir@/src/lib/datasrc/.libs:$@ENV_LIBRARY_PATH@
export @ENV_LIBRARY_PATH@
fi
diff --git a/src/bin/bind10/tests/Makefile.am b/src/bin/bind10/tests/Makefile.am
index 6d59dbd..d7e51d5 100644
--- a/src/bin/bind10/tests/Makefile.am
+++ b/src/bin/bind10/tests/Makefile.am
@@ -8,7 +8,7 @@ noinst_SCRIPTS = $(PYTESTS)
# required by loadable python modules.
LIBRARY_PATH_PLACEHOLDER =
if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/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/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/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:$$$(ENV_LIBRARY_PATH)
endif
# test using command-line arguments, so use check-local target instead of TESTS
diff --git a/src/bin/bind10/tests/init_test.py.in b/src/bin/bind10/tests/init_test.py.in
index 9a591ef..8ac6458 100644
--- a/src/bin/bind10/tests/init_test.py.in
+++ b/src/bin/bind10/tests/init_test.py.in
@@ -1595,7 +1595,7 @@ class TestInitComponents(unittest.TestCase):
init.components[53] = component
# case where the returned pid is unknown to us. nothing should
- # happpen then.
+ # happen then.
init.get_process_exit_status_called = False
init._get_process_exit_status = init._get_process_exit_status_unknown_pid
init.components_to_restart = []
@@ -2339,6 +2339,16 @@ class SocketSrvTest(unittest.TestCase):
self.assertEqual({}, self.__b10_init._unix_sockets)
self.assertTrue(sock.closed)
+ def test_log_starting(self):
+ """
+ Checks the log_starting call doesn't raise any errors
+ (does not check actual log output)
+ """
+ self.__b10_init.log_starting("foo")
+ self.__b10_init.log_starting("foo", 1)
+ self.__b10_init.log_starting("foo", 1, "192.0.2.1")
+
+
class TestFunctions(unittest.TestCase):
def setUp(self):
self.lockfile_testpath = \
diff --git a/src/bin/bindctl/bindcmd.py b/src/bin/bindctl/bindcmd.py
index f382e2a..03b5d6b 100644
--- a/src/bin/bindctl/bindcmd.py
+++ b/src/bin/bindctl/bindcmd.py
@@ -33,6 +33,7 @@ import inspect
import pprint
import ssl, socket
import os, time, random, re
+import os.path
import getpass
from hashlib import sha1
import csv
@@ -147,9 +148,9 @@ class BindCmdInterpreter(Cmd):
# is processed by a script that expects a specific format.
if my_readline == sys.stdin.readline and sys.stdin.isatty():
sys.stdout.write("""\
-WARNING: Python readline module isn't available, so the command line editor
- (including command history management) does not work. See BIND 10
- guide for more details.\n\n""")
+WARNING: The Python readline module isn't available, so some command line
+ editing features (including command history management) will not
+ work. See the BIND 10 guide for more details.\n\n""")
try:
if not self.login_to_cmdctl():
@@ -214,22 +215,16 @@ WARNING: Python readline module isn't available, so the command line editor
return True
- def __print_check_ssl_msg(self):
- self._print("Please check the logs of b10-cmdctl, there may "
- "be a problem accepting SSL connections, such "
- "as a permission problem on the server "
- "certificate file.")
-
def _try_login(self, username, password):
'''
- Attempts to log in to cmdctl by sending a POST with
- the given username and password.
- On success of the POST (mind, not the login, only the network
- operation), returns a tuple (response, data).
- On failure, raises a FailToLogin exception, and prints some
- information on the failure.
- This call is essentially 'private', but made 'protected' for
- easier testing.
+ Attempts to log into cmdctl by sending a POST with the given
+ username and password. On success of the POST (not the login,
+ but the network operation), it returns a tuple (response, data).
+ We check for some failures such as SSL errors and socket errors
+ which could happen due to the environment in which BIND 10 runs.
+ On failure, it raises a FailToLogin exception and prints some
+ information on the failure. This call is essentially 'private',
+ but made 'protected' for easier testing.
'''
param = {'username': username, 'password' : password}
try:
@@ -237,16 +232,8 @@ WARNING: Python readline module isn't available, so the command line editor
data = response.read().decode()
# return here (will raise error after try block)
return (response, data)
- except ssl.SSLError as err:
- self._print("SSL error while sending login information: ", err)
- if err.errno == ssl.SSL_ERROR_EOF:
- self.__print_check_ssl_msg()
- except socket.error as err:
- self._print("Socket error while sending login information: ", err)
- # An SSL setup error can also bubble up as a plain CONNRESET...
- # (on some systems it usually does)
- if err.errno == errno.ECONNRESET:
- self.__print_check_ssl_msg()
+ except (ssl.SSLError, socket.error) as err:
+ self._print('Error while sending login information:', err)
pass
raise FailToLogin()
@@ -270,9 +257,21 @@ WARNING: Python readline module isn't available, so the command line editor
# No valid logins were found, prompt the user for a username/password
count = 0
- self._print('No stored password file found, please see sections '
- '"Configuration specification for b10-cmdctl" and "bindctl '
- 'command-line options" of the BIND 10 guide.')
+ if not os.path.exists(self.csv_file_dir + CSV_FILE_NAME):
+ self._print('\nNo stored password file found.\n\n'
+ 'When the system is first set up you need to create '
+ 'at least one user account.\n'
+ 'For information on how to set up a BIND 10 system, '
+ 'please check see the\n'
+ 'BIND 10 Guide: \n\n'
+ 'http://bind10.isc.org/docs/bind10-guide.html#quick-start-auth-dns\n\n'
+
+ 'If a user account has been set up, please check the '
+ 'b10-cmdctl log for other\n'
+ 'information.\n')
+ else:
+ self._print('Login failed: either the user name or password is '
+ 'invalid.\n')
while True:
count = count + 1
if count > 3:
@@ -317,14 +316,14 @@ WARNING: Python readline module isn't available, so the command line editor
return {}
- def send_POST(self, url, post_param = None):
+ def send_POST(self, url, post_param=None):
'''Send POST request to cmdctl, session id is send with the name
'cookie' in header.
Format: /module_name/command_name
parameters of command is encoded as a map
'''
param = None
- if (len(post_param) != 0):
+ if post_param is not None and len(post_param) != 0:
param = json.dumps(post_param)
headers = {"cookie" : self.session_id}
@@ -938,5 +937,3 @@ WARNING: Python readline module isn't available, so the command line editor
if data != "" and data != "{}":
self._print(json.dumps(json.loads(data), sort_keys=True,
indent=4))
-
-
diff --git a/src/bin/bindctl/run_bindctl.sh.in b/src/bin/bindctl/run_bindctl.sh.in
index 97e9250..0bd8818 100755
--- a/src/bin/bindctl/run_bindctl.sh.in
+++ b/src/bin/bindctl/run_bindctl.sh.in
@@ -27,7 +27,7 @@ export PYTHONPATH
# required by loadable python modules.
SET_ENV_LIBRARY_PATH=@SET_ENV_LIBRARY_PATH@
if test $SET_ENV_LIBRARY_PATH = yes; then
- @ENV_LIBRARY_PATH@=@abs_top_builddir@/src/lib/dns/.libs:@abs_top_builddir@/src/lib/dns/python/.libs:@abs_top_builddir@/src/lib/cryptolink/.libs:@abs_top_builddir@/src/lib/cc/.libs:@abs_top_builddir@/src/lib/config/.libs:@abs_top_builddir@/src/lib/log/.libs:@abs_top_builddir@/src/lib/util/.libs:@abs_top_builddir@/src/lib/util/io/.libs:@abs_top_builddir@/src/lib/exceptions/.libs:@abs_top_builddir@/src/lib/datasrc/.libs:$@ENV_LIBRARY_PATH@
+ @ENV_LIBRARY_PATH@=@abs_top_builddir@/src/lib/dns/.libs:@abs_top_builddir@/src/lib/dns/python/.libs:@abs_top_builddir@/src/lib/cryptolink/.libs:@abs_top_builddir@/src/lib/cc/.libs:@abs_top_builddir@/src/lib/config/.libs:@abs_top_builddir@/src/lib/log/.libs:@abs_top_builddir@/src/lib/util/.libs:@abs_top_builddir@/src/lib/util/threads/.libs:@abs_top_builddir@/src/lib/util/io/.libs:@abs_top_builddir@/src/lib/exceptions/.libs:@abs_top_builddir@/src/lib/datasrc/.libs:$@ENV_LIBRARY_PATH@
export @ENV_LIBRARY_PATH@
fi
diff --git a/src/bin/bindctl/tests/Makefile.am b/src/bin/bindctl/tests/Makefile.am
index 3d08a17..c781d7c 100644
--- a/src/bin/bindctl/tests/Makefile.am
+++ b/src/bin/bindctl/tests/Makefile.am
@@ -6,7 +6,7 @@ EXTRA_DIST = $(PYTESTS)
# required by loadable python modules.
LIBRARY_PATH_PLACEHOLDER =
if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/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/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/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:$$$(ENV_LIBRARY_PATH)
endif
# test using command-line arguments, so use check-local target instead of TESTS
diff --git a/src/bin/bindctl/tests/bindctl_test.py b/src/bin/bindctl/tests/bindctl_test.py
index 0ec9b58..c3262e1 100644
--- a/src/bin/bindctl/tests/bindctl_test.py
+++ b/src/bin/bindctl/tests/bindctl_test.py
@@ -26,6 +26,7 @@ import http.client
import pwd
import getpass
import re
+import json
from optparse import OptionParser
from isc.config.config_data import ConfigData, MultiConfigData
from isc.config.module_spec import ModuleSpec
@@ -386,7 +387,7 @@ class TestConfigCommands(unittest.TestCase):
self.tool.send_POST = send_POST_raiseImmediately
self.assertRaises(FailToLogin, self.tool._try_login, "foo", "bar")
expected_printed_messages.append(
- 'Socket error while sending login information: test error')
+ 'Error while sending login information: test error')
self.__check_printed_messages(expected_printed_messages)
def create_send_POST_raiseOnRead(exception):
@@ -405,7 +406,7 @@ class TestConfigCommands(unittest.TestCase):
create_send_POST_raiseOnRead(socket.error("read error"))
self.assertRaises(FailToLogin, self.tool._try_login, "foo", "bar")
expected_printed_messages.append(
- 'Socket error while sending login information: read error')
+ 'Error while sending login information: read error')
self.__check_printed_messages(expected_printed_messages)
# connection reset
@@ -415,13 +416,7 @@ class TestConfigCommands(unittest.TestCase):
create_send_POST_raiseOnRead(exc)
self.assertRaises(FailToLogin, self.tool._try_login, "foo", "bar")
expected_printed_messages.append(
- 'Socket error while sending login information: '
- 'connection reset')
- expected_printed_messages.append(
- 'Please check the logs of b10-cmdctl, there may be a '
- 'problem accepting SSL connections, such as a permission '
- 'problem on the server certificate file.'
- )
+ 'Error while sending login information: connection reset')
self.__check_printed_messages(expected_printed_messages)
# 'normal' SSL error
@@ -430,7 +425,7 @@ class TestConfigCommands(unittest.TestCase):
create_send_POST_raiseOnRead(exc)
self.assertRaises(FailToLogin, self.tool._try_login, "foo", "bar")
expected_printed_messages.append(
- 'SSL error while sending login information: .*')
+ 'Error while sending login information: .*')
self.__check_printed_messages(expected_printed_messages)
# 'EOF' SSL error
@@ -440,12 +435,7 @@ class TestConfigCommands(unittest.TestCase):
create_send_POST_raiseOnRead(exc)
self.assertRaises(FailToLogin, self.tool._try_login, "foo", "bar")
expected_printed_messages.append(
- 'SSL error while sending login information: .*')
- expected_printed_messages.append(
- 'Please check the logs of b10-cmdctl, there may be a '
- 'problem accepting SSL connections, such as a permission '
- 'problem on the server certificate file.'
- )
+ 'Error while sending login information: .*')
self.__check_printed_messages(expected_printed_messages)
# any other exception should be passed through
@@ -457,6 +447,44 @@ class TestConfigCommands(unittest.TestCase):
finally:
self.tool.send_POST = orig_send_POST
+ def test_try_login_calls_cmdctl(self):
+ # Make sure _try_login() makes the right API call to cmdctl.
+ orig_conn = self.tool.conn
+ try:
+ class MyConn:
+ def __init__(self):
+ self.method = None
+ self.url = None
+ self.param = None
+ self.headers = None
+
+ def request(self, method, url, param, headers):
+ self.method = method
+ self.url = url
+ self.param = param
+ self.headers = headers
+
+ def getresponse(self):
+ class MyResponse:
+ def __init__(self):
+ self.status = http.client.OK
+ def read(self):
+ class MyData:
+ def decode(self):
+ return json.dumps(True)
+ return MyData()
+ return MyResponse()
+
+ self.tool.conn = MyConn()
+ self.assertTrue(self.tool._try_login('user32', 'pass64'))
+ self.assertEqual(self.tool.conn.method, 'POST')
+ self.assertEqual(self.tool.conn.url, '/login')
+ self.assertEqual(json.loads(self.tool.conn.param),
+ {"password": "pass64", "username": "user32"})
+ self.assertIn('cookie', self.tool.conn.headers)
+ finally:
+ self.tool.conn = orig_conn
+
def test_run(self):
def login_to_cmdctl():
return True
diff --git a/src/bin/cfgmgr/plugins/datasrc.spec.pre.in b/src/bin/cfgmgr/plugins/datasrc.spec.pre.in
index 6d5bd77..3f16758 100644
--- a/src/bin/cfgmgr/plugins/datasrc.spec.pre.in
+++ b/src/bin/cfgmgr/plugins/datasrc.spec.pre.in
@@ -18,9 +18,9 @@
],
"CH": [
{
- "type": "static",
- "cache-enable": false,
- "params": "@@STATIC_ZONE_FILE@@"
+ "type": "MasterFiles",
+ "cache-enable": true,
+ "params": {"BIND": "@@STATIC_ZONE_FILE@@"}
}
]
},
@@ -63,6 +63,17 @@
"item_optional": false,
"item_default": ""
}
+ },
+ {
+ "item_name": "name",
+ "item_type": "string",
+ "item_optional": true
+ },
+ {
+ "item_name": "cache-type",
+ "item_type": "string",
+ "item_optional": true,
+ "item_default": "local"
}
]
}
diff --git a/src/bin/cfgmgr/plugins/tests/Makefile.am b/src/bin/cfgmgr/plugins/tests/Makefile.am
index 9b8b925..978dc4b 100644
--- a/src/bin/cfgmgr/plugins/tests/Makefile.am
+++ b/src/bin/cfgmgr/plugins/tests/Makefile.am
@@ -7,10 +7,12 @@ EXTRA_DIST = $(PYTESTS)
# required by loadable python modules.
LIBRARY_PATH_PLACEHOLDER =
if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/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/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/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:$$$(ENV_LIBRARY_PATH)
endif
# test using command-line arguments, so use check-local target instead of TESTS
+# We need to set B10_FROM_BUILD as some of the tests need to create lock file
+# for logging.
check-local:
if ENABLE_PYTHON_COVERAGE
touch $(abs_top_srcdir)/.coverage
@@ -20,6 +22,7 @@ endif
for pytest in $(PYTESTS) ; do \
echo Running test: $$pytest ; \
B10_TEST_PLUGIN_DIR=$(abs_srcdir)/..:$(abs_builddir)/.. \
+ B10_FROM_BUILD=$(abs_top_builddir) \
PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_builddir)/src/bin/cfgmgr:$(abs_top_builddir)/src/lib/dns/python/.libs \
$(LIBRARY_PATH_PLACEHOLDER) \
$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
diff --git a/src/bin/cfgmgr/plugins/tests/datasrc_test.py b/src/bin/cfgmgr/plugins/tests/datasrc_test.py
index f4381b4..3c714c6 100644
--- a/src/bin/cfgmgr/plugins/tests/datasrc_test.py
+++ b/src/bin/cfgmgr/plugins/tests/datasrc_test.py
@@ -142,7 +142,7 @@ class DatasrcTest(unittest.TestCase):
def test_no_such_file_mem(self):
"""
- We also check the existance of master files. Not the actual content,
+ We also check the existence of master files. Not the actual content,
though.
"""
self.reject({"IN": [{
@@ -153,6 +153,101 @@ class DatasrcTest(unittest.TestCase):
}
}]})
+ def test_names_present(self):
+ """
+ Test we don't choke on configuration with the "name" being present on
+ some items.
+ """
+ self.accept({"IN": [{
+ "type": "MasterFiles",
+ "cache-enable": True,
+ "params": {},
+ "name": "Whatever"
+ }]})
+
+ def test_names_default_classes(self):
+ """
+ Test we can have a client of the same type in different classes
+ without specified name. The defaults should be derived both from
+ the type and the class.
+ """
+ self.accept({
+ "IN": [{
+ "type": "MasterFiles",
+ "cache-enable": True,
+ "params": {}
+ }],
+ "CH": [{
+ "type": "MasterFiles",
+ "cache-enable": True,
+ "params": {}
+ }]})
+
+ def test_names_collision(self):
+ """
+ Reject when two names are the same.
+
+ Cases are:
+ - Explicit names.
+ - Two default names turn out to be the same (same type and class).
+ - One explicit is set to the same as the default one.
+ """
+ self.reject({"IN": [
+ {
+ "type": "MasterFiles",
+ "cache-enable": True,
+ "params": {},
+ "name": "Whatever"
+ },
+ {
+ "type": "MasterFiles",
+ "cache-enable": True,
+ "params": {},
+ "name": "Whatever"
+ }]})
+ # The same, but across different classes is allowed (we would
+ # identify the data source by class+name tuple)
+ self.accept({
+ "IN": [
+ {
+ "type": "MasterFiles",
+ "cache-enable": True,
+ "params": {},
+ "name": "Whatever"
+ }
+ ],
+ "CH": [
+ {
+ "type": "MasterFiles",
+ "cache-enable": True,
+ "params": {},
+ "name": "Whatever"
+ }
+ ]})
+ self.reject({"IN": [
+ {
+ "type": "MasterFiles",
+ "cache-enable": True,
+ "params": {}
+ },
+ {
+ "type": "MasterFiles",
+ "cache-enable": True,
+ "params": {}
+ }]})
+ self.reject({"IN": [
+ {
+ "type": "MasterFiles",
+ "cache-enable": True,
+ "params": {},
+ "name": "MasterFiles"
+ },
+ {
+ "type": "MasterFiles",
+ "cache-enable": True,
+ "params": {}
+ }]})
+
if __name__ == '__main__':
isc.log.init("bind10")
isc.log.resetUnitTestRootLogger()
diff --git a/src/bin/cfgmgr/plugins/tests/tsig_keys_test.py b/src/bin/cfgmgr/plugins/tests/tsig_keys_test.py
index 8c1639c..575879d 100644
--- a/src/bin/cfgmgr/plugins/tests/tsig_keys_test.py
+++ b/src/bin/cfgmgr/plugins/tests/tsig_keys_test.py
@@ -92,7 +92,7 @@ class TSigKeysTest(unittest.TestCase):
def test_bad_format(self):
"""
Test we fail on bad format. We don't really care much how here, though,
- as this should not get in trough config manager anyway.
+ as this should not get in through config manager anyway.
"""
self.assertNotEqual(None, tsig_keys.check({'bad_name': {}}))
self.assertNotEqual(None, tsig_keys.check({'keys': 'not_list'}))
diff --git a/src/bin/cfgmgr/tests/Makefile.am b/src/bin/cfgmgr/tests/Makefile.am
index a2e43ff..6293571 100644
--- a/src/bin/cfgmgr/tests/Makefile.am
+++ b/src/bin/cfgmgr/tests/Makefile.am
@@ -8,7 +8,7 @@ EXTRA_DIST = testdata/plugins/testplugin.py
# required by loadable python modules.
LIBRARY_PATH_PLACEHOLDER =
if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/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/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/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:$$$(ENV_LIBRARY_PATH)
endif
# test using command-line arguments, so use check-local target instead of TESTS
diff --git a/src/bin/cfgmgr/tests/b10-cfgmgr_test.py.in b/src/bin/cfgmgr/tests/b10-cfgmgr_test.py.in
index 02b48bd..c7c1081 100644
--- a/src/bin/cfgmgr/tests/b10-cfgmgr_test.py.in
+++ b/src/bin/cfgmgr/tests/b10-cfgmgr_test.py.in
@@ -70,7 +70,7 @@ class TestPlugins(unittest.TestCase):
class TestConfigManagerStartup(unittest.TestCase):
def test_cfgmgr(self):
# some creative module use;
- # b10-cfgmgr has a hypen, so we use __import__
+ # b10-cfgmgr has a hyphen, so we use __import__
# this also gives us the chance to override the imported
# module ConfigManager in it.
b = __import__("b10-cfgmgr")
diff --git a/src/bin/cmdctl/Makefile.am b/src/bin/cmdctl/Makefile.am
index 8cadf89..72a400a 100644
--- a/src/bin/cmdctl/Makefile.am
+++ b/src/bin/cmdctl/Makefile.am
@@ -11,20 +11,18 @@ pylogmessagedir = $(pyexecdir)/isc/log_messages/
b10_cmdctldir = $(pkgdatadir)
-USERSFILES = cmdctl-accounts.csv
CERTFILES = cmdctl-keyfile.pem cmdctl-certfile.pem
b10_cmdctl_DATA = cmdctl.spec
-EXTRA_DIST = $(USERSFILES)
-
CLEANFILES= b10-cmdctl cmdctl.pyc cmdctl.spec
CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/cmdctl_messages.py
CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/cmdctl_messages.pyc
man_MANS = b10-cmdctl.8 b10-certgen.1
DISTCLEANFILES = $(man_MANS) cmdctl-certfile.pem cmdctl-keyfile.pem
-EXTRA_DIST += $(man_MANS) b10-certgen.xml b10-cmdctl.xml cmdctl_messages.mes
+EXTRA_DIST = $(man_MANS) b10-certgen.xml b10-cmdctl.xml cmdctl_messages.mes
+EXTRA_DIST += cmdctl-accounts.csv
if GENERATE_DOCS
diff --git a/src/bin/cmdctl/cmdctl.py.in b/src/bin/cmdctl/cmdctl.py.in
index 457873b..b1ee903 100755
--- a/src/bin/cmdctl/cmdctl.py.in
+++ b/src/bin/cmdctl/cmdctl.py.in
@@ -42,6 +42,7 @@ import random
import time
import signal
from isc.config import ccsession
+import isc.cc.proto_defs
import isc.util.process
import isc.net.parse
from optparse import OptionParser, OptionValueError
@@ -97,6 +98,11 @@ def check_file(file_name):
class SecureHTTPRequestHandler(http.server.BaseHTTPRequestHandler):
'''https connection request handler.
Currently only GET and POST are supported. '''
+ def __init__(self, request, client_address, server):
+ http.server.BaseHTTPRequestHandler.__init__(self, request,
+ client_address, server)
+ self.session_id = None
+
def do_GET(self):
'''The client should send its session id in header with
the name 'cookie'
@@ -121,7 +127,7 @@ class SecureHTTPRequestHandler(http.server.BaseHTTPRequestHandler):
return self.server.get_reply_data_for_GET(id, module)
def _is_session_valid(self):
- return self.session_id
+ return self.session_id is not None
def _is_user_logged_in(self):
login_time = self.server.user_sessions.get(self.session_id)
@@ -171,7 +177,7 @@ class SecureHTTPRequestHandler(http.server.BaseHTTPRequestHandler):
is_user_valid, error_info = self._check_user_name_and_pwd()
if is_user_valid:
self.server.save_user_session_id(self.session_id)
- return http.client.OK, ["login success "]
+ return http.client.OK, ["login success"]
else:
return http.client.UNAUTHORIZED, error_info
@@ -241,6 +247,7 @@ class CommandControl():
CommandControl to communicate with other modules. '''
self._verbose = verbose
self._httpserver = httpserver
+ self.__msg_handler_thread = None # set in _start_msg_handle_thread
self._lock = threading.Lock()
self._setup_session()
self.modules_spec = self._get_modules_specification()
@@ -320,7 +327,26 @@ class CommandControl():
self._cmdctl_config_data[key] = new_config[key]
return answer
+ def _get_current_thread(self):
+ """A simple wrapper of returning the 'current' thread object.
+
+ This is extracted as a 'protected' method so tests can override for
+ their convenience.
+
+ """
+ return threading.currentThread()
+
def command_handler(self, command, args):
+ """Handle commands from other modules.
+
+ This method must not be called by any other threads than
+ __msg_handler_thread invoked at the intialization of the class;
+ otherwise it would cause critical race or dead locks.
+
+ """
+ # Check the restriction described above.
+ assert self._get_current_thread() == self.__msg_handler_thread
+
answer = ccsession.create_answer(0)
if command == ccsession.COMMAND_MODULE_SPECIFICATION_UPDATE:
# The 'value' of a specification update can be either
@@ -356,6 +382,7 @@ class CommandControl():
''' Start one thread to handle received message from msgq.'''
td = threading.Thread(target=self._handle_msg_from_msgq)
td.daemon = True
+ self.__msg_handler_thread = td
td.start()
def _handle_msg_from_msgq(self):
@@ -396,7 +423,7 @@ class CommandControl():
rcode, reply = self.send_command('ConfigManager', ccsession.COMMAND_GET_MODULE_SPEC)
return self._parse_command_result(rcode, reply)
- def send_command_with_check(self, module_name, command_name, params = None):
+ def send_command_with_check(self, module_name, command_name, params=None):
'''Before send the command to modules, check if module_name, command_name
parameters are legal according the spec file of the module.
Return rcode, dict. TODO, the rcode should be defined properly.
@@ -418,31 +445,34 @@ class CommandControl():
return self.send_command(module_name, command_name, params)
- def send_command(self, module_name, command_name, params = None):
- '''Send the command from bindctl to proper module. '''
+ def send_command(self, module_name, command_name, params=None):
+ """Send the command from bindctl to proper module.
+
+ Note that commands sent to Cmdctl itself are also delivered via the
+ CC session. Since this method is called from a thread handling a
+ particular HTTP session, it cannot directly call command_handler().
+
+ """
errstr = 'unknown error'
answer = None
logger.debug(DBG_CMDCTL_MESSAGING, CMDCTL_SEND_COMMAND,
command_name, module_name)
- if module_name == self._module_name:
- # Process the command sent to cmdctl directly.
- answer = self.command_handler(command_name, params)
- else:
- # FIXME: Due to the fact that we use a separate session
- # from the module one (due to threads and blocking), and
- # because the plain cc session does not have the high-level
- # rpc-call method, we use the low-level way and create the
- # command ourselves.
- msg = ccsession.create_command(command_name, params)
- seq = self._cc.group_sendmsg(msg, module_name, want_answer=True)
- logger.debug(DBG_CMDCTL_MESSAGING, CMDCTL_COMMAND_SENT,
- command_name, module_name)
- #TODO, it may be blocked, msqg need to add a new interface waiting in timeout.
- try:
- answer, env = self._cc.group_recvmsg(False, seq)
- except isc.cc.session.SessionTimeout:
- errstr = "Module '%s' not responding" % module_name
+ # FIXME: Due to the fact that we use a separate session
+ # from the module one (due to threads and blocking), and
+ # because the plain cc session does not have the high-level
+ # rpc-call method, we use the low-level way and create the
+ # command ourselves.
+ msg = ccsession.create_command(command_name, params)
+ seq = self._cc.group_sendmsg(msg, module_name, want_answer=True)
+ logger.debug(DBG_CMDCTL_MESSAGING, CMDCTL_COMMAND_SENT, command_name,
+ module_name)
+ # TODO, it may be blocked, msqg need to add a new interface waiting
+ # in timeout.
+ try:
+ answer, env = self._cc.group_recvmsg(False, seq)
+ except isc.cc.session.SessionTimeout:
+ errstr = "Module '%s' not responding" % module_name
if answer:
try:
@@ -454,7 +484,8 @@ class CommandControl():
else:
return rcode, {}
else:
- errstr = str(answer['result'][1])
+ errstr = \
+ str(answer[isc.cc.proto_defs.CC_PAYLOAD_RESULT][1])
except ccsession.ModuleCCSessionError as mcse:
errstr = str("Error in ccsession answer:") + str(mcse)
@@ -502,12 +533,25 @@ class SecureHTTPServer(socketserver_mixin.NoPollMixIn,
self._verbose = verbose
self._lock = threading.Lock()
self._user_infos = {}
- self._accounts_file = None
+ self.__accounts_file = None
+ self.__accounts_file_mtime = 0
def _create_user_info(self, accounts_file):
'''Read all user's name and its' salt, hashed password
from accounts file.'''
- if (self._accounts_file == accounts_file) and (len(self._user_infos) > 0):
+
+ # If the file does not exist, set accounts to empty, and return
+ if not os.path.exists(accounts_file):
+ self._user_infos = {}
+ self.__accounts_file = None
+ self.__accounts_file_mtime = 0
+ return
+
+ # If the filename hasn't changed, and the file itself
+ # has neither, do nothing
+ accounts_file_mtime = os.stat(accounts_file).st_mtime
+ if self.__accounts_file == accounts_file and\
+ accounts_file_mtime <= self.__accounts_file_mtime:
return
with self._lock:
@@ -525,7 +569,8 @@ class SecureHTTPServer(socketserver_mixin.NoPollMixIn,
if csvfile:
csvfile.close()
- self._accounts_file = accounts_file
+ self.__accounts_file = accounts_file
+ self.__accounts_file_mtime = accounts_file_mtime
if len(self._user_infos) == 0:
logger.error(CMDCTL_NO_USER_ENTRIES_READ)
diff --git a/src/bin/cmdctl/run_b10-cmdctl.sh.in b/src/bin/cmdctl/run_b10-cmdctl.sh.in
index 7dcf1d5..454e02c 100644
--- a/src/bin/cmdctl/run_b10-cmdctl.sh.in
+++ b/src/bin/cmdctl/run_b10-cmdctl.sh.in
@@ -26,7 +26,7 @@ export PYTHONPATH
# required by loadable python modules.
SET_ENV_LIBRARY_PATH=@SET_ENV_LIBRARY_PATH@
if test $SET_ENV_LIBRARY_PATH = yes; then
- @ENV_LIBRARY_PATH@=@abs_top_builddir@/src/lib/dns/.libs:@abs_top_builddir@/src/lib/dns/python/.libs:@abs_top_builddir@/src/lib/cryptolink/.libs:@abs_top_builddir@/src/lib/cc/.libs:@abs_top_builddir@/src/lib/config/.libs:@abs_top_builddir@/src/lib/log/.libs:@abs_top_builddir@/src/lib/util/.libs:@abs_top_builddir@/src/lib/util/io/.libs:@abs_top_builddir@/src/lib/exceptions/.libs:@abs_top_builddir@/src/lib/datasrc/.libs:$@ENV_LIBRARY_PATH@
+ @ENV_LIBRARY_PATH@=@abs_top_builddir@/src/lib/dns/.libs:@abs_top_builddir@/src/lib/dns/python/.libs:@abs_top_builddir@/src/lib/cryptolink/.libs:@abs_top_builddir@/src/lib/cc/.libs:@abs_top_builddir@/src/lib/config/.libs:@abs_top_builddir@/src/lib/log/.libs:@abs_top_builddir@/src/lib/util/.libs:@abs_top_builddir@/src/lib/util/threads/.libs:@abs_top_builddir@/src/lib/util/io/.libs:@abs_top_builddir@/src/lib/exceptions/.libs:@abs_top_builddir@/src/lib/datasrc/.libs:$@ENV_LIBRARY_PATH@
export @ENV_LIBRARY_PATH@
fi
diff --git a/src/bin/cmdctl/tests/Makefile.am b/src/bin/cmdctl/tests/Makefile.am
index 74b801a..93bf040 100644
--- a/src/bin/cmdctl/tests/Makefile.am
+++ b/src/bin/cmdctl/tests/Makefile.am
@@ -9,7 +9,7 @@ EXTRA_DIST += testdata/noca-certfile.pem
# 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/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/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:$$$(ENV_LIBRARY_PATH)
endif
CLEANFILES = test-keyfile.pem test-certfile.pem
@@ -25,8 +25,8 @@ endif
echo Running test: $$pytest ; \
$(LIBRARY_PATH_PLACEHOLDER) \
PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_builddir)/src/bin/cmdctl \
- CMDCTL_BUILD_PATH=$(abs_top_builddir)/src/bin/cmdctl \
CMDCTL_SRC_PATH=$(abs_top_srcdir)/src/bin/cmdctl \
+ CMDCTL_BUILD_PATH=$(abs_top_builddir)/src/bin/cmdctl \
B10_LOCKFILE_DIR_FROM_BUILD=$(abs_top_builddir) \
$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
done
diff --git a/src/bin/cmdctl/tests/b10-certgen_test.py b/src/bin/cmdctl/tests/b10-certgen_test.py
index d54efa3..56630bc 100644
--- a/src/bin/cmdctl/tests/b10-certgen_test.py
+++ b/src/bin/cmdctl/tests/b10-certgen_test.py
@@ -172,10 +172,7 @@ class TestCertGenTool(unittest.TestCase):
"""
Tests a few pre-created certificates with the -c option
"""
- if ('CMDCTL_SRC_PATH' in os.environ):
- path = os.environ['CMDCTL_SRC_PATH'] + "/tests/testdata/"
- else:
- path = "testdata/"
+ path = os.environ['CMDCTL_SRC_PATH'] + '/tests/testdata/'
self.validate_certificate(10, path + 'expired-certfile.pem')
self.validate_certificate(100, path + 'mangled-certfile.pem')
self.validate_certificate(17, path + 'noca-certfile.pem')
diff --git a/src/bin/cmdctl/tests/cmdctl_test.py b/src/bin/cmdctl/tests/cmdctl_test.py
index ee44f45..4a6b0e3 100644
--- a/src/bin/cmdctl/tests/cmdctl_test.py
+++ b/src/bin/cmdctl/tests/cmdctl_test.py
@@ -17,6 +17,7 @@
import unittest
import socket
import tempfile
+import time
import stat
import sys
from cmdctl import *
@@ -33,7 +34,7 @@ BUILD_FILE_PATH = os.environ['CMDCTL_BUILD_PATH'] + os.sep
# Rewrite the class for unittest.
class MySecureHTTPRequestHandler(SecureHTTPRequestHandler):
def __init__(self):
- pass
+ self.session_id = None
def send_response(self, rcode):
self.rcode = rcode
@@ -41,19 +42,6 @@ class MySecureHTTPRequestHandler(SecureHTTPRequestHandler):
def end_headers(self):
pass
- def do_GET(self):
- self.wfile = open('tmp.file', 'wb')
- super().do_GET()
- self.wfile.close()
- os.remove('tmp.file')
-
- def do_POST(self):
- self.wfile = open("tmp.file", 'wb')
- super().do_POST()
- self.wfile.close()
- os.remove('tmp.file')
-
-
class FakeSecureHTTPServer(SecureHTTPServer):
def __init__(self):
self.user_sessions = {}
@@ -84,6 +72,26 @@ class UnreadableFile:
def __exit__(self, type, value, traceback):
os.chmod(self.file_name, self.orig_mode)
+class TmpTextFile:
+ """
+ Context class for temporarily creating a text file with some
+ lines of content.
+
+ The file is automatically deleted if the context is left, so
+ make sure to not use the path of an existing file!
+ """
+ def __init__(self, path, contents):
+ self.__path = path
+ self.__contents = contents
+
+ def __enter__(self):
+ with open(self.__path, 'w') as f:
+ f.write("\n".join(self.__contents) + "\n")
+
+ def __exit__(self, type, value, traceback):
+ os.unlink(self.__path)
+
+
class TestSecureHTTPRequestHandler(unittest.TestCase):
def setUp(self):
self.old_stdout = sys.stdout
@@ -93,13 +101,22 @@ class TestSecureHTTPRequestHandler(unittest.TestCase):
self.handler.server.user_sessions = {}
self.handler.server._user_infos = {}
self.handler.headers = {}
- self.handler.rfile = open("check.tmp", 'w+b')
+ self.handler.rfile = open('input.tmp', 'w+b')
+ self.handler.wfile = open('output.tmp', 'w+b')
def tearDown(self):
sys.stdout.close()
sys.stdout = self.old_stdout
+ self.handler.wfile.close()
+ os.remove('output.tmp')
self.handler.rfile.close()
- os.remove('check.tmp')
+ os.remove('input.tmp')
+
+ def test_is_session_valid(self):
+ self.assertIsNone(self.handler.session_id)
+ self.assertFalse(self.handler._is_session_valid())
+ self.handler.session_id = 4234
+ self.assertTrue(self.handler._is_session_valid())
def test_parse_request_path(self):
self.handler.path = ''
@@ -160,7 +177,7 @@ class TestSecureHTTPRequestHandler(unittest.TestCase):
self.handler.do_GET()
self.assertEqual(self.handler.rcode, http.client.OK)
- def test_user_logged_in(self):
+ def test_is_user_logged_in(self):
self.handler.server.user_sessions = {}
self.handler.session_id = 12345
self.assertTrue(self.handler._is_user_logged_in() == False)
@@ -294,7 +311,92 @@ class TestSecureHTTPRequestHandler(unittest.TestCase):
rcode, reply = self.handler._handle_post_request()
self.assertEqual(http.client.BAD_REQUEST, rcode)
+ def test_handle_login(self):
+ orig_is_user_logged_in = self.handler._is_user_logged_in
+ orig_check_user_name_and_pwd = self.handler._check_user_name_and_pwd
+ try:
+ def create_is_user_logged_in(status):
+ '''Create a replacement _is_user_logged_in() method.'''
+ def my_is_user_logged_in():
+ return status
+ return my_is_user_logged_in
+
+ # Check case where _is_user_logged_in() returns True
+ self.handler._is_user_logged_in = create_is_user_logged_in(True)
+ self.handler.headers['cookie'] = 12345
+ self.handler.path = '/login'
+ self.handler.do_POST()
+ self.assertEqual(self.handler.rcode, http.client.OK)
+ self.handler.wfile.seek(0, 0)
+ d = self.handler.wfile.read()
+ self.assertEqual(json.loads(d.decode()),
+ ['user has already login'])
+
+ # Clear the output
+ self.handler.wfile.seek(0, 0)
+ self.handler.wfile.truncate()
+
+ # Check case where _is_user_logged_in() returns False
+ self.handler._is_user_logged_in = create_is_user_logged_in(False)
+
+ def create_check_user_name_and_pwd(status, error_info=None):
+ '''Create a replacement _check_user_name_and_pwd() method.'''
+ def my_check_user_name_and_pwd():
+ return status, error_info
+ return my_check_user_name_and_pwd
+
+ # (a) Check case where _check_user_name_and_pwd() returns
+ # valid user status
+ self.handler._check_user_name_and_pwd = \
+ create_check_user_name_and_pwd(True)
+ self.handler.do_POST()
+ self.assertEqual(self.handler.rcode, http.client.OK)
+ self.handler.wfile.seek(0, 0)
+ d = self.handler.wfile.read()
+ self.assertEqual(json.loads(d.decode()), ['login success'])
+
+ # Clear the output
+ self.handler.wfile.seek(0, 0)
+ self.handler.wfile.truncate()
+
+ # (b) Check case where _check_user_name_and_pwd() returns
+ # invalid user status
+ self.handler._check_user_name_and_pwd = \
+ create_check_user_name_and_pwd(False, ['login failed'])
+ self.handler.do_POST()
+ self.assertEqual(self.handler.rcode, http.client.UNAUTHORIZED)
+ self.handler.wfile.seek(0, 0)
+ d = self.handler.wfile.read()
+ self.assertEqual(json.loads(d.decode()), ['login failed'])
+
+ finally:
+ self.handler._is_user_logged_in = orig_is_user_logged_in
+ self.handler._check_user_name_and_pwd = orig_check_user_name_and_pwd
+
+class MockSession:
+ """Act like isc.cc.Session, stealing group_sendmsg/recvmsg().
+
+ The initial simple version only records given parameters in
+ group_sendmsg() for later inspection and raise a timeout exception
+ from recvmsg(). As we see the need for more test cases these methods
+ should be extended.
+
+ """
+ def __init__(self, sent_messages):
+ self.__sent_messages = sent_messages
+
+ def group_sendmsg(self, msg, module_name, want_answer):
+ self.__sent_messages.append((msg, module_name))
+
+ def group_recvmsg(self, nonblock, seq):
+ raise isc.cc.session.SessionTimeout('dummy timeout')
+
class MyCommandControl(CommandControl):
+ def __init__(self, httpserver, verbose):
+ super().__init__(httpserver, verbose)
+ self.sent_messages = [] # for inspection; allow tests to see it
+ self._cc = MockSession(self.sent_messages)
+
def _get_modules_specification(self):
return {}
@@ -311,6 +413,12 @@ class MyCommandControl(CommandControl):
def _handle_msg_from_msgq(self):
pass
+ def _start_msg_handle_thread(self): # just not bother to be threads
+ pass
+
+ def _get_current_thread(self):
+ return None
+
class TestCommandControl(unittest.TestCase):
def setUp(self):
@@ -423,8 +531,24 @@ class TestCommandControl(unittest.TestCase):
os.remove(file_name)
def test_send_command(self):
- rcode, value = self.cmdctl.send_command('Cmdctl', 'print_settings', None)
- self.assertEqual(rcode, 0)
+ # Send a command to other module. We check an expected message
+ # is sent via the session (cmdct._cc). Due to the behavior of
+ # our mock session object the anser will be "fail", but it's not
+ # the subject of this test, and so it's okay.
+ # TODO: more detailed cases should be tested.
+ rcode, value = self.cmdctl.send_command('Init', 'shutdown', None)
+ self.assertEqual(1, len(self.cmdctl.sent_messages))
+ self.assertEqual(({'command': ['shutdown']}, 'Init'),
+ self.cmdctl.sent_messages[-1])
+ self.assertEqual(1, rcode)
+
+ # Send a command to cmdctl itself. Should be the same effect.
+ rcode, value = self.cmdctl.send_command('Cmdctl', 'print_settings',
+ None)
+ self.assertEqual(2, len(self.cmdctl.sent_messages))
+ self.assertEqual(({'command': ['print_settings']}, 'Cmdctl'),
+ self.cmdctl.sent_messages[-1])
+ self.assertEqual(1, rcode)
class MySecureHTTPServer(SecureHTTPServer):
def server_bind(self):
@@ -470,6 +594,88 @@ class TestSecureHTTPServer(unittest.TestCase):
self.assertEqual(1, len(self.server._user_infos))
self.assertTrue('root' in self.server._user_infos)
+ def test_get_user_info(self):
+ self.assertIsNone(self.server.get_user_info('root'))
+ self.server._create_user_info(SRC_FILE_PATH + 'cmdctl-accounts.csv')
+ self.assertIn('6f0c73bd33101a5ec0294b3ca39fec90ef4717fe',
+ self.server.get_user_info('root'))
+
+ # When the file is not changed calling _create_user_info() again
+ # should have no effect. In order to test this, we overwrite the
+ # user-infos that were just set and make sure it isn't touched by
+ # the call (so make sure it isn't set to some empty value)
+ fake_users_val = { 'notinfile': [] }
+ self.server._user_infos = fake_users_val
+ self.server._create_user_info(SRC_FILE_PATH + 'cmdctl-accounts.csv')
+ self.assertEqual(fake_users_val, self.server._user_infos)
+
+ def test_create_user_info_changing_file_time(self):
+ self.assertEqual(0, len(self.server._user_infos))
+
+ # Create a file
+ accounts_file = BUILD_FILE_PATH + 'new_file.csv'
+ with TmpTextFile(accounts_file, ['root,foo,bar']):
+ self.server._create_user_info(accounts_file)
+ self.assertEqual(1, len(self.server._user_infos))
+ self.assertTrue('root' in self.server._user_infos)
+
+ # Make sure re-reading is a noop if file was not modified
+ fake_users_val = { 'notinfile': [] }
+ self.server._user_infos = fake_users_val
+ self.server._create_user_info(accounts_file)
+ self.assertEqual(fake_users_val, self.server._user_infos)
+
+ # create the file again, this time read should not be a noop
+ with TmpTextFile(accounts_file, ['otherroot,foo,bar']):
+ # Set mtime in future
+ stat = os.stat(accounts_file)
+ os.utime(accounts_file, (stat.st_atime, stat.st_mtime + 10))
+ self.server._create_user_info(accounts_file)
+ self.assertEqual(1, len(self.server._user_infos))
+ self.assertTrue('otherroot' in self.server._user_infos)
+
+ def test_create_user_info_changing_file_name(self):
+ """
+ Check that the accounts file is re-read if the file name is different
+ """
+ self.assertEqual(0, len(self.server._user_infos))
+
+ # Create two files
+ accounts_file1 = BUILD_FILE_PATH + 'new_file.csv'
+ accounts_file2 = BUILD_FILE_PATH + 'new_file2.csv'
+ with TmpTextFile(accounts_file2, ['otherroot,foo,bar']):
+ with TmpTextFile(accounts_file1, ['root,foo,bar']):
+ self.server._create_user_info(accounts_file1)
+ self.assertEqual(1, len(self.server._user_infos))
+ self.assertTrue('root' in self.server._user_infos)
+
+ # Make sure re-reading is a noop if file was not modified
+ fake_users_val = { 'notinfile': [] }
+ self.server._user_infos = fake_users_val
+ self.server._create_user_info(accounts_file1)
+ self.assertEqual(fake_users_val, self.server._user_infos)
+
+ # But a different file should be read
+ self.server._create_user_info(accounts_file2)
+ self.assertEqual(1, len(self.server._user_infos))
+ self.assertTrue('otherroot' in self.server._user_infos)
+
+ def test_create_user_info_nonexistent_file(self):
+ # Even if there was data initially, if set to a nonexistent
+ # file it should result in no users
+ accounts_file = BUILD_FILE_PATH + 'new_file.csv'
+ self.assertFalse(os.path.exists(accounts_file))
+ fake_users_val = { 'notinfile': [] }
+ self.server._user_infos = fake_users_val
+ self.server._create_user_info(accounts_file)
+ self.assertEqual({}, self.server._user_infos)
+
+ # Should it now be created it should be read
+ with TmpTextFile(accounts_file, ['root,foo,bar']):
+ self.server._create_user_info(accounts_file)
+ self.assertEqual(1, len(self.server._user_infos))
+ self.assertTrue('root' in self.server._user_infos)
+
def test_check_file(self):
# Just some file that we know exists
file_name = BUILD_FILE_PATH + 'cmdctl-keyfile.pem'
diff --git a/src/bin/dbutil/run_dbutil.sh.in b/src/bin/dbutil/run_dbutil.sh.in
index 8ec5668..cdbaf15 100755
--- a/src/bin/dbutil/run_dbutil.sh.in
+++ b/src/bin/dbutil/run_dbutil.sh.in
@@ -30,7 +30,7 @@ export PYTHONPATH
# required by loadable python modules.
SET_ENV_LIBRARY_PATH=@SET_ENV_LIBRARY_PATH@
if test $SET_ENV_LIBRARY_PATH = yes; then
- @ENV_LIBRARY_PATH@=@abs_top_builddir@/src/lib/dns/.libs:@abs_top_builddir@/src/lib/dns/python/.libs:@abs_top_builddir@/src/lib/cryptolink/.libs:@abs_top_builddir@/src/lib/cc/.libs:@abs_top_builddir@/src/lib/config/.libs:@abs_top_builddir@/src/lib/log/.libs:@abs_top_builddir@/src/lib/util/.libs:@abs_top_builddir@/src/lib/util/io/.libs:@abs_top_builddir@/src/lib/exceptions/.libs:@abs_top_builddir@/src/lib/datasrc/.libs:$@ENV_LIBRARY_PATH@
+ @ENV_LIBRARY_PATH@=@abs_top_builddir@/src/lib/dns/.libs:@abs_top_builddir@/src/lib/dns/python/.libs:@abs_top_builddir@/src/lib/cryptolink/.libs:@abs_top_builddir@/src/lib/cc/.libs:@abs_top_builddir@/src/lib/config/.libs:@abs_top_builddir@/src/lib/log/.libs:@abs_top_builddir@/src/lib/util/.libs:@abs_top_builddir@/src/lib/util/threads/.libs:@abs_top_builddir@/src/lib/util/io/.libs:@abs_top_builddir@/src/lib/exceptions/.libs:@abs_top_builddir@/src/lib/datasrc/.libs:$@ENV_LIBRARY_PATH@
export @ENV_LIBRARY_PATH@
fi
diff --git a/src/bin/dbutil/tests/Makefile.am b/src/bin/dbutil/tests/Makefile.am
index aaa57cc..1030c63 100644
--- a/src/bin/dbutil/tests/Makefile.am
+++ b/src/bin/dbutil/tests/Makefile.am
@@ -5,5 +5,11 @@ SUBDIRS = . testdata
noinst_SCRIPTS = dbutil_test.sh
check-local:
+if HAVE_SQLITE3_PROGRAM
B10_LOCKFILE_DIR_FROM_BUILD=$(abs_top_builddir) \
$(SHELL) $(abs_builddir)/dbutil_test.sh
+else
+ @echo ""
+ @echo " **** The sqlite3 program is required to run dbutil tests **** "
+ @echo ""
+endif
diff --git a/src/bin/dbutil/tests/dbutil_test.sh.in b/src/bin/dbutil/tests/dbutil_test.sh.in
index a98e4b1..f14c564 100755
--- a/src/bin/dbutil/tests/dbutil_test.sh.in
+++ b/src/bin/dbutil/tests/dbutil_test.sh.in
@@ -161,7 +161,7 @@ get_schema() {
# @param $2 Expected backup file
upgrade_ok_test() {
copy_file $1 $tempfile
- ${SHELL} ../run_dbutil.sh --upgrade --noconfirm $tempfile
+ @SHELL@ ../run_dbutil.sh --upgrade --noconfirm $tempfile
if [ $? -eq 0 ]
then
# Compare schema with the reference
@@ -199,7 +199,7 @@ upgrade_ok_test() {
# @param $2 Expected backup file
upgrade_fail_test() {
copy_file $1 $tempfile
- ${SHELL} ../run_dbutil.sh --upgrade --noconfirm $tempfile
+ @SHELL@ ../run_dbutil.sh --upgrade --noconfirm $tempfile
failzero $?
check_backup $1 $backupfile
}
@@ -222,7 +222,7 @@ record_count_test() {
records_count=`sqlite3 $tempfile 'select count(*) from records'`
zones_count=`sqlite3 $tempfile 'select count(*) from zones'`
- ${SHELL} ../run_dbutil.sh --upgrade --noconfirm $tempfile
+ @SHELL@ ../run_dbutil.sh --upgrade --noconfirm $tempfile
if [ $? -ne 0 ]
then
# Reason for failure should already have been output
@@ -268,12 +268,12 @@ record_count_test() {
# @param $2 Expected version string
check_version() {
copy_file $1 $verfile
- ${SHELL} ../run_dbutil.sh --check $verfile
+ @SHELL@ ../run_dbutil.sh --check $verfile
if [ $? -gt 2 ]
then
fail "version check failed on database $1; return code $?"
else
- ${SHELL} ../run_dbutil.sh --check $verfile 2>&1 | grep "$2" > /dev/null
+ @SHELL@ ../run_dbutil.sh --check $verfile 2>&1 | grep "$2" > /dev/null
if [ $? -ne 0 ]
then
fail "database $1 not at expected version $2 (output: $?)"
@@ -293,7 +293,7 @@ check_version() {
# @param $2 Backup file
check_version_fail() {
copy_file $1 $verfile
- ${SHELL} ../run_dbutil.sh --check $verfile
+ @SHELL@ ../run_dbutil.sh --check $verfile
failzero $?
check_no_backup $tempfile $backupfile
}
@@ -310,12 +310,12 @@ sec=0
# Test: check that the utility fails if the database does not exist
sec=`expr $sec + 1`
echo $sec".1. Non-existent database - check"
-${SHELL} ../run_dbutil.sh --check $tempfile
+ at SHELL@ ../run_dbutil.sh --check $tempfile
failzero $?
check_no_backup $tempfile $backupfile
echo $sec".2. Non-existent database - upgrade"
-${SHELL} ../run_dbutil.sh --upgrade --noconfirm $tempfile
+ at SHELL@ ../run_dbutil.sh --upgrade --noconfirm $tempfile
failzero $?
check_no_backup $tempfile $backupfile
rm -f $tempfile $backupfile
@@ -330,7 +330,7 @@ rm -f $tempfile $backupfile
echo $sec".2. Database is an empty file - upgrade"
touch $tempfile
-${SHELL} ../run_dbutil.sh --upgrade --noconfirm $tempfile
+ at SHELL@ ../run_dbutil.sh --upgrade --noconfirm $tempfile
failzero $?
# A backup is performed before anything else, so the backup should exist.
check_backup $tempfile $backupfile
@@ -344,7 +344,7 @@ rm -f $tempfile $backupfile
echo $sec".2. Database is not an SQLite file - upgrade"
echo "This is not an sqlite3 database" > $tempfile
-${SHELL} ../run_dbutil.sh --upgrade --noconfirm $tempfile
+ at SHELL@ ../run_dbutil.sh --upgrade --noconfirm $tempfile
failzero $?
# ...and as before, a backup should have been created
check_backup $tempfile $backupfile
@@ -459,31 +459,31 @@ rm -f $tempfile $backupfile ${backupfile}-1 ${backupfile}-2
sec=`expr $sec + 1`
echo $sec".1 Command-line errors"
copy_file $testdata/old_v1.sqlite3 $tempfile
-${SHELL} ../run_dbutil.sh $tempfile
+ at SHELL@ ../run_dbutil.sh $tempfile
failzero $?
-${SHELL} ../run_dbutil.sh --upgrade --check $tempfile
+ at SHELL@ ../run_dbutil.sh --upgrade --check $tempfile
failzero $?
-${SHELL} ../run_dbutil.sh --noconfirm --check $tempfile
+ at SHELL@ ../run_dbutil.sh --noconfirm --check $tempfile
failzero $?
-${SHELL} ../run_dbutil.sh --check
+ at SHELL@ ../run_dbutil.sh --check
failzero $?
-${SHELL} ../run_dbutil.sh --upgrade --noconfirm
+ at SHELL@ ../run_dbutil.sh --upgrade --noconfirm
failzero $?
-${SHELL} ../run_dbutil.sh --check $tempfile $backupfile
+ at SHELL@ ../run_dbutil.sh --check $tempfile $backupfile
failzero $?
-${SHELL} ../run_dbutil.sh --upgrade --noconfirm $tempfile $backupfile
+ at SHELL@ ../run_dbutil.sh --upgrade --noconfirm $tempfile $backupfile
failzero $?
rm -f $tempfile $backupfile
echo $sec".2 verbose flag"
copy_file $testdata/old_v1.sqlite3 $tempfile
-${SHELL} ../run_dbutil.sh --upgrade --noconfirm --verbose $tempfile
+ at SHELL@ ../run_dbutil.sh --upgrade --noconfirm --verbose $tempfile
passzero $?
rm -f $tempfile $backupfile
echo $sec".3 Interactive prompt - yes"
copy_file $testdata/old_v1.sqlite3 $tempfile
-${SHELL} ../run_dbutil.sh --upgrade $tempfile << .
+ at SHELL@ ../run_dbutil.sh --upgrade $tempfile << .
Yes
.
passzero $?
@@ -492,7 +492,7 @@ rm -f $tempfile $backupfile
echo $sec".4 Interactive prompt - no"
copy_file $testdata/old_v1.sqlite3 $tempfile
-${SHELL} ../run_dbutil.sh --upgrade $tempfile << .
+ at SHELL@ ../run_dbutil.sh --upgrade $tempfile << .
no
.
passzero $?
@@ -502,7 +502,7 @@ rm -f $tempfile $backupfile
echo $sec".5 quiet flag"
copy_file $testdata/old_v1.sqlite3 $tempfile
-${SHELL} ../run_dbutil.sh --check --quiet $tempfile 2>&1 | grep .
+ at SHELL@ ../run_dbutil.sh --check --quiet $tempfile 2>&1 | grep .
failzero $?
rm -f $tempfile $backupfile
diff --git a/src/bin/ddns/ddns.py.in b/src/bin/ddns/ddns.py.in
index 6f09bcc..47f67ca 100755
--- a/src/bin/ddns/ddns.py.in
+++ b/src/bin/ddns/ddns.py.in
@@ -354,7 +354,7 @@ class DDNSServer:
zname = Name(zone_spec['name'])
# class has the default value in case it's unspecified.
# ideally this should be merged within the config module, but
- # the current implementation doesn't esnure that, so we need to
+ # the current implementation doesn't ensure that, so we need to
# subsitute it ourselves.
if 'class' in zone_spec:
zclass = RRClass(zone_spec['class'])
@@ -510,7 +510,7 @@ class DDNSServer:
'''Send DDNS response to the client.
Right now, this is a straightforward subroutine of handle_request(),
- but is intended to be extended evetually so that it can handle more
+ but is intended to be extended eventually so that it can handle more
complicated operations for TCP (which requires asynchronous write).
Further, when we support multiple requests over a single TCP
connection, this method may even be shared by multiple methods.
diff --git a/src/bin/ddns/ddns_messages.mes b/src/bin/ddns/ddns_messages.mes
index 7d440d9..c537ae4 100644
--- a/src/bin/ddns/ddns_messages.mes
+++ b/src/bin/ddns/ddns_messages.mes
@@ -141,7 +141,7 @@ logged.
% DDNS_RESPONSE_TCP_SOCKET_SEND_FAILED failed to complete sending update response to %1 over TCP
b10-ddns had tried to send an update response over TCP, and it hadn't
-been completed at that time, and a followup attempt to complete the
+been completed at that time, and a follow-up attempt to complete the
send operation failed due to some network I/O error. While a network
error can happen any time, this event is quite unexpected for two
reasons. First, since the size of a response to an update request
diff --git a/src/bin/ddns/tests/Makefile.am b/src/bin/ddns/tests/Makefile.am
index 5c824d4..bf72353 100644
--- a/src/bin/ddns/tests/Makefile.am
+++ b/src/bin/ddns/tests/Makefile.am
@@ -6,7 +6,7 @@ EXTRA_DIST = $(PYTESTS)
# required by loadable python modules.
LIBRARY_PATH_PLACEHOLDER =
if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/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/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)
+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
diff --git a/src/bin/dhcp4/config_parser.cc b/src/bin/dhcp4/config_parser.cc
index d8a586b..f30ff21 100644
--- a/src/bin/dhcp4/config_parser.cc
+++ b/src/bin/dhcp4/config_parser.cc
@@ -13,19 +13,21 @@
// PERFORMANCE OF THIS SOFTWARE.
#include <config/ccsession.h>
-#include <dhcp4/config_parser.h>
#include <dhcp4/dhcp4_log.h>
#include <dhcp/libdhcp++.h>
#include <dhcp/option_definition.h>
#include <dhcpsrv/cfgmgr.h>
+#include <dhcp4/config_parser.h>
#include <dhcpsrv/dbaccess_parser.h>
-#include <dhcpsrv/dhcp_config_parser.h>
+#include <dhcpsrv/dhcp_parsers.h>
#include <dhcpsrv/option_space_container.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>
@@ -39,1415 +41,213 @@ using namespace isc::asiolink;
namespace {
-// Forward declarations of some of the parser classes.
-// They are used to define pointer types for these classes.
-class BooleanParser;
-class StringParser;
-class Uint32Parser;
-
-// Pointers to various parser objects.
-typedef boost::shared_ptr<BooleanParser> BooleanParserPtr;
-typedef boost::shared_ptr<StringParser> StringParserPtr;
-typedef boost::shared_ptr<Uint32Parser> Uint32ParserPtr;
-
-/// @brief a factory method that will create a parser for a given element name
-typedef isc::dhcp::DhcpConfigParser* ParserFactory(const std::string& config_id);
-
-/// @brief a collection of factories that creates parsers for specified element names
-typedef std::map<std::string, ParserFactory*> FactoryMap;
-
-/// @brief a collection of elements that store uint32 values (e.g. renew-timer = 900)
-typedef std::map<std::string, uint32_t> Uint32Storage;
-
-/// @brief a collection of elements that store string values
-typedef std::map<std::string, std::string> StringStorage;
-
-/// @brief Storage for parsed boolean values.
-typedef std::map<string, bool> BooleanStorage;
-
-/// @brief Storage for option definitions.
-typedef OptionSpaceContainer<OptionDefContainer,
- OptionDefinitionPtr> OptionDefStorage;
-
-/// @brief a collection of pools
-///
-/// That type is used as intermediate storage, when pools are parsed, but there is
-/// no subnet object created yet to store them.
-typedef std::vector<Pool4Ptr> PoolStorage;
-
-/// Collection of containers holding option spaces. Each container within
-/// a particular option space holds so-called option descriptors.
-typedef OptionSpaceContainer<Subnet::OptionContainer,
- Subnet::OptionDescriptor> OptionStorage;
-
-/// @brief Global uint32 parameters that will be used as defaults.
-Uint32Storage uint32_defaults;
-
-/// @brief global string parameters that will be used as defaults.
-StringStorage string_defaults;
-
-/// @brief Global storage for options that will be used as defaults.
-OptionStorage option_defaults;
-
-/// @brief Global storage for option definitions.
-OptionDefStorage option_def_intermediate;
-
-/// @brief a dummy configuration parser
-///
-/// It is a debugging parser. It does not configure anything,
-/// will accept any configuration and will just print it out
-/// on commit. Useful for debugging existing configurations and
-/// adding new ones.
-class DebugParser : public DhcpConfigParser {
-public:
-
- /// @brief Constructor
- ///
- /// See @ref DhcpConfigParser class for details.
- ///
- /// @param param_name name of the parsed parameter
- DebugParser(const std::string& param_name)
- :param_name_(param_name) {
- }
-
- /// @brief builds parameter value
- ///
- /// See @ref DhcpConfigParser class for details.
- ///
- /// @param new_config pointer to the new configuration
- virtual void build(ConstElementPtr new_config) {
- std::cout << "Build for token: [" << param_name_ << "] = ["
- << value_->str() << "]" << std::endl;
- value_ = new_config;
- }
-
- /// @brief pretends to apply the configuration
- ///
- /// This is a method required by base class. It pretends to apply the
- /// configuration, but in fact it only prints the parameter out.
- ///
- /// See @ref DhcpConfigParser class for details.
- virtual void commit() {
- // Debug message. The whole DebugParser class is used only for parser
- // debugging, and is not used in production code. It is very convenient
- // to keep it around. Please do not turn this cout into logger calls.
- std::cout << "Commit for token: [" << param_name_ << "] = ["
- << value_->str() << "]" << std::endl;
- }
-
- /// @brief factory that constructs DebugParser objects
- ///
- /// @param param_name name of the parameter to be parsed
- static DhcpConfigParser* Factory(const std::string& param_name) {
- return (new DebugParser(param_name));
- }
-
-private:
- /// name of the parsed parameter
- std::string param_name_;
-
- /// pointer to the actual value of the parameter
- ConstElementPtr value_;
-};
-
-/// @brief A boolean value parser.
-///
-/// This parser handles configuration values of the boolean type.
-/// Parsed values are stored in a provided storage. If no storage
-/// is provided then the build function throws an exception.
-class BooleanParser : public DhcpConfigParser {
-public:
- /// @brief Constructor.
- ///
- /// @param param_name name of the parameter.
- BooleanParser(const std::string& param_name)
- : storage_(NULL),
- param_name_(param_name),
- value_(false) {
- // Empty parameter name is invalid.
- if (param_name_.empty()) {
- isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
- << "empty parameter name provided");
- }
- }
-
- /// @brief Parse a boolean value.
- ///
- /// @param value a value to be parsed.
- ///
- /// @throw isc::InvalidOperation if a storage has not been set
- /// prior to calling this function
- /// @throw isc::dhcp::DhcpConfigError if a provided parameter's
- /// name is empty.
- virtual void build(ConstElementPtr value) {
- if (storage_ == NULL) {
- isc_throw(isc::InvalidOperation, "parser logic error:"
- << " storage for the " << param_name_
- << " value has not been set");
- } else if (param_name_.empty()) {
- isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
- << "empty parameter name provided");
- }
- // The Config Manager checks if user specified a
- // valid value for a boolean parameter: True or False.
- // It is then ok to assume that if str() does not return
- // 'true' the value is 'false'.
- value_ = (value->str() == "true") ? true : false;
- }
-
- /// @brief Put a parsed value to the storage.
- virtual void commit() {
- if (storage_ != NULL && !param_name_.empty()) {
- (*storage_)[param_name_] = value_;
- }
- }
-
- /// @brief Create an instance of the boolean parser.
- ///
- /// @param param_name name of the parameter for which the
- /// parser is created.
- static DhcpConfigParser* factory(const std::string& param_name) {
- return (new BooleanParser(param_name));
- }
-
- /// @brief Set the storage for parsed value.
- ///
- /// This function must be called prior to calling build.
- ///
- /// @param storage a pointer to the storage where parsed data
- /// is to be stored.
- void setStorage(BooleanStorage* storage) {
- storage_ = storage;
- }
-
-private:
- /// Pointer to the storage where parsed value is stored.
- BooleanStorage* storage_;
- /// Name of the parameter which value is parsed with this parser.
- std::string param_name_;
- /// Parsed value.
- bool value_;
-};
-
-/// @brief Configuration parser for uint32 parameters
-///
-/// This class is a generic parser that is able to handle any uint32 integer
-/// type. By default it stores the value in external global container
-/// (uint32_defaults). If used in smaller scopes (e.g. to parse parameters
-/// in subnet config), it can be pointed to a different storage, using
-/// setStorage() method. This class follows the parser interface, laid out
-/// in its base class, @ref DhcpConfigParser.
-///
-/// For overview of usability of this generic purpose parser, see
-/// @ref dhcpv4ConfigInherit page.
-class Uint32Parser : public DhcpConfigParser {
-public:
-
- /// @brief constructor for Uint32Parser
- /// @param param_name name of the configuration parameter being parsed
- Uint32Parser(const std::string& param_name)
- : storage_(&uint32_defaults),
- param_name_(param_name) {
- // Empty parameter name is invalid.
- if (param_name_.empty()) {
- isc_throw(DhcpConfigError, "parser logic error:"
- << "empty parameter name provided");
- }
- }
-
- /// @brief Parses configuration configuration parameter as uint32_t.
- ///
- /// @param value pointer to the content of parsed values
- /// @throw BadValue if supplied value could not be base to uint32_t
- /// or the parameter name is empty.
- virtual void build(ConstElementPtr value) {
- if (param_name_.empty()) {
- isc_throw(DhcpConfigError, "parser logic error:"
- << "empty parameter name provided");
- }
-
- int64_t check;
- string x = value->str();
- try {
- check = boost::lexical_cast<int64_t>(x);
- } catch (const boost::bad_lexical_cast &) {
- isc_throw(BadValue, "Failed to parse value " << value->str()
- << " as unsigned 32-bit integer.");
- }
- if (check > std::numeric_limits<uint32_t>::max()) {
- isc_throw(BadValue, "Value " << value->str() << "is too large"
- << " for unsigned 32-bit integer.");
- }
- if (check < 0) {
- isc_throw(BadValue, "Value " << value->str() << "is negative."
- << " Only 0 or larger are allowed for unsigned 32-bit integer.");
- }
-
- // value is small enough to fit
- value_ = static_cast<uint32_t>(check);
- }
-
- /// @brief Stores the parsed uint32_t value in a storage.
- virtual void commit() {
- if (storage_ != NULL && !param_name_.empty()) {
- // If a given parameter already exists in the storage we override
- // its value. If it doesn't we insert a new element.
- (*storage_)[param_name_] = value_;
- }
- }
-
- /// @brief factory that constructs Uint32Parser objects
- ///
- /// @param param_name name of the parameter to be parsed
- static DhcpConfigParser* factory(const std::string& param_name) {
- return (new Uint32Parser(param_name));
- }
-
- /// @brief sets storage for value of this parameter
- ///
- /// See @ref dhcpv4ConfigInherit for details.
- ///
- /// @param storage pointer to the storage container
- void setStorage(Uint32Storage* storage) {
- storage_ = storage;
- }
-
-private:
- /// pointer to the storage, where parsed value will be stored
- Uint32Storage* storage_;
-
- /// name of the parameter to be parsed
- std::string param_name_;
-
- /// the actual parsed value
- uint32_t value_;
-};
-
-/// @brief Configuration parser for string parameters
-///
-/// This class is a generic parser that is able to handle any string
-/// parameter. By default it stores the value in external global container
-/// (string_defaults). If used in smaller scopes (e.g. to parse parameters
-/// in subnet config), it can be pointed to a different storage, using
-/// setStorage() method. This class follows the parser interface, laid out
-/// in its base class, @ref DhcpConfigParser.
-///
-/// For overview of usability of this generic purpose parser, see
-/// @ref dhcpv4ConfigInherit page.
-class StringParser : public DhcpConfigParser {
-public:
-
- /// @brief constructor for StringParser
- /// @param param_name name of the configuration parameter being parsed
- StringParser(const std::string& param_name)
- :storage_(&string_defaults), param_name_(param_name) {
- // Empty parameter name is invalid.
- if (param_name_.empty()) {
- isc_throw(DhcpConfigError, "parser logic error:"
- << "empty parameter name provided");
- }
- }
-
- /// @brief parses parameter value
- ///
- /// Parses configuration entry and stores it in storage. See
- /// @ref setStorage() for details.
- ///
- /// @param value pointer to the content of parsed values
- virtual void build(ConstElementPtr value) {
- value_ = value->str();
- boost::erase_all(value_, "\"");
- }
-
- /// @brief Stores the parsed value in a storage.
- virtual void commit() {
- if (storage_ != NULL && !param_name_.empty()) {
- // If a given parameter already exists in the storage we override
- // its value. If it doesn't we insert a new element.
- (*storage_)[param_name_] = value_;
- }
- }
-
- /// @brief factory that constructs StringParser objects
- ///
- /// @param param_name name of the parameter to be parsed
- static DhcpConfigParser* factory(const std::string& param_name) {
- return (new StringParser(param_name));
- }
-
- /// @brief sets storage for value of this parameter
- ///
- /// See \ref dhcpv4ConfigInherit for details.
- ///
- /// @param storage pointer to the storage container
- void setStorage(StringStorage* storage) {
- storage_ = storage;
- }
-
-private:
- /// pointer to the storage, where parsed value will be stored
- StringStorage* storage_;
-
- /// name of the parameter to be parsed
- std::string param_name_;
-
- /// the actual parsed value
- std::string value_;
-};
-
-
-/// @brief parser for interface list definition
-///
-/// This parser handles Dhcp4/interface entry.
-/// It contains a list of network interfaces that the server listens on.
-/// In particular, it can contain an entry called "all" or "any" that
-/// designates all interfaces.
-///
-/// It is useful for parsing Dhcp4/interface parameter.
-class InterfaceListConfigParser : public DhcpConfigParser {
-public:
-
- /// @brief constructor
- ///
- /// As this is a dedicated parser, it must be used to parse
- /// "interface" parameter only. All other types will throw exception.
- ///
- /// @param param_name name of the configuration parameter being parsed
- /// @throw BadValue if supplied parameter name is not "interface"
- InterfaceListConfigParser(const std::string& param_name) {
- if (param_name != "interface") {
- isc_throw(BadValue, "Internal error. Interface configuration "
- "parser called for the wrong parameter: " << param_name);
- }
- }
-
- /// @brief parses parameters value
- ///
- /// Parses configuration entry (list of parameters) and adds each element
- /// to the interfaces list.
- ///
- /// @param value pointer to the content of parsed values
- virtual void build(ConstElementPtr value) {
- BOOST_FOREACH(ConstElementPtr iface, value->listValue()) {
- interfaces_.push_back(iface->str());
- }
- }
-
- /// @brief commits interfaces list configuration
- virtual void commit() {
- /// @todo: Implement per interface listening. Currently always listening
- /// on all interfaces.
- }
-
- /// @brief factory that constructs InterfaceListConfigParser objects
- ///
- /// @param param_name name of the parameter to be parsed
- static DhcpConfigParser* factory(const std::string& param_name) {
- return (new InterfaceListConfigParser(param_name));
- }
-
-private:
- /// contains list of network interfaces
- vector<string> interfaces_;
-};
-
-/// @brief parser for pool definition
-///
-/// This parser handles pool definitions, i.e. a list of entries of one
-/// of two syntaxes: min-max and prefix/len. Pool4 objects are created
-/// and stored in chosen PoolStorage container.
-///
-/// As there are no default values for pool, setStorage() must be called
-/// before build(). Otherwise exception will be thrown.
-///
-/// It is useful for parsing Dhcp4/subnet4[X]/pool parameters.
-class PoolParser : public DhcpConfigParser {
-public:
-
- /// @brief constructor.
- PoolParser(const std::string& /*param_name*/)
- :pools_(NULL) {
- // ignore parameter name, it is always Dhcp4/subnet4[X]/pool
- }
-
- /// @brief parses the actual list
- ///
- /// This method parses the actual list of interfaces.
- /// No validation is done at this stage, everything is interpreted as
- /// interface name.
- /// @param pools_list list of pools defined for a subnet
- /// @throw InvalidOperation if storage was not specified (setStorage() not called)
- /// @throw DhcpConfigError when pool parsing fails
- void build(ConstElementPtr pools_list) {
- // setStorage() should have been called before build
- if (!pools_) {
- isc_throw(InvalidOperation, "Parser logic error. No pool storage set,"
- " but pool parser asked to parse pools");
- }
-
- BOOST_FOREACH(ConstElementPtr text_pool, pools_list->listValue()) {
-
- // That should be a single pool representation. It should contain
- // text is form prefix/len or first - last. Note that spaces
- // are allowed
- string txt = text_pool->stringValue();
-
- // first let's remove any whitespaces
- boost::erase_all(txt, " "); // space
- boost::erase_all(txt, "\t"); // tabulation
-
- // Is this prefix/len notation?
- size_t pos = txt.find("/");
- if (pos != string::npos) {
- IOAddress addr("::");
- uint8_t len = 0;
- try {
- addr = IOAddress(txt.substr(0, pos));
-
- // start with the first character after /
- string prefix_len = txt.substr(pos + 1);
-
- // It is lexical cast to int and then downcast to uint8_t.
- // Direct cast to uint8_t (which is really an unsigned char)
- // will result in interpreting the first digit as output
- // value and throwing exception if length is written on two
- // digits (because there are extra characters left over).
-
- // No checks for values over 128. Range correctness will
- // be checked in Pool4 constructor.
- len = boost::lexical_cast<int>(prefix_len);
- } catch (...) {
- isc_throw(DhcpConfigError, "Failed to parse pool "
- "definition: " << text_pool->stringValue());
- }
-
- Pool4Ptr pool(new Pool4(addr, len));
- local_pools_.push_back(pool);
- continue;
- }
-
- // Is this min-max notation?
- pos = txt.find("-");
- if (pos != string::npos) {
- // using min-max notation
- IOAddress min(txt.substr(0,pos));
- IOAddress max(txt.substr(pos + 1));
-
- Pool4Ptr pool(new Pool4(min, max));
-
- local_pools_.push_back(pool);
- continue;
- }
-
- isc_throw(DhcpConfigError, "Failed to parse pool definition:"
- << text_pool->stringValue() <<
- ". Does not contain - (for min-max) nor / (prefix/len)");
- }
- }
-
- /// @brief sets storage for value of this parameter
- ///
- /// See \ref dhcpv4ConfigInherit for details.
- ///
- /// @param storage pointer to the storage container
- void setStorage(PoolStorage* storage) {
- pools_ = storage;
- }
-
- /// @brief Stores the parsed values in a storage provided
- /// by an upper level parser.
- virtual void commit() {
- if (pools_) {
- // local_pools_ holds the values produced by the build function.
- // At this point parsing should have completed successfuly so
- // we can append new data to the supplied storage.
- pools_->insert(pools_->end(), local_pools_.begin(),
- local_pools_.end());
- }
- }
-
- /// @brief factory that constructs PoolParser objects
- ///
- /// @param param_name name of the parameter to be parsed
- static DhcpConfigParser* factory(const std::string& param_name) {
- return (new PoolParser(param_name));
- }
-
-private:
- /// @brief pointer to the actual Pools storage
- ///
- /// That is typically a storage somewhere in Subnet parser
- /// (an upper level parser).
- PoolStorage* pools_;
- /// A temporary storage for pools configuration. It is a
- /// storage where pools are stored by build function.
- PoolStorage local_pools_;
-};
-
-/// @brief Parser for option data value.
+/// @brief Parser for DHCP4 option data value.
///
/// This parser parses configuration entries that specify value of
-/// a single option. These entries include option name, option code
-/// and data carried by the option. The option data can be specified
-/// in one of the two available formats: binary value represented as
-/// a string of hexadecimal digits or a list of comma separated values.
-/// The format being used is controlled by csv-format configuration
-/// parameter. When setting this value to True, the latter format is
-/// used. The subsequent values in the CSV format apply to relevant
-/// option data fields in the configured option. For example the
-/// configuration: "data" : "192.168.2.0, 56, hello world" can be
-/// used to set values for the option comprising IPv4 address,
-/// integer and string data field. Note that order matters. If the
-/// order of values does not match the order of data fields within
-/// an option the configuration will not be accepted. If parsing
-/// is successful then an instance of an option is created and
-/// added to the storage provided by the calling class.
-class OptionDataParser : public DhcpConfigParser {
+/// a single option specific to DHCP4. It provides the DHCP4-specific
+/// implementation of the abstract class OptionDataParser.
+class Dhcp4OptionDataParser : public OptionDataParser {
public:
-
/// @brief Constructor.
///
- /// Class constructor.
- OptionDataParser(const std::string&)
- : options_(NULL),
- // initialize option to NULL ptr
- option_descriptor_(false) { }
-
- /// @brief Parses the single option data.
- ///
- /// This method parses the data of a single option from the configuration.
- /// The option data includes option name, option code and data being
- /// carried by this option. Eventually it creates the instance of the
- /// option.
- ///
- /// @warning setStorage must be called with valid storage pointer prior
- /// to calling this method.
- ///
- /// @param option_data_entries collection of entries that define value
- /// for a particular option.
- /// @throw DhcpConfigError if invalid parameter specified in
- /// the configuration.
- /// @throw isc::InvalidOperation if failed to set storage prior to
- /// calling build.
- virtual void build(ConstElementPtr option_data_entries) {
- if (options_ == NULL) {
- isc_throw(isc::InvalidOperation, "Parser logic error: storage must be set before "
- "parsing option data.");
- }
- BOOST_FOREACH(ConfigPair param, option_data_entries->mapValue()) {
- ParserPtr parser;
- if (param.first == "name" || param.first == "data" ||
- param.first == "space") {
- boost::shared_ptr<StringParser>
- name_parser(dynamic_cast<StringParser*>(StringParser::factory(param.first)));
- if (name_parser) {
- name_parser->setStorage(&string_values_);
- parser = name_parser;
- }
- } else if (param.first == "code") {
- boost::shared_ptr<Uint32Parser>
- code_parser(dynamic_cast<Uint32Parser*>(Uint32Parser::factory(param.first)));
- if (code_parser) {
- code_parser->setStorage(&uint32_values_);
- parser = code_parser;
- }
- } else if (param.first == "csv-format") {
- boost::shared_ptr<BooleanParser>
- value_parser(dynamic_cast<BooleanParser*>(BooleanParser::factory(param.first)));
- if (value_parser) {
- value_parser->setStorage(&boolean_values_);
- parser = value_parser;
- }
- } else {
- isc_throw(DhcpConfigError,
- "Parser error: option-data parameter not supported: "
- << param.first);
- }
- parser->build(param.second);
- // Before we can create an option we need to get the data from
- // the child parsers. The only way to do it is to invoke commit
- // on them so as they store the values in appropriate storages
- // that this class provided to them. Note that this will not
- // modify values stored in the global storages so the configuration
- // will remain consistent even parsing fails somewhere further on.
- parser->commit();
- }
- // Try to create the option instance.
- createOption();
- }
-
- /// @brief Commits option value.
- ///
- /// This function adds a new option to the storage or replaces an existing option
- /// with the same code.
- ///
- /// @throw isc::InvalidOperation if failed to set pointer to storage or failed
- /// to call build() prior to commit. If that happens data in the storage
- /// remain un-modified.
- virtual void commit() {
- if (options_ == NULL) {
- isc_throw(isc::InvalidOperation, "parser logic error: storage must be set before "
- "committing option data.");
- } else if (!option_descriptor_.option) {
- // Before we can commit the new option should be configured. If it is not
- // than somebody must have called commit() before build().
- isc_throw(isc::InvalidOperation, "parser logic error: no option has been configured and"
- " thus there is nothing to commit. Has build() been called?");
- }
- uint16_t opt_type = option_descriptor_.option->getType();
- Subnet::OptionContainerPtr options = options_->getItems(option_space_);
- // The getItems() should never return NULL pointer. If there are no
- // options configured for the particular option space a pointer
- // to an empty container should be returned.
- assert(options);
- Subnet::OptionContainerTypeIndex& idx = options->get<1>();
- // Try to find options with the particular option code in the main
- // storage. If found, remove these options because they will be
- // replaced with new one.
- Subnet::OptionContainerTypeRange range =
- idx.equal_range(opt_type);
- if (std::distance(range.first, range.second) > 0) {
- idx.erase(range.first, range.second);
- }
- // Append new option to the main storage.
- options_->addItem(option_descriptor_, option_space_);
- }
-
- /// @brief Set storage for the parser.
- ///
- /// Sets storage for the parser. This storage points to the
- /// vector of options and is used by multiple instances of
- /// OptionDataParser. Each instance creates exactly one object
- /// of dhcp::Option or derived type and appends it to this
- /// storage.
- ///
- /// @param storage pointer to the options storage
- void setStorage(OptionStorage* storage) {
- options_ = storage;
- }
-
-private:
-
- /// @brief Create option instance.
- ///
- /// Creates an instance of an option and adds it to the provided
- /// options storage. If the option data parsed by \ref build function
- /// are invalid or insufficient this function emits an exception.
- ///
- /// @warning this function does not check if options_ storage pointer
- /// is intitialized but this check is not needed here because it is done
- /// in the \ref build function.
- ///
- /// @throw DhcpConfigError if parameters provided in the configuration
- /// are invalid.
- void createOption() {
- // Option code is held in the uint32_t storage but is supposed to
- // be uint16_t value. We need to check that value in the configuration
- // does not exceed range of uint8_t and is not zero.
- uint32_t option_code = getParam<uint32_t>("code", uint32_values_);
- if (option_code == 0) {
- isc_throw(DhcpConfigError, "option code must not be zero."
- << " Option code '0' is reserved in DHCPv4.");
- } else if (option_code > std::numeric_limits<uint8_t>::max()) {
- isc_throw(DhcpConfigError, "invalid option code '" << option_code
- << "', it must not exceed '"
- << std::numeric_limits<uint8_t>::max() << "'");
- }
- // Check that the option name has been specified, is non-empty and does not
- // contain spaces.
- std::string option_name = getParam<std::string>("name", string_values_);
- if (option_name.empty()) {
- isc_throw(DhcpConfigError, "name of the option with code '"
- << option_code << "' is empty");
- } else if (option_name.find(" ") != std::string::npos) {
- isc_throw(DhcpConfigError, "invalid option name '" << option_name
- << "', space character is not allowed");
- }
-
- std::string option_space = getParam<std::string>("space", string_values_);
- if (!OptionSpace::validateName(option_space)) {
- isc_throw(DhcpConfigError, "invalid option space name '"
- << option_space << "' specified for option '"
- << option_name << "' (code '" << option_code
- << "')");
- }
-
+ /// @param dummy first param, option names are always "Dhcp4/option-data[n]"
+ /// @param options is the option storage in which to store the parsed option
+ /// upon "commit".
+ /// @param global_context is a pointer to the global context which
+ /// stores global scope parameters, options, option defintions.
+ Dhcp4OptionDataParser(const std::string&,
+ OptionStoragePtr options, ParserContextPtr global_context)
+ :OptionDataParser("", options, global_context) {
+ }
+
+ /// @brief static factory method for instantiating Dhcp4OptionDataParsers
+ ///
+ /// @param param_name name of the parameter to be parsed.
+ /// @param options storage where the parameter value is to be stored.
+ /// @param global_context is a pointer to the global context which
+ /// stores global scope parameters, options, option defintions.
+ /// @return returns a pointer to a new OptionDataParser. Caller is
+ /// is responsible for deleting it when it is no longer needed.
+ static OptionDataParser* factory(const std::string& param_name,
+ OptionStoragePtr options, ParserContextPtr global_context) {
+ return (new Dhcp4OptionDataParser(param_name, options, global_context));
+ }
+
+protected:
+ /// @brief Finds an option definition within the server's option space
+ ///
+ /// Given an option space and an option code, find the correpsonding
+ /// option defintion within the server's option defintion storage.
+ ///
+ /// @param option_space name of the parameter option space
+ /// @param option_code numeric value of the parameter to find
+ /// @return OptionDefintionPtr of the option defintion or an
+ /// empty OptionDefinitionPtr if not found.
+ /// @throw DhcpConfigError if the option space requested is not valid
+ /// for this server.
+ virtual OptionDefinitionPtr findServerSpaceOptionDefinition (
+ std::string& option_space, uint32_t option_code) {
OptionDefinitionPtr def;
if (option_space == "dhcp4" &&
LibDHCP::isStandardOption(Option::V4, option_code)) {
def = LibDHCP::getOptionDef(Option::V4, option_code);
-
} else if (option_space == "dhcp6") {
isc_throw(DhcpConfigError, "'dhcp6' option space name is reserved"
- << " for DHCPv6 server");
- } else {
- // If we are not dealing with a standard option then we
- // need to search for its definition among user-configured
- // options. They are expected to be in the global storage
- // already.
- OptionDefContainerPtr defs = option_def_intermediate.getItems(option_space);
- // The getItems() should never return the NULL pointer. If there are
- // no option definitions for the particular option space a pointer
- // to an empty container should be returned.
- assert(defs);
- const OptionDefContainerTypeIndex& idx = defs->get<1>();
- OptionDefContainerTypeRange range = idx.equal_range(option_code);
- if (std::distance(range.first, range.second) > 0) {
- def = *range.first;
- }
- if (!def) {
- isc_throw(DhcpConfigError, "definition for the option '"
- << option_space << "." << option_name
- << "' having code '" << option_code
- << "' does not exist");
- }
-
+ << " for DHCPv6 server");
}
- // Get option data from the configuration database ('data' field).
- const std::string option_data = getParam<std::string>("data", string_values_);
- const bool csv_format = getParam<bool>("csv-format", boolean_values_);
-
- // Transform string of hexadecimal digits into binary format.
- std::vector<uint8_t> binary;
- std::vector<std::string> data_tokens;
-
- if (csv_format) {
- // If the option data is specified as a string of comma
- // separated values then we need to split this string into
- // individual values - each value will be used to initialize
- // one data field of an option.
- data_tokens = isc::util::str::tokens(option_data, ",");
- } else {
- // Otherwise, the option data is specified as a string of
- // hexadecimal digits that we have to turn into binary format.
- try {
- util::encode::decodeHex(option_data, binary);
- } catch (...) {
- isc_throw(DhcpConfigError, "option data is not a valid"
- << " string of hexadecimal digits: " << option_data);
- }
- }
-
- OptionPtr option;
- if (!def) {
- if (csv_format) {
- isc_throw(DhcpConfigError, "the CSV option data format can be"
- " used to specify values for an option that has a"
- " definition. The option with code " << option_code
- << " does not have a definition.");
- }
-
- // @todo We have a limited set of option definitions intiialized at the moment.
- // In the future we want to initialize option definitions for all options.
- // Consequently an error will be issued if an option definition does not exist
- // for a particular option code. For now it is ok to create generic option
- // if definition does not exist.
- OptionPtr option(new Option(Option::V4, static_cast<uint16_t>(option_code),
- binary));
- // The created option is stored in option_descriptor_ class member until the
- // commit stage when it is inserted into the main storage. If an option with the
- // same code exists in main storage already the old option is replaced.
- option_descriptor_.option = option;
- option_descriptor_.persistent = false;
- } else {
-
- // Option name should match the definition. The option name
- // may seem to be redundant but in the future we may want
- // to reference options and definitions using their names
- // and/or option codes so keeping the option name in the
- // definition of option value makes sense.
- if (def->getName() != option_name) {
- isc_throw(DhcpConfigError, "specified option name '"
- << option_name << "' does not match the "
- << "option definition: '" << option_space
- << "." << def->getName() << "'");
- }
-
- // Option definition has been found so let's use it to create
- // an instance of our option.
- try {
- OptionPtr option = csv_format ?
- def->optionFactory(Option::V4, option_code, data_tokens) :
- def->optionFactory(Option::V4, option_code, binary);
- Subnet::OptionDescriptor desc(option, false);
- option_descriptor_.option = option;
- option_descriptor_.persistent = false;
- } catch (const isc::Exception& ex) {
- isc_throw(DhcpConfigError, "option data does not match"
- << " option definition (space: " << option_space
- << ", code: " << option_code << "): "
- << ex.what());
- }
-
- }
- // All went good, so we can set the option space name.
- option_space_ = option_space;
+ return (def);
}
-
- /// Storage for uint32 values (e.g. option code).
- Uint32Storage uint32_values_;
- /// Storage for string values (e.g. option name or data).
- StringStorage string_values_;
- /// Storage for boolean values.
- BooleanStorage boolean_values_;
- /// Pointer to options storage. This storage is provided by
- /// the calling class and is shared by all OptionDataParser objects.
- OptionStorage* options_;
- /// Option descriptor holds newly configured option.
- Subnet::OptionDescriptor option_descriptor_;
- /// Option space name where the option belongs to.
- std::string option_space_;
};
-/// @brief Parser for option data values within a subnet.
+/// @brief Parser for IPv4 pool definitions.
///
-/// This parser iterates over all entries that define options
-/// data for a particular subnet and creates a collection of options.
-/// If parsing is successful, all these options are added to the Subnet
-/// object.
-class OptionDataListParser : public DhcpConfigParser {
+/// This is the IPv4 derivation of the PoolParser class and handles pool
+/// definitions, i.e. a list of entries of one of two syntaxes: min-max and
+/// prefix/len for IPv4 pools. Pool4 objects are created and stored in chosen
+/// PoolStorage container.
+///
+/// It is useful for parsing Dhcp4/subnet4[X]/pool parameters.
+class Pool4Parser : public PoolParser {
public:
/// @brief Constructor.
///
- /// Unless otherwise specified, parsed options will be stored in
- /// a global option container (option_default). That storage location
- /// is overriden on a subnet basis.
- OptionDataListParser(const std::string&)
- : options_(&option_defaults), local_options_() { }
-
- /// @brief Parses entries that define options' data for a subnet.
- ///
- /// This method iterates over all entries that define option data
- /// for options within a single subnet and creates options' instances.
- ///
- /// @param option_data_list pointer to a list of options' data sets.
- /// @throw DhcpConfigError if option parsing failed.
- void build(ConstElementPtr option_data_list) {
- BOOST_FOREACH(ConstElementPtr option_value, option_data_list->listValue()) {
- boost::shared_ptr<OptionDataParser> parser(new OptionDataParser("option-data"));
- // options_ member will hold instances of all options thus
- // each OptionDataParser takes it as a storage.
- parser->setStorage(&local_options_);
- // Build the instance of a single option.
- parser->build(option_value);
- // Store a parser as it will be used to commit.
- parsers_.push_back(parser);
- }
- }
-
- /// @brief Set storage for option instances.
- ///
- /// @param storage pointer to options storage.
- void setStorage(OptionStorage* storage) {
- options_ = storage;
+ /// @param param_name name of the parameter. Note, it is passed through
+ /// but unused, parameter is currently always "Dhcp4/subnet4[X]/pool"
+ /// @param pools storage container in which to store the parsed pool
+ /// upon "commit"
+ Pool4Parser(const std::string& param_name, PoolStoragePtr pools)
+ :PoolParser(param_name, pools) {
}
-
- /// @brief Commit all option values.
+protected:
+ /// @brief Creates a Pool4 object given a IPv4 prefix and the prefix length.
///
- /// This function invokes commit for all option values.
- void commit() {
- BOOST_FOREACH(ParserPtr parser, parsers_) {
- parser->commit();
- }
- // Parsing was successful and we have all configured
- // options in local storage. We can now replace old values
- // with new values.
- std::swap(local_options_, *options_);
+ /// @param addr is the IPv4 prefix of the pool.
+ /// @param len is the prefix length.
+ /// @param ignored dummy parameter to provide symmetry between the
+ /// PoolParser derivations. The V6 derivation requires a third value.
+ /// @return returns a PoolPtr to the new Pool4 object.
+ PoolPtr poolMaker (IOAddress &addr, uint32_t len, int32_t) {
+ return (PoolPtr(new Pool4(addr, len)));
}
- /// @brief Create DhcpDataListParser object
- ///
- /// @param param_name param name.
+ /// @brief Creates a Pool4 object given starting and ending IPv4 addresses.
///
- /// @return DhcpConfigParser object.
- static DhcpConfigParser* factory(const std::string& param_name) {
- return (new OptionDataListParser(param_name));
+ /// @param min is the first IPv4 address in the pool.
+ /// @param max is the last IPv4 address in the pool.
+ /// @param ignored dummy parameter to provide symmetry between the
+ /// PoolParser derivations. The V6 derivation requires a third value.
+ /// @return returns a PoolPtr to the new Pool4 object.
+ PoolPtr poolMaker (IOAddress &min, IOAddress &max, int32_t) {
+ return (PoolPtr(new Pool4(min, max)));
}
-
- /// Pointer to options instances storage.
- OptionStorage* options_;
- /// Intermediate option storage. This storage is used by
- /// lower level parsers to add new options. Values held
- /// in this storage are assigned to main storage (options_)
- /// if overall parsing was successful.
- OptionStorage local_options_;
- /// Collection of parsers;
- ParserCollection parsers_;
};
-/// @brief Parser for a single option definition.
+/// @brief This class parses a single IPv4 subnet.
///
-/// This parser creates an instance of a single option definition.
-class OptionDefParser : public DhcpConfigParser {
+/// This is the IPv4 derivation of the SubnetConfigParser class and it parses
+/// the whole subnet definition. It creates parsersfor received configuration
+/// parameters as needed.
+class Subnet4ConfigParser : public SubnetConfigParser {
public:
-
- /// @brief Constructor.
- ///
- /// This constructor sets the pointer to the option definitions
- /// storage to NULL. It must be set to point to the actual storage
- /// before \ref build is called.
- OptionDefParser(const std::string&)
- : storage_(NULL) {
- }
-
- /// @brief Parses an entry that describes single option definition.
- ///
- /// @param option_def a configuration entry to be parsed.
+ /// @brief Constructor
///
- /// @throw DhcpConfigError if parsing was unsuccessful.
- void build(ConstElementPtr option_def) {
- if (storage_ == NULL) {
- isc_throw(DhcpConfigError, "parser logic error: storage must be set"
- " before parsing option definition data");
- }
- // Parse the elements that make up the option definition.
- BOOST_FOREACH(ConfigPair param, option_def->mapValue()) {
- std::string entry(param.first);
- ParserPtr parser;
- if (entry == "name" || entry == "type" ||
- entry == "record-types" || entry == "space" ||
- entry == "encapsulate") {
- StringParserPtr
- str_parser(dynamic_cast<StringParser*>(StringParser::factory(entry)));
- if (str_parser) {
- str_parser->setStorage(&string_values_);
- parser = str_parser;
- }
- } else if (entry == "code") {
- Uint32ParserPtr
- code_parser(dynamic_cast<Uint32Parser*>(Uint32Parser::factory(entry)));
- if (code_parser) {
- code_parser->setStorage(&uint32_values_);
- parser = code_parser;
- }
- } else if (entry == "array") {
- BooleanParserPtr
- array_parser(dynamic_cast<BooleanParser*>(BooleanParser::factory(entry)));
- if (array_parser) {
- array_parser->setStorage(&boolean_values_);
- parser = array_parser;
- }
- } else {
- isc_throw(DhcpConfigError, "invalid parameter: " << entry);
- }
+ /// @param ignored first parameter
+ /// stores global scope parameters, options, option defintions.
+ Subnet4ConfigParser(const std::string&)
+ :SubnetConfigParser("", globalContext()) {
+ }
- parser->build(param.second);
- parser->commit();
- }
-
- // Create an instance of option definition.
- createOptionDef();
-
- // Get all items we collected so far for the particular option space.
- OptionDefContainerPtr defs = storage_->getItems(option_space_name_);
- // Check if there are any items with option code the same as the
- // one specified for the definition we are now creating.
- const OptionDefContainerTypeIndex& idx = defs->get<1>();
- const OptionDefContainerTypeRange& range =
- idx.equal_range(option_definition_->getCode());
- // If there are any items with this option code already we need
- // to issue an error because we don't allow duplicates for
- // option definitions within an option space.
- if (std::distance(range.first, range.second) > 0) {
- isc_throw(DhcpConfigError, "duplicated option definition for"
- << " code '" << option_definition_->getCode() << "'");
- }
- }
-
- /// @brief Stores the parsed option definition in a storage.
+ /// @brief Adds the created subnet to a server's configuration.
+ /// @throw throws Unexpected if dynamic cast fails.
void commit() {
- if (storage_ && option_definition_ &&
- OptionSpace::validateName(option_space_name_)) {
- storage_->addItem(option_definition_, option_space_name_);
- }
- }
-
- /// @brief Sets a pointer to the data store.
- ///
- /// The newly created instance of an option definition will be
- /// added to the data store given by the argument.
- ///
- /// @param storage pointer to the data store where the option definition
- /// will be added to.
- void setStorage(OptionDefStorage* storage) {
- storage_ = storage;
- }
-
-private:
-
- /// @brief Create option definition from the parsed parameters.
- void createOptionDef() {
- // Get the option space name and validate it.
- std::string space = getParam<std::string>("space", string_values_);
- if (!OptionSpace::validateName(space)) {
- isc_throw(DhcpConfigError, "invalid option space name '"
- << space << "'");
- }
-
- // Get other parameters that are needed to create the
- // option definition.
- std::string name = getParam<std::string>("name", string_values_);
- uint32_t code = getParam<uint32_t>("code", uint32_values_);
- std::string type = getParam<std::string>("type", string_values_);
- bool array_type = getParam<bool>("array", boolean_values_);
- std::string encapsulates = getParam<std::string>("encapsulate",
- string_values_);
-
- // Create option definition.
- OptionDefinitionPtr def;
- // We need to check if user has set encapsulated option space
- // name. If so, different constructor will be used.
- if (!encapsulates.empty()) {
- // Arrays can't be used together with sub-options.
- if (array_type) {
- isc_throw(DhcpConfigError, "option '" << space << "."
- << "name" << "', comprising an array of data"
- << " fields may not encapsulate any option space");
-
- } else if (encapsulates == space) {
- isc_throw(DhcpConfigError, "option must not encapsulate"
- << " an option space it belongs to: '"
- << space << "." << name << "' is set to"
- << " encapsulate '" << space << "'");
-
- } else {
- def.reset(new OptionDefinition(name, code, type,
- encapsulates.c_str()));
+ if (subnet_) {
+ Subnet4Ptr sub4ptr = boost::dynamic_pointer_cast<Subnet4>(subnet_);
+ if (!sub4ptr) {
+ // If we hit this, it is a programming error.
+ isc_throw(Unexpected,
+ "Invalid cast in Subnet4ConfigParser::commit");
}
- } else {
- def.reset(new OptionDefinition(name, code, type, array_type));
-
+ isc::dhcp::CfgMgr::instance().addSubnet4(sub4ptr);
}
- // The record-types field may carry a list of comma separated names
- // of data types that form a record.
- std::string record_types = getParam<std::string>("record-types",
- string_values_);
- // Split the list of record types into tokens.
- std::vector<std::string> record_tokens =
- isc::util::str::tokens(record_types, ",");
- // Iterate over each token and add a record type into
- // option definition.
- BOOST_FOREACH(std::string record_type, record_tokens) {
- try {
- boost::trim(record_type);
- if (!record_type.empty()) {
- def->addRecordField(record_type);
- }
- } catch (const Exception& ex) {
- isc_throw(DhcpConfigError, "invalid record type values"
- << " specified for the option definition: "
- << ex.what());
- }
- }
-
- // Check the option definition parameters are valid.
- try {
- def->validate();
- } catch (const isc::Exception& ex) {
- isc_throw(DhcpConfigError, "invalid option definition"
- << " parameters: " << ex.what());
- }
- // Option definition has been created successfully.
- option_space_name_ = space;
- option_definition_ = def;
}
- /// Instance of option definition being created by this parser.
- OptionDefinitionPtr option_definition_;
- /// Name of the space the option definition belongs to.
- std::string option_space_name_;
-
- /// Pointer to a storage where the option definition will be
- /// added when \ref commit is called.
- OptionDefStorage* storage_;
+protected:
- /// Storage for boolean values.
- BooleanStorage boolean_values_;
- /// Storage for string values.
- StringStorage string_values_;
- /// Storage for uint32 values.
- Uint32Storage uint32_values_;
-};
-
-/// @brief Parser for a list of option definitions.
-///
-/// This parser iterates over all configuration entries that define
-/// option definitions and creates instances of these definitions.
-/// If the parsing is successful, the collection of created definitions
-/// is put into the provided storage.
-class OptionDefListParser : DhcpConfigParser {
-public:
-
- /// @brief Constructor.
- ///
- /// This constructor initializes the pointer to option definitions
- /// storage to NULL value. This pointer has to be set to point to
- /// the actual storage before the \ref build function is called.
- OptionDefListParser(const std::string&) {
- }
-
- /// @brief Parse configuration entries.
+ /// @brief Creates parsers for entries in subnet definition.
///
- /// This function parses configuration entries and creates instances
- /// of option definitions.
+ /// @param config_id name of the entry
///
- /// @param option_def_list pointer to an element that holds entries
- /// that define option definitions.
- /// @throw DhcpConfigError if configuration parsing fails.
- void build(ConstElementPtr option_def_list) {
- // Clear existing items in the global storage.
- // We are going to replace all of them.
- option_def_intermediate.clearItems();
-
- if (!option_def_list) {
- isc_throw(DhcpConfigError, "parser error: a pointer to a list of"
- << " option definitions is NULL");
- }
-
- BOOST_FOREACH(ConstElementPtr option_def, option_def_list->listValue()) {
- boost::shared_ptr<OptionDefParser>
- parser(new OptionDefParser("single-option-def"));
- parser->setStorage(&option_def_intermediate);
- parser->build(option_def);
- parser->commit();
- }
- }
-
- /// @brief Stores option definitions in the CfgMgr.
- void commit() {
-
- CfgMgr& cfg_mgr = CfgMgr::instance();
-
- cfg_mgr.deleteOptionDefs();
-
- // We need to move option definitions from the temporary
- // storage to the global storage.
- std::list<std::string> space_names =
- option_def_intermediate.getOptionSpaceNames();
- BOOST_FOREACH(std::string space_name, space_names) {
-
- BOOST_FOREACH(OptionDefinitionPtr def,
- *option_def_intermediate.getItems(space_name)) {
- // All option definitions should be initialized to non-NULL
- // values. The validation is expected to be made by the
- // OptionDefParser when creating an option definition.
- assert(def);
- cfg_mgr.addOptionDef(def, space_name);
- }
+ /// @return parser object for specified entry name. Note the caller is
+ /// responsible for deleting the parser created.
+ /// @throw isc::dhcp::DhcpConfigError if trying to create a parser
+ /// for unknown config element
+ DhcpConfigParser* createSubnetConfigParser(const std::string& config_id) {
+ DhcpConfigParser* parser = NULL;
+ if ((config_id.compare("valid-lifetime") == 0) ||
+ (config_id.compare("renew-timer") == 0) ||
+ (config_id.compare("rebind-timer") == 0)) {
+ parser = new Uint32Parser(config_id, uint32_values_);
+ } else if ((config_id.compare("subnet") == 0) ||
+ (config_id.compare("interface") == 0)) {
+ parser = new StringParser(config_id, string_values_);
+ } else if (config_id.compare("pool") == 0) {
+ parser = new Pool4Parser(config_id, pools_);
+ } else if (config_id.compare("option-data") == 0) {
+ parser = new OptionDataListParser(config_id, options_,
+ global_context_,
+ Dhcp4OptionDataParser::factory);
+ } else {
+ isc_throw(NotImplemented,
+ "parser error: Subnet4 parameter not supported: " << config_id);
}
- }
-
- /// @brief Create an OptionDefListParser object.
- ///
- /// @param param_name configuration entry holding option definitions.
- ///
- /// @return OptionDefListParser object.
- static DhcpConfigParser* factory(const std::string& param_name) {
- return (new OptionDefListParser(param_name));
- }
-
-};
-/// @brief this class parses a single subnet
-///
-/// This class parses the whole subnet definition. It creates parsers
-/// for received configuration parameters as needed.
-class Subnet4ConfigParser : public DhcpConfigParser {
-public:
-
- /// @brief constructor
- Subnet4ConfigParser(const std::string& ) {
- // The parameter should always be "subnet", but we don't check here
- // against it in case someone wants to reuse this parser somewhere.
+ return (parser);
}
- /// @brief parses parameter value
- ///
- /// @param subnet pointer to the content of subnet definition
- void build(ConstElementPtr subnet) {
-
- BOOST_FOREACH(ConfigPair param, subnet->mapValue()) {
- ParserPtr parser(createSubnet4ConfigParser(param.first));
- // The actual type of the parser is unknown here. We have to discover
- // the parser type here to invoke the corresponding setStorage function
- // on it. We discover parser type by trying to cast the parser to various
- // parser types and checking which one was successful. For this one
- // a setStorage and build methods are invoked.
-
- // Try uint32 type parser.
- if (!buildParser<Uint32Parser, Uint32Storage >(parser, uint32_values_,
- param.second) &&
- // Try string type parser.
- !buildParser<StringParser, StringStorage >(parser, string_values_,
- param.second) &&
- // Try pool parser.
- !buildParser<PoolParser, PoolStorage >(parser, pools_,
- param.second) &&
- // Try option data parser.
- !buildParser<OptionDataListParser, OptionStorage >(parser, options_,
- param.second)) {
- // Appropriate parsers are created in the createSubnet6ConfigParser
- // and they should be limited to those that we check here for. Thus,
- // if we fail to find a matching parser here it is a programming error.
- isc_throw(DhcpConfigError, "failed to find suitable parser");
- }
- }
- // In order to create new subnet we need to get the data out
- // of the child parsers first. The only way to do it is to
- // invoke commit on them because it will make them write
- // parsed data into storages we have supplied.
- // Note that triggering commits on child parsers does not
- // affect global data because we supplied pointers to storages
- // local to this object. Thus, even if this method fails
- // later on, the configuration remains consistent.
- BOOST_FOREACH(ParserPtr parser, parsers_) {
- parser->commit();
- }
-
- // Create a subnet.
- createSubnet();
- }
- /// @brief commits received configuration.
+ /// @brief Determines if the given option space name and code describe
+ /// a standard option for the DCHP4 server.
///
- /// This method does most of the configuration. Many other parsers are just
- /// storing the values that are actually consumed here. Pool definitions
- /// created in other parsers are used here and added to newly created Subnet4
- /// objects. Subnet4 are then added to DHCP CfgMgr.
- /// @throw DhcpConfigError if there are any issues encountered during commit
- void commit() {
- if (subnet_) {
- CfgMgr::instance().addSubnet4(subnet_);
- }
+ /// @param option_space is the name of the option space to consider
+ /// @param code is the numeric option code to consider
+ /// @return returns true if the space and code are part of the server's
+ /// standard options.
+ bool isServerStdOption(std::string option_space, uint32_t code) {
+ return ((option_space.compare("dhcp4") == 0)
+ && LibDHCP::isStandardOption(Option::V4, code));
}
-private:
-
- /// @brief Set storage for a parser and invoke build.
- ///
- /// This helper method casts the provided parser pointer to the specified
- /// type. If the cast is successful it sets the corresponding storage for
- /// this parser, invokes build on it and saves the parser.
- ///
- /// @tparam T parser type to which parser argument should be cast.
- /// @tparam Y storage type for the specified parser type.
- /// @param parser parser on which build must be invoked.
- /// @param storage reference to a storage that will be set for a parser.
- /// @param subnet subnet element read from the configuration and being parsed.
- /// @return true if parser pointer was successfully cast to specialized
- /// parser type provided as Y.
- template<typename T, typename Y>
- bool buildParser(const ParserPtr& parser, Y& storage, const ConstElementPtr& subnet) {
- // We need to cast to T in order to set storage for the parser.
- boost::shared_ptr<T> cast_parser = boost::dynamic_pointer_cast<T>(parser);
- // It is common that this cast is not successful because we try to cast to all
- // supported parser types as we don't know the type of a parser in advance.
- if (cast_parser) {
- // Cast, successful so we go ahead with setting storage and actual parse.
- cast_parser->setStorage(&storage);
- parser->build(subnet);
- parsers_.push_back(parser);
- // We indicate that cast was successful so as the calling function
- // may skip attempts to cast to other parser types and proceed to
- // next element.
- return (true);
- }
- // It was not successful. Indicate that another parser type
- // should be tried.
- return (false);
+ /// @brief Returns the option definition for a given option code from
+ /// the DHCP4 server's standard set of options.
+ /// @param code is the numeric option code of the desired option definition.
+ /// @return returns a pointer the option definition
+ OptionDefinitionPtr getServerStdOptionDefinition (uint32_t code) {
+ return (LibDHCP::getOptionDef(Option::V4, code));
}
- /// @brief Append sub-options to an option.
- ///
- /// @param option_space a name of the encapsulated option space.
- /// @param option option instance to append sub-options to.
- void appendSubOptions(const std::string& option_space, OptionPtr& option) {
- // Only non-NULL options are stored in option container.
- // If this option pointer is NULL this is a serious error.
- assert(option);
-
- OptionDefinitionPtr def;
- if (option_space == "dhcp4" &&
- LibDHCP::isStandardOption(Option::V4, option->getType())) {
- def = LibDHCP::getOptionDef(Option::V4, option->getType());
- // Definitions for some of the standard options hasn't been
- // implemented so it is ok to leave here.
- if (!def) {
- return;
- }
- } else {
- const OptionDefContainerPtr defs =
- option_def_intermediate.getItems(option_space);
- const OptionDefContainerTypeIndex& idx = defs->get<1>();
- const OptionDefContainerTypeRange& range =
- idx.equal_range(option->getType());
- // There is no definition so we have to leave.
- if (std::distance(range.first, range.second) == 0) {
- return;
- }
-
- def = *range.first;
-
- // If the definition exists, it must be non-NULL.
- // Otherwise it is a programming error.
- assert(def);
- }
-
- // We need to get option definition for the particular option space
- // and code. This definition holds the information whether our
- // option encapsulates any option space.
- // Get the encapsulated option space name.
- std::string encapsulated_space = def->getEncapsulatedSpace();
- // If option space name is empty it means that our option does not
- // encapsulate any option space (does not include sub-options).
- if (!encapsulated_space.empty()) {
- // Get the sub-options that belong to the encapsulated
- // option space.
- const Subnet::OptionContainerPtr sub_opts =
- option_defaults.getItems(encapsulated_space);
- // Append sub-options to the option.
- BOOST_FOREACH(Subnet::OptionDescriptor desc, *sub_opts) {
- if (desc.option) {
- option->addOption(desc.option);
- }
- }
- }
+ /// @brief Issues a DHCP4 server specific warning regarding duplicate subnet
+ /// options.
+ ///
+ /// @param code is the numeric option code of the duplicate option
+ /// @param addr is the subnet address
+ /// @todo a means to know the correct logger and perhaps a common
+ /// message would allow this method to be emitted by the base class.
+ virtual void duplicate_option_warning(uint32_t code,
+ isc::asiolink::IOAddress& addr) {
+ LOG_WARN(dhcp4_logger, DHCP4_CONFIG_OPTION_DUPLICATE)
+ .arg(code).arg(addr.toText());
}
- /// @brief Create a new subnet using a data from child parsers.
- ///
- /// @throw isc::dhcp::DhcpConfigError if subnet configuration parsing failed.
- void createSubnet() {
- StringStorage::const_iterator it = string_values_.find("subnet");
- if (it == string_values_.end()) {
- isc_throw(DhcpConfigError,
- "Mandatory subnet definition in subnet missing");
- }
- // Remove any spaces or tabs.
- string subnet_txt = it->second;
- boost::erase_all(subnet_txt, " ");
- boost::erase_all(subnet_txt, "\t");
-
- // The subnet format is prefix/len. We are going to extract
- // the prefix portion of a subnet string to create IOAddress
- // object from it. IOAddress will be passed to the Subnet's
- // constructor later on. In order to extract the prefix we
- // need to get all characters preceding "/".
- size_t pos = subnet_txt.find("/");
- if (pos == string::npos) {
- isc_throw(DhcpConfigError,
- "Invalid subnet syntax (prefix/len expected):" << it->second);
- }
-
- // Try to create the address object. It also validates that
- // the address syntax is ok.
- IOAddress addr(subnet_txt.substr(0, pos));
- uint8_t len = boost::lexical_cast<unsigned int>(subnet_txt.substr(pos + 1));
-
+ /// @brief Instantiates the IPv4 Subnet based on a given IPv4 address
+ /// and prefix length.
+ ///
+ /// @param addr is IPv4 address of the subnet.
+ /// @param len is the prefix length
+ void initSubnet(isc::asiolink::IOAddress addr, uint8_t len) {
// Get all 'time' parameters using inheritance.
// If the subnet-specific value is defined then use it, else
// use the global value. The global value must always be
@@ -1465,154 +265,10 @@ private:
LOG_INFO(dhcp4_logger, DHCP4_CONFIG_NEW_SUBNET).arg(tmp.str());
subnet_.reset(new Subnet4(addr, len, t1, t2, valid));
-
- for (PoolStorage::iterator it = pools_.begin(); it != pools_.end(); ++it) {
- subnet_->addPool(*it);
- }
-
- // We are going to move configured options to the Subnet object.
- // Configured options reside in the container where options
- // are grouped by space names. Thus we need to get all space names
- // and iterate over all options that belong to them.
- std::list<std::string> space_names = options_.getOptionSpaceNames();
- BOOST_FOREACH(std::string option_space, space_names) {
- // Get all options within a particular option space.
- BOOST_FOREACH(Subnet::OptionDescriptor desc,
- *options_.getItems(option_space)) {
- // The pointer should be non-NULL. The validation is expected
- // to be performed by the OptionDataParser before adding an
- // option descriptor to the container.
- assert(desc.option);
- // We want to check whether an option with the particular
- // option code has been already added. If so, we want
- // to issue a warning.
- Subnet::OptionDescriptor existing_desc =
- subnet_->getOptionDescriptor("option_space",
- desc.option->getType());
- if (existing_desc.option) {
- LOG_WARN(dhcp4_logger, DHCP4_CONFIG_OPTION_DUPLICATE)
- .arg(desc.option->getType()).arg(addr.toText());
- }
- // Add sub-options (if any).
- appendSubOptions(option_space, desc.option);
- // In any case, we add the option to the subnet.
- subnet_->addOption(desc.option, false, option_space);
- }
- }
-
- // Check all global options and add them to the subnet object if
- // they have been configured in the global scope. If they have been
- // configured in the subnet scope we don't add global option because
- // the one configured in the subnet scope always takes precedence.
- space_names = option_defaults.getOptionSpaceNames();
- BOOST_FOREACH(std::string option_space, space_names) {
- // Get all global options for the particular option space.
- BOOST_FOREACH(Subnet::OptionDescriptor desc,
- *option_defaults.getItems(option_space)) {
- // The pointer should be non-NULL. The validation is expected
- // to be performed by the OptionDataParser before adding an
- // option descriptor to the container.
- assert(desc.option);
- // Check if the particular option has been already added.
- // This would mean that it has been configured in the
- // subnet scope. Since option values configured in the
- // subnet scope take precedence over globally configured
- // values we don't add option from the global storage
- // if there is one already.
- Subnet::OptionDescriptor existing_desc =
- subnet_->getOptionDescriptor(option_space, desc.option->getType());
- if (!existing_desc.option) {
- // Add sub-options (if any).
- appendSubOptions(option_space, desc.option);
-
- subnet_->addOption(desc.option, false, option_space);
- }
- }
- }
- }
-
- /// @brief creates parsers for entries in subnet definition
- ///
- /// @todo Add subnet-specific things here (e.g. subnet-specific options)
- ///
- /// @param config_id name od the entry
- /// @return parser object for specified entry name
- /// @throw NotImplemented if trying to create a parser for unknown config element
- DhcpConfigParser* createSubnet4ConfigParser(const std::string& config_id) {
- FactoryMap factories;
-
- factories["valid-lifetime"] = Uint32Parser::factory;
- factories["renew-timer"] = Uint32Parser::factory;
- factories["rebind-timer"] = Uint32Parser::factory;
- factories["subnet"] = StringParser::factory;
- factories["pool"] = PoolParser::factory;
- factories["option-data"] = OptionDataListParser::factory;
-
- FactoryMap::iterator f = factories.find(config_id);
- if (f == factories.end()) {
- // Used for debugging only.
- // return new DebugParser(config_id);
-
- isc_throw(NotImplemented,
- "parser error: Subnet4 parameter not supported: "
- << config_id);
- }
- return (f->second(config_id));
}
-
- /// @brief Returns value for a given parameter (after using inheritance)
- ///
- /// This method implements inheritance. For a given parameter name, it first
- /// checks if there is a global value for it and overwrites it with specific
- /// value if such value was defined in subnet.
- ///
- /// @param name name of the parameter
- /// @return triplet with the parameter name
- /// @throw DhcpConfigError when requested parameter is not present
- Triplet<uint32_t> getParam(const std::string& name) {
- uint32_t value = 0;
- bool found = false;
- Uint32Storage::iterator global = uint32_defaults.find(name);
- if (global != uint32_defaults.end()) {
- value = global->second;
- found = true;
- }
-
- Uint32Storage::iterator local = uint32_values_.find(name);
- if (local != uint32_values_.end()) {
- value = local->second;
- found = true;
- }
-
- if (found) {
- return (Triplet<uint32_t>(value));
- } else {
- isc_throw(DhcpConfigError, "Mandatory parameter " << name
- << " missing (no global default and no subnet-"
- << "specific value)");
- }
- }
-
- /// storage for subnet-specific uint32 values
- Uint32Storage uint32_values_;
-
- /// storage for subnet-specific integer values
- StringStorage string_values_;
-
- /// storage for pools belonging to this subnet
- PoolStorage pools_;
-
- /// storage for options belonging to this subnet
- OptionStorage options_;
-
- /// parsers are stored here
- ParserCollection parsers_;
-
- /// @brief Pointer to the created subnet object.
- isc::dhcp::Subnet4Ptr subnet_;
};
-/// @brief this class parses list of subnets
+/// @brief this class parses list of DHCP4 subnets
///
/// This is a wrapper parser that handles the whole list of Subnet4
/// definitions. It iterates over all entries and creates Subnet4ConfigParser
@@ -1622,8 +278,9 @@ public:
/// @brief constructor
///
+ /// @param dummy first argument, always ignored. All parsers accept a
+ /// string parameter "name" as their first argument.
Subnets4ListConfigParser(const std::string&) {
- /// parameter name is ignored
}
/// @brief parses contents of the list
@@ -1633,22 +290,17 @@ public:
///
/// @param subnets_list pointer to a list of IPv4 subnets
void build(ConstElementPtr subnets_list) {
-
- // No need to define FactoryMap here. There's only one type
- // used: Subnet4ConfigParser
-
BOOST_FOREACH(ConstElementPtr subnet, subnets_list->listValue()) {
ParserPtr parser(new Subnet4ConfigParser("subnet"));
parser->build(subnet);
subnets_.push_back(parser);
}
-
}
/// @brief commits subnets definitions.
///
- /// Iterates over all Subnet4 parsers. Each parser contains definitions
- /// of a single subnet and its parameters and commits each subnet separately.
+ /// Iterates over all Subnet4 parsers. Each parser contains definitions of
+ /// a single subnet and its parameters and commits each subnet separately.
void commit() {
// @todo: Implement more subtle reconfiguration than toss
// the old one and replace with the new one.
@@ -1686,44 +338,54 @@ namespace dhcp {
///
/// @param config_id pointer to received global configuration entry
/// @return parser for specified global DHCPv4 parameter
-/// @throw NotImplemented if trying to create a parser for unknown config element
+/// @throw NotImplemented if trying to create a parser for unknown
+/// config element
DhcpConfigParser* createGlobalDhcp4ConfigParser(const std::string& config_id) {
- FactoryMap factories;
-
- factories["valid-lifetime"] = Uint32Parser::factory;
- factories["renew-timer"] = Uint32Parser::factory;
- factories["rebind-timer"] = Uint32Parser::factory;
- factories["interface"] = InterfaceListConfigParser::factory;
- factories["subnet4"] = Subnets4ListConfigParser::factory;
- factories["option-data"] = OptionDataListParser::factory;
- factories["option-def"] = OptionDefListParser::factory;
- factories["version"] = StringParser::factory;
- factories["lease-database"] = DbAccessParser::factory;
-
- FactoryMap::iterator f = factories.find(config_id);
- if (f == factories.end()) {
- // Used for debugging only.
- // return new DebugParser(config_id);
-
+ DhcpConfigParser* parser = NULL;
+ if ((config_id.compare("valid-lifetime") == 0) ||
+ (config_id.compare("renew-timer") == 0) ||
+ (config_id.compare("rebind-timer") == 0)) {
+ parser = new Uint32Parser(config_id,
+ globalContext()->uint32_values_);
+ } else if (config_id.compare("interface") == 0) {
+ parser = new InterfaceListConfigParser(config_id);
+ } else if (config_id.compare("subnet4") == 0) {
+ parser = new Subnets4ListConfigParser(config_id);
+ } else if (config_id.compare("option-data") == 0) {
+ parser = new OptionDataListParser(config_id,
+ globalContext()->options_,
+ globalContext(),
+ Dhcp4OptionDataParser::factory);
+ } else if (config_id.compare("option-def") == 0) {
+ parser = new OptionDefListParser(config_id,
+ globalContext()->option_defs_);
+ } else if (config_id.compare("version") == 0) {
+ parser = new StringParser(config_id,
+ globalContext()->string_values_);
+ } else if (config_id.compare("lease-database") == 0) {
+ parser = new DbAccessParser(config_id);
+ } else {
isc_throw(NotImplemented,
- "Parser error: Global configuration parameter not supported: "
- << config_id);
+ "Parser error: Global configuration parameter not supported: "
+ << config_id);
}
- return (f->second(config_id));
+
+ return (parser);
}
isc::data::ConstElementPtr
-configureDhcp4Server(Dhcpv4Srv&, ConstElementPtr config_set) {
+configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) {
if (!config_set) {
ConstElementPtr answer = isc::config::createAnswer(1,
string("Can't parse NULL config"));
return (answer);
}
- /// @todo: append most essential info here (like "2 new subnets configured")
+ /// @todo: Append most essential info here (like "2 new subnets configured")
string config_details;
- LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND, DHCP4_CONFIG_START).arg(config_set->str());
+ LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND,
+ DHCP4_CONFIG_START).arg(config_set->str());
// Some of the values specified in the configuration depend on
// other values. Typically, the values in the subnet4 structure
@@ -1743,14 +405,11 @@ configureDhcp4Server(Dhcpv4Srv&, ConstElementPtr config_set) {
// parsing operation fails after the global storage has been
// modified. We need to preserve the original global data here
// so as we can rollback changes when an error occurs.
- Uint32Storage uint32_local(uint32_defaults);
- StringStorage string_local(string_defaults);
- OptionStorage option_local(option_defaults);
- OptionDefStorage option_def_local(option_def_intermediate);
+ ParserContext original_context(*globalContext());
- // answer will hold the result.
+ // Answer will hold the result.
ConstElementPtr answer;
- // rollback informs whether error occured and original data
+ // Rollback informs whether error occured and original data
// have to be restored to global storages.
bool rollback = false;
// config_pair holds the details of the current parser when iterating over
@@ -1760,17 +419,15 @@ configureDhcp4Server(Dhcpv4Srv&, ConstElementPtr config_set) {
try {
// Make parsers grouping.
const std::map<std::string, ConstElementPtr>& values_map =
- config_set->mapValue();
+ config_set->mapValue();
BOOST_FOREACH(config_pair, values_map) {
ParserPtr parser(createGlobalDhcp4ConfigParser(config_pair.first));
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_PARSER_CREATED)
.arg(config_pair.first);
if (config_pair.first == "subnet4") {
subnet_parser = parser;
-
} else if (config_pair.first == "option-data") {
option_parser = parser;
-
} else {
// Those parsers should be started before other
// parsers so we can call build straight away.
@@ -1808,7 +465,7 @@ configureDhcp4Server(Dhcpv4Srv&, ConstElementPtr config_set) {
rollback = true;
} catch (...) {
- // for things like bad_cast in boost::lexical_cast
+ // For things like bad_cast in boost::lexical_cast
LOG_ERROR(dhcp4_logger, DHCP4_PARSER_EXCEPTION).arg(config_pair.first);
answer = isc::config::createAnswer(1,
string("Configuration parsing failed"));
@@ -1832,23 +489,18 @@ configureDhcp4Server(Dhcpv4Srv&, ConstElementPtr config_set) {
answer = isc::config::createAnswer(2,
string("Configuration commit failed: ") + ex.what());
rollback = true;
-
} catch (...) {
- // for things like bad_cast in boost::lexical_cast
+ // For things like bad_cast in boost::lexical_cast
LOG_ERROR(dhcp4_logger, DHCP4_PARSER_COMMIT_EXCEPTION);
answer = isc::config::createAnswer(2,
string("Configuration commit failed"));
rollback = true;
-
}
}
// Rollback changes as the configuration parsing failed.
if (rollback) {
- std::swap(uint32_defaults, uint32_local);
- std::swap(string_defaults, string_local);
- std::swap(option_defaults, option_local);
- std::swap(option_def_intermediate, option_def_local);
+ globalContext().reset(new ParserContext(original_context));
return (answer);
}
@@ -1859,9 +511,13 @@ configureDhcp4Server(Dhcpv4Srv&, ConstElementPtr config_set) {
return (answer);
}
-const std::map<std::string, uint32_t>& getUint32Defaults() {
- return (uint32_defaults);
+ParserContextPtr& globalContext() {
+ static ParserContextPtr global_context_ptr(new ParserContext(Option::V4));
+ return (global_context_ptr);
}
+
+
}; // end of isc::dhcp namespace
}; // end of isc namespace
+
diff --git a/src/bin/dhcp4/config_parser.h b/src/bin/dhcp4/config_parser.h
index 4f1ea32..ea84cc6 100644
--- a/src/bin/dhcp4/config_parser.h
+++ b/src/bin/dhcp4/config_parser.h
@@ -12,8 +12,10 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-#include <exceptions/exceptions.h>
#include <cc/data.h>
+#include <exceptions/exceptions.h>
+#include <dhcpsrv/dhcp_parsers.h>
+
#include <stdint.h>
#include <string>
@@ -28,7 +30,8 @@ namespace dhcp {
class Dhcpv4Srv;
-/// @brief Configure DHCPv4 server (@c Dhcpv4Srv) with a set of configuration values.
+/// @brief Configure DHCPv4 server (@c Dhcpv4Srv) with a set of configuration
+/// values.
///
/// This function parses configuration information stored in @c config_set
/// and configures the @c server by applying the configuration to it.
@@ -41,9 +44,9 @@ class Dhcpv4Srv;
/// (such as malformed configuration or invalid configuration parameter),
/// this function returns appropriate error code.
///
-/// This function is called every time a new configuration is received. The extra
-/// parameter is a reference to DHCPv4 server component. It is currently not used
-/// and CfgMgr::instance() is accessed instead.
+/// This function is called every time a new configuration is received. The
+/// extra parameter is a reference to DHCPv4 server component. It is currently
+/// not used and CfgMgr::instance() is accessed instead.
///
/// This method does not throw. It catches all exceptions and returns them as
/// reconfiguration statuses. It may return the following response codes:
@@ -58,15 +61,10 @@ isc::data::ConstElementPtr
configureDhcp4Server(Dhcpv4Srv&,
isc::data::ConstElementPtr config_set);
-
-/// @brief Returns the global uint32_t values storage.
-///
-/// This function must be only used by unit tests that need
-/// to access uint32_t global storage to verify that the
-/// Uint32Parser works as expected.
+/// @brief Returns the global context
///
-/// @return a reference to a global uint32 values storage.
-const std::map<std::string, uint32_t>& getUint32Defaults();
+/// @return a reference to the global context
+ParserContextPtr& globalContext();
}; // end of isc::dhcp namespace
}; // end of isc namespace
diff --git a/src/bin/dhcp4/ctrl_dhcp4_srv.h b/src/bin/dhcp4/ctrl_dhcp4_srv.h
index e45deff..8f14f4b 100644
--- a/src/bin/dhcp4/ctrl_dhcp4_srv.h
+++ b/src/bin/dhcp4/ctrl_dhcp4_srv.h
@@ -97,7 +97,7 @@ protected:
/// @brief A dummy configuration handler that always returns success.
///
/// This configuration handler does not perform configuration
- /// parsing and always returns success. A dummy hanlder should
+ /// parsing and always returns success. A dummy handler should
/// be installed using \ref isc::config::ModuleCCSession ctor
/// to get the initial configuration. This initial configuration
/// comprises values for only those elements that were modified
diff --git a/src/bin/dhcp4/dhcp4_messages.mes b/src/bin/dhcp4/dhcp4_messages.mes
index 2f49ac8..8b3e255 100644
--- a/src/bin/dhcp4/dhcp4_messages.mes
+++ b/src/bin/dhcp4/dhcp4_messages.mes
@@ -74,7 +74,7 @@ many possible reasons for such a failure.
% DHCP4_LEASE_ALLOC lease %1 has been allocated for client-id %2, hwaddr %3
This debug message indicates that the server successfully granted a lease
in response to client's REQUEST message. This is a normal behavior and
-incicates successful operation.
+indicates successful operation.
% DHCP4_LEASE_ALLOC_FAIL failed to grant a lease for client-id %1, hwaddr %2
This message indicates that the server failed to grant a lease to the
diff --git a/src/bin/dhcp4/dhcp4_srv.cc b/src/bin/dhcp4/dhcp4_srv.cc
index 261e213..6f119ed 100644
--- a/src/bin/dhcp4/dhcp4_srv.cc
+++ b/src/bin/dhcp4/dhcp4_srv.cc
@@ -57,7 +57,7 @@ static const char* SERVER_ID_FILE = "b10-dhcp4-serverid";
// These are hardcoded parameters. Currently this is a skeleton server that only
// grants those options and a single, fixed, hardcoded lease.
-Dhcpv4Srv::Dhcpv4Srv(uint16_t port, const char* dbconfig) {
+Dhcpv4Srv::Dhcpv4Srv(uint16_t port, const char* dbconfig, const bool use_bcast) {
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_OPEN_SOCKET).arg(port);
try {
// First call to instance() will create IfaceMgr (it's a singleton)
@@ -67,7 +67,7 @@ Dhcpv4Srv::Dhcpv4Srv(uint16_t port, const char* dbconfig) {
if (port) {
// open sockets only if port is non-zero. Port 0 is used
// for non-socket related testing.
- IfaceMgr::instance().openSockets4(port);
+ IfaceMgr::instance().openSockets4(port, use_bcast);
}
string srvid_file = CfgMgr::instance().getDataDir() + "/" + string(SERVER_ID_FILE);
@@ -287,9 +287,9 @@ Dhcpv4Srv::generateServerID() {
continue;
}
- const IfaceMgr::AddressCollection addrs = iface->getAddresses();
+ const Iface::AddressCollection addrs = iface->getAddresses();
- for (IfaceMgr::AddressCollection::const_iterator addr = addrs.begin();
+ for (Iface::AddressCollection::const_iterator addr = addrs.begin();
addr != addrs.end(); ++addr) {
if (addr->getFamily() != AF_INET) {
continue;
@@ -317,7 +317,7 @@ Dhcpv4Srv::writeServerID(const std::string& file_name) {
return (true);
}
-string
+string
Dhcpv4Srv::srvidToString(const OptionPtr& srvid) {
if (!srvid) {
isc_throw(BadValue, "NULL pointer passed to srvidToString()");
@@ -343,7 +343,7 @@ Dhcpv4Srv::copyDefaultFields(const Pkt4Ptr& question, Pkt4Ptr& answer) {
answer->setIndex(question->getIndex());
answer->setCiaddr(question->getCiaddr());
- answer->setSiaddr(IOAddress("0.0.0.0")); // explictly set this to 0
+ answer->setSiaddr(IOAddress("0.0.0.0")); // explicitly set this to 0
answer->setHops(question->getHops());
// copy MAC address
@@ -517,6 +517,28 @@ Dhcpv4Srv::assignLease(const Pkt4Ptr& question, Pkt4Ptr& answer) {
answer->setYiaddr(lease->addr_);
+ // If remote address is not set, we are dealing with a directly
+ // connected client requesting new lease. We can send response to
+ // the address assigned in the lease, but first we have to make sure
+ // that IfaceMgr supports responding directly to the client when
+ // client doesn't have address assigned to its interface yet.
+ if (answer->getRemoteAddr().toText() == "0.0.0.0") {
+ if (IfaceMgr::instance().isDirectResponseSupported()) {
+ answer->setRemoteAddr(lease->addr_);
+ } else {
+ // Since IfaceMgr does not support direct responses to
+ // clients not having IP addresses, we have to send response
+ // to broadcast. We don't check whether the use_bcast flag
+ // was set in the constructor, because this flag is only used
+ // by unit tests to prevent opening broadcast sockets, as
+ // it requires root privileges. If this function is invoked by
+ // unit tests, we expect that it sets broadcast address if
+ // direct response is not supported, so as a test can verify
+ // function's behavior, regardless of the use_bcast flag's value.
+ answer->setRemoteAddr(IOAddress("255.255.255.255"));
+ }
+ }
+
// IP Address Lease time (type 51)
opt = OptionPtr(new Option(Option::V4, DHO_DHCP_LEASE_TIME));
opt->setUint32(lease->valid_lft_);
diff --git a/src/bin/dhcp4/dhcp4_srv.h b/src/bin/dhcp4/dhcp4_srv.h
index 1c988b1..bc8851e 100644
--- a/src/bin/dhcp4/dhcp4_srv.h
+++ b/src/bin/dhcp4/dhcp4_srv.h
@@ -66,8 +66,10 @@ class Dhcpv4Srv : public boost::noncopyable {
/// @param port specifies port number to listen on
/// @param dbconfig Lease manager configuration string. The default
/// of the "memfile" manager is used for testing.
+ /// @param use_bcast configure sockets to support broadcast messages.
Dhcpv4Srv(uint16_t port = DHCP4_SERVER_PORT,
- const char* dbconfig = "type=memfile");
+ const char* dbconfig = "type=memfile",
+ const bool use_bcast = true);
/// @brief Destructor. Used during DHCPv4 service shutdown.
~Dhcpv4Srv();
@@ -216,7 +218,7 @@ protected:
/// @param msg_type specifies message type
void appendDefaultOptions(Pkt4Ptr& msg, uint8_t msg_type);
- /// @brief Returns server-intentifier option
+ /// @brief Returns server-identifier option
///
/// @return server-id option
OptionPtr getServerID() { return serverid_; }
diff --git a/src/bin/dhcp4/tests/Makefile.am b/src/bin/dhcp4/tests/Makefile.am
index 73bf00b..3e10c3b 100644
--- a/src/bin/dhcp4/tests/Makefile.am
+++ b/src/bin/dhcp4/tests/Makefile.am
@@ -8,7 +8,7 @@ EXTRA_DIST = $(PYTESTS)
# about python not being able to load liblog library.
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/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/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:$$$(ENV_LIBRARY_PATH)
endif
# test using command-line arguments, so use check-local target instead of TESTS
diff --git a/src/bin/dhcp4/tests/config_parser_unittest.cc b/src/bin/dhcp4/tests/config_parser_unittest.cc
index 80b304c..32770b3 100644
--- a/src/bin/dhcp4/tests/config_parser_unittest.cc
+++ b/src/bin/dhcp4/tests/config_parser_unittest.cc
@@ -25,7 +25,10 @@
#include <dhcp/option_int.h>
#include <dhcpsrv/subnet.h>
#include <dhcpsrv/cfgmgr.h>
+
#include <boost/foreach.hpp>
+#include <boost/scoped_ptr.hpp>
+
#include <iostream>
#include <fstream>
#include <sstream>
@@ -47,20 +50,20 @@ public:
// Open port 0 means to not do anything at all. We don't want to
// deal with sockets here, just check if configuration handling
// is sane.
- srv_ = new Dhcpv4Srv(0);
+ srv_.reset(new Dhcpv4Srv(0));
}
// Checks if global parameter of name have expected_value
void checkGlobalUint32(string name, uint32_t expected_value) {
- const std::map<std::string, uint32_t>& uint32_defaults = getUint32Defaults();
- std::map<std::string, uint32_t>::const_iterator it =
- uint32_defaults.find(name);
- if (it == uint32_defaults.end()) {
+ const Uint32StoragePtr uint32_defaults =
+ globalContext()->uint32_values_;
+ try {
+ uint32_t actual_value = uint32_defaults->getParam(name);
+ EXPECT_EQ(expected_value, actual_value);
+ } catch (DhcpConfigError) {
ADD_FAILURE() << "Expected uint32 with name " << name
<< " not found";
- return;
}
- EXPECT_EQ(expected_value, it->second);
}
// Checks if the result of DHCP server configuration has
@@ -74,7 +77,6 @@ public:
~Dhcp4ParserTest() {
resetConfiguration();
- delete srv_;
};
/// @brief Create the simple configuration with single option.
@@ -83,7 +85,7 @@ public:
/// option value. These parameters are: "name", "code", "data",
/// "csv-format" and "space".
///
- /// @param param_value string holiding option parameter value to be
+ /// @param param_value string holding option parameter value to be
/// injected into the configuration string.
/// @param parameter name of the parameter to be configured with
/// param value.
@@ -279,7 +281,7 @@ public:
}
}
- Dhcpv4Srv* srv_;
+ boost::scoped_ptr<Dhcpv4Srv> srv_;
int rcode_;
ConstElementPtr comment_;
diff --git a/src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc b/src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc
index fd4e90e..13b57be 100644
--- a/src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc
+++ b/src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -18,6 +18,7 @@
#include <dhcp/dhcp4.h>
#include <dhcp4/ctrl_dhcp4_srv.h>
+#include <boost/scoped_ptr.hpp>
#include <gtest/gtest.h>
#include <fstream>
@@ -36,7 +37,7 @@ using namespace isc::config;
namespace {
class NakedControlledDhcpv4Srv: public ControlledDhcpv4Srv {
- // "naked" DHCPv4 server, exposes internal fields
+ // "Naked" DHCPv4 server, exposes internal fields
public:
NakedControlledDhcpv4Srv():ControlledDhcpv4Srv(DHCP4_SERVER_PORT + 10000) { }
};
@@ -52,21 +53,21 @@ public:
TEST_F(CtrlDhcpv4SrvTest, commands) {
- ControlledDhcpv4Srv* srv = NULL;
- ASSERT_NO_THROW({
- srv = new ControlledDhcpv4Srv(DHCP4_SERVER_PORT + 10000);
- });
+ boost::scoped_ptr<ControlledDhcpv4Srv> srv;
+ ASSERT_NO_THROW(
+ srv.reset(new ControlledDhcpv4Srv(DHCP4_SERVER_PORT + 10000))
+ );
- // use empty parameters list
+ // Use empty parameters list
ElementPtr params(new isc::data::MapElement());
int rcode = -1;
- // case 1: send bogus command
+ // Case 1: send bogus command
ConstElementPtr result = ControlledDhcpv4Srv::execDhcpv4ServerCommand("blah", params);
ConstElementPtr comment = parseAnswer(rcode, result);
EXPECT_EQ(1, rcode); // expect failure (no such command as blah)
- // case 2: send shutdown command without any parameters
+ // Case 2: send shutdown command without any parameters
result = ControlledDhcpv4Srv::execDhcpv4ServerCommand("shutdown", params);
comment = parseAnswer(rcode, result);
EXPECT_EQ(0, rcode); // expect success
@@ -75,13 +76,10 @@ TEST_F(CtrlDhcpv4SrvTest, commands) {
ConstElementPtr x(new isc::data::IntElement(pid));
params->set("pid", x);
- // case 3: send shutdown command with 1 parameter: pid
+ // Case 3: send shutdown command with 1 parameter: pid
result = ControlledDhcpv4Srv::execDhcpv4ServerCommand("shutdown", params);
comment = parseAnswer(rcode, result);
EXPECT_EQ(0, rcode); // expect success
-
-
- delete srv;
}
-} // end of anonymous namespace
+} // End of anonymous namespace
diff --git a/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc b/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
index c938155..cabd8b7 100644
--- a/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
+++ b/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
@@ -17,6 +17,7 @@
#include <asiolink/io_address.h>
#include <dhcp/dhcp4.h>
+#include <dhcp/iface_mgr.h>
#include <dhcp/option.h>
#include <dhcp/option4_addrlst.h>
#include <dhcp/option_custom.h>
@@ -29,6 +30,8 @@
#include <dhcpsrv/utils.h>
#include <gtest/gtest.h>
+#include <boost/scoped_ptr.hpp>
+
#include <fstream>
#include <iostream>
@@ -44,7 +47,16 @@ namespace {
class NakedDhcpv4Srv: public Dhcpv4Srv {
// "Naked" DHCPv4 server, exposes internal fields
public:
- NakedDhcpv4Srv(uint16_t port = 0):Dhcpv4Srv(port) { }
+
+ /// @brief Constructor.
+ ///
+ /// It disables configuration of broadcast options on
+ /// sockets that are opened by the Dhcpv4Srv constructor.
+ /// Setting broadcast options requires root privileges
+ /// which is not the case when running unit tests.
+ NakedDhcpv4Srv(uint16_t port = 0)
+ : Dhcpv4Srv(port, "type=memfile", false) {
+ }
using Dhcpv4Srv::processDiscover;
using Dhcpv4Srv::processRequest;
@@ -116,10 +128,10 @@ public:
/// @brief Configures options being requested in the PRL option.
///
- /// The lpr-servers option is NOT configured here altough it is
+ /// The lpr-servers option is NOT configured here although it is
/// added to the 'Parameter Request List' option in the
/// \ref addPrlOption. When requested option is not configured
- /// the server should not return it in its rensponse. The goal
+ /// the server should not return it in its response. The goal
/// of not configuring the requested option is to verify that
/// the server will not return it.
void configureRequestedOptions() {
@@ -132,8 +144,7 @@ public:
// domain-name
OptionDefinition def("domain-name", DHO_DOMAIN_NAME, OPT_FQDN_TYPE);
- boost::shared_ptr<OptionCustom>
- option_domain_name(new OptionCustom(def, Option::V4));
+ OptionCustomPtr option_domain_name(new OptionCustom(def, Option::V4));
option_domain_name->writeFqdn("example.com");
subnet_->addOption(option_domain_name, false, "dhcp4");
@@ -152,8 +163,7 @@ public:
/// @brief checks that the response matches request
/// @param q query (client's message)
/// @param a answer (server's message)
- void messageCheck(const boost::shared_ptr<Pkt4>& q,
- const boost::shared_ptr<Pkt4>& a) {
+ void messageCheck(const Pkt4Ptr& q, const Pkt4Ptr& a) {
ASSERT_TRUE(q);
ASSERT_TRUE(a);
@@ -170,6 +180,8 @@ public:
EXPECT_TRUE(a->getOption(DHO_DHCP_SERVER_IDENTIFIER));
EXPECT_TRUE(a->getOption(DHO_DHCP_LEASE_TIME));
EXPECT_TRUE(a->getOption(DHO_SUBNET_MASK));
+ EXPECT_TRUE(a->getOption(DHO_DOMAIN_NAME));
+ EXPECT_TRUE(a->getOption(DHO_DOMAIN_NAME_SERVERS));
// Check that something is offered
EXPECT_TRUE(a->getYiaddr().toText() != "0.0.0.0");
@@ -345,6 +357,120 @@ public:
EXPECT_TRUE(expected_clientid->getData() == opt->getData());
}
+ /// @brief Tests if Discover or Request message is processed correctly
+ ///
+ /// @param msg_type DHCPDISCOVER or DHCPREQUEST
+ /// @param client_addr client address
+ /// @param relay_addr relay address
+ void testDiscoverRequest(const uint8_t msg_type,
+ const IOAddress& client_addr,
+ const IOAddress& relay_addr) {
+
+ boost::scoped_ptr<NakedDhcpv4Srv> srv(new NakedDhcpv4Srv(0));
+ vector<uint8_t> mac(6);
+ for (int i = 0; i < 6; i++) {
+ mac[i] = i*10;
+ }
+
+ boost::shared_ptr<Pkt4> req(new Pkt4(msg_type, 1234));
+ boost::shared_ptr<Pkt4> rsp;
+
+ req->setIface("eth0");
+ req->setIndex(17);
+ req->setHWAddr(1, 6, mac);
+ req->setRemoteAddr(IOAddress(client_addr));
+ req->setGiaddr(relay_addr);
+
+ // We are going to test that certain options are returned
+ // in the response message when requested using 'Parameter
+ // Request List' option. Let's configure those options that
+ // are returned when requested.
+ configureRequestedOptions();
+
+ if (msg_type == DHCPDISCOVER) {
+ ASSERT_NO_THROW(
+ rsp = srv->processDiscover(req);
+ );
+
+ // Should return OFFER
+ ASSERT_TRUE(rsp);
+ EXPECT_EQ(DHCPOFFER, rsp->getType());
+
+ } else {
+ ASSERT_NO_THROW(
+ rsp = srv->processRequest(req);
+ );
+
+ // Should return ACK
+ ASSERT_TRUE(rsp);
+ EXPECT_EQ(DHCPACK, rsp->getType());
+
+ }
+
+ if (relay_addr.toText() != "0.0.0.0") {
+ // This is relayed message. It should be sent brsp to relay address.
+ EXPECT_EQ(req->getGiaddr().toText(),
+ rsp->getRemoteAddr().toText());
+
+ } else if (client_addr.toText() != "0.0.0.0") {
+ // This is a message from a client having an IP address.
+ EXPECT_EQ(req->getRemoteAddr().toText(),
+ rsp->getRemoteAddr().toText());
+
+ } else {
+ // This is a message from a client having no IP address yet.
+ // If IfaceMgr supports direct traffic the response should
+ // be sent to the new address assigned to the client.
+ if (IfaceMgr::instance().isDirectResponseSupported()) {
+ EXPECT_EQ(rsp->getYiaddr(),
+ rsp->getRemoteAddr().toText());
+
+ // If direct response to the client having no IP address is
+ // not supported, response should go to broadcast.
+ } else {
+ EXPECT_EQ("255.255.255.255", rsp->getRemoteAddr().toText());
+
+ }
+
+ }
+
+ messageCheck(req, rsp);
+
+ // We did not request any options so these should not be present
+ // in the RSP.
+ EXPECT_FALSE(rsp->getOption(DHO_LOG_SERVERS));
+ EXPECT_FALSE(rsp->getOption(DHO_COOKIE_SERVERS));
+ EXPECT_FALSE(rsp->getOption(DHO_LPR_SERVERS));
+
+ // Repeat the test but request some options.
+ // Add 'Parameter Request List' option.
+ addPrlOption(req);
+
+ if (msg_type == DHCPDISCOVER) {
+ ASSERT_NO_THROW(
+ rsp = srv->processDiscover(req);
+ );
+
+ // Should return non-NULL packet.
+ ASSERT_TRUE(rsp);
+ EXPECT_EQ(DHCPOFFER, rsp->getType());
+
+ } else {
+ ASSERT_NO_THROW(
+ rsp = srv->processRequest(req);
+ );
+
+ // Should return non-NULL packet.
+ ASSERT_TRUE(rsp);
+ EXPECT_EQ(DHCPACK, rsp->getType());
+
+ }
+
+ // Check that the requested options are returned.
+ optionsCheck(rsp);
+
+ }
+
~Dhcpv4SrvTest() {
CfgMgr::instance().deleteSubnets4();
@@ -367,29 +493,21 @@ public:
TEST_F(Dhcpv4SrvTest, basic) {
// Check that the base class can be instantiated
- Dhcpv4Srv* srv = NULL;
- ASSERT_NO_THROW({
- srv = new Dhcpv4Srv(DHCP4_SERVER_PORT + 10000);
- });
- delete srv;
+ boost::scoped_ptr<Dhcpv4Srv> srv;
+ ASSERT_NO_THROW(srv.reset(new Dhcpv4Srv(DHCP4_SERVER_PORT + 10000)));
+ srv.reset();
// Check that the derived class can be instantiated
- NakedDhcpv4Srv* naked_srv = NULL;
- ASSERT_NO_THROW({
- naked_srv = new NakedDhcpv4Srv(DHCP4_SERVER_PORT + 10000);
- });
+ boost::scoped_ptr<NakedDhcpv4Srv> naked_srv;
+ ASSERT_NO_THROW(
+ naked_srv.reset(new NakedDhcpv4Srv(DHCP4_SERVER_PORT + 10000)));
EXPECT_TRUE(naked_srv->getServerID());
- delete naked_srv;
- ASSERT_NO_THROW({
- naked_srv = new NakedDhcpv4Srv(0);
- });
+ ASSERT_NO_THROW(naked_srv.reset(new NakedDhcpv4Srv(0)));
EXPECT_TRUE(naked_srv->getServerID());
-
- delete naked_srv;
}
-// Verifies that received DISCOVER can be processed correctly,
+// Verifies that DISCOVER received via relay can be processed correctly,
// that the OFFER message generated in response is valid and
// contains necessary options.
//
@@ -397,248 +515,86 @@ TEST_F(Dhcpv4SrvTest, basic) {
// are other tests that verify correctness of the allocation
// engine. See DiscoverBasic, DiscoverHint, DiscoverNoClientId
// and DiscoverInvalidHint.
-TEST_F(Dhcpv4SrvTest, processDiscover) {
- NakedDhcpv4Srv* srv = new NakedDhcpv4Srv(0);
- vector<uint8_t> mac(6);
- for (int i = 0; i < 6; i++) {
- mac[i] = 255 - i;
- }
-
- boost::shared_ptr<Pkt4> pkt(new Pkt4(DHCPDISCOVER, 1234));
- boost::shared_ptr<Pkt4> offer;
-
- pkt->setIface("eth0");
- pkt->setIndex(17);
- pkt->setHWAddr(1, 6, mac);
- pkt->setRemoteAddr(IOAddress("192.0.2.56"));
- pkt->setGiaddr(IOAddress("192.0.2.67"));
-
- // Let's make it a relayed message
- pkt->setHops(3);
- pkt->setRemotePort(DHCP4_SERVER_PORT);
-
- // We are going to test that certain options are returned
- // (or not returned) in the OFFER message when requested
- // using 'Parameter Request List' option. Let's configure
- // those options that are returned when requested.
- configureRequestedOptions();
-
- // Should not throw
- EXPECT_NO_THROW(
- offer = srv->processDiscover(pkt);
- );
-
- // Should return something
- ASSERT_TRUE(offer);
-
- EXPECT_EQ(DHCPOFFER, offer->getType());
-
- // This is relayed message. It should be sent back to relay address.
- EXPECT_EQ(pkt->getGiaddr(), offer->getRemoteAddr());
-
- messageCheck(pkt, offer);
-
- // There are some options that are always present in the
- // message, even if not requested.
- EXPECT_TRUE(offer->getOption(DHO_DOMAIN_NAME));
- EXPECT_TRUE(offer->getOption(DHO_DOMAIN_NAME_SERVERS));
-
- // We did not request any options so they should not be present
- // in the OFFER.
- EXPECT_FALSE(offer->getOption(DHO_LOG_SERVERS));
- EXPECT_FALSE(offer->getOption(DHO_COOKIE_SERVERS));
- EXPECT_FALSE(offer->getOption(DHO_LPR_SERVERS));
-
- // Add 'Parameter Request List' option.
- addPrlOption(pkt);
-
- // Now repeat the test but request some options.
- EXPECT_NO_THROW(
- offer = srv->processDiscover(pkt);
- );
-
- // Should return something
- ASSERT_TRUE(offer);
-
- EXPECT_EQ(DHCPOFFER, offer->getType());
-
- // This is relayed message. It should be sent back to relay address.
- EXPECT_EQ(pkt->getGiaddr(), offer->getRemoteAddr());
-
- messageCheck(pkt, offer);
-
- // Check that the requested options are returned.
- optionsCheck(offer);
-
- // Now repeat the test for directly sent message
- pkt->setHops(0);
- pkt->setGiaddr(IOAddress("0.0.0.0"));
- pkt->setRemotePort(DHCP4_CLIENT_PORT);
-
- EXPECT_NO_THROW(
- offer = srv->processDiscover(pkt);
- );
-
- // Should return something
- ASSERT_TRUE(offer);
-
- EXPECT_EQ(DHCPOFFER, offer->getType());
-
- // This is direct message. It should be sent back to origin, not
- // to relay.
- EXPECT_EQ(pkt->getRemoteAddr(), offer->getRemoteAddr());
-
- messageCheck(pkt, offer);
+TEST_F(Dhcpv4SrvTest, processDiscoverRelay) {
+ testDiscoverRequest(DHCPDISCOVER,
+ IOAddress("192.0.2.56"),
+ IOAddress("192.0.2.67"));
+}
- // Check that the requested options are returned.
- optionsCheck(offer);
+// Verifies that the non-relayed DISCOVER is processed correctly when
+// client source address is specified.
+TEST_F(Dhcpv4SrvTest, processDiscoverNoRelay) {
+ testDiscoverRequest(DHCPDISCOVER,
+ IOAddress("0.0.0.0"),
+ IOAddress("192.0.2.67"));
+}
- delete srv;
+// Verified that the non-relayed DISCOVER is processed correctly when
+// client source address is not specified.
+TEST_F(Dhcpv4SrvTest, processDiscoverNoClientAddr) {
+ testDiscoverRequest(DHCPDISCOVER,
+ IOAddress("0.0.0.0"),
+ IOAddress("0.0.0.0"));
}
-// Verifies that received REQUEST can be processed correctly,
-// that the ACK message generated in response is valid and
+// Verifies that REQUEST received via relay can be processed correctly,
+// that the OFFER message generated in response is valid and
// contains necessary options.
//
// Note: this test focuses on the packet correctness. There
// are other tests that verify correctness of the allocation
-// engine. See RequestBasic.
-TEST_F(Dhcpv4SrvTest, processRequest) {
- NakedDhcpv4Srv* srv = new NakedDhcpv4Srv(0);
- vector<uint8_t> mac(6);
- for (int i = 0; i < 6; i++) {
- mac[i] = i*10;
- }
-
- boost::shared_ptr<Pkt4> req(new Pkt4(DHCPREQUEST, 1234));
- boost::shared_ptr<Pkt4> ack;
-
- req->setIface("eth0");
- req->setIndex(17);
- req->setHWAddr(1, 6, mac);
- req->setRemoteAddr(IOAddress("192.0.2.56"));
- req->setGiaddr(IOAddress("192.0.2.67"));
-
- // We are going to test that certain options are returned
- // in the ACK message when requested using 'Parameter
- // Request List' option. Let's configure those options that
- // are returned when requested.
- configureRequestedOptions();
-
- // Should not throw
- ASSERT_NO_THROW(
- ack = srv->processRequest(req);
- );
-
- // Should return something
- ASSERT_TRUE(ack);
-
- EXPECT_EQ(DHCPACK, ack->getType());
-
- // This is relayed message. It should be sent back to relay address.
- EXPECT_EQ(req->getGiaddr(), ack->getRemoteAddr());
-
- messageCheck(req, ack);
-
- // There are some options that are always present in the
- // message, even if not requested.
- EXPECT_TRUE(ack->getOption(DHO_DOMAIN_NAME));
- EXPECT_TRUE(ack->getOption(DHO_DOMAIN_NAME_SERVERS));
-
- // We did not request any options so these should not be present
- // in the ACK.
- EXPECT_FALSE(ack->getOption(DHO_LOG_SERVERS));
- EXPECT_FALSE(ack->getOption(DHO_COOKIE_SERVERS));
- EXPECT_FALSE(ack->getOption(DHO_LPR_SERVERS));
-
- // Add 'Parameter Request List' option.
- addPrlOption(req);
-
- // Repeat the test but request some options.
- ASSERT_NO_THROW(
- ack = srv->processRequest(req);
- );
-
- // Should return something
- ASSERT_TRUE(ack);
-
- EXPECT_EQ(DHCPACK, ack->getType());
-
- // This is relayed message. It should be sent back to relay address.
- EXPECT_EQ(req->getGiaddr(), ack->getRemoteAddr());
-
- // Check that the requested options are returned.
- optionsCheck(ack);
-
- // Now repeat the test for directly sent message
- req->setHops(0);
- req->setGiaddr(IOAddress("0.0.0.0"));
- req->setRemotePort(DHCP4_CLIENT_PORT);
-
- EXPECT_NO_THROW(
- ack = srv->processDiscover(req);
- );
-
- // Should return something
- ASSERT_TRUE(ack);
-
- EXPECT_EQ(DHCPOFFER, ack->getType());
-
- // This is direct message. It should be sent back to origin, not
- // to relay.
- EXPECT_EQ(ack->getRemoteAddr(), req->getRemoteAddr());
-
- messageCheck(req, ack);
+// engine. See DiscoverBasic, DiscoverHint, DiscoverNoClientId
+// and DiscoverInvalidHint.
+TEST_F(Dhcpv4SrvTest, processRequestRelay) {
+ testDiscoverRequest(DHCPREQUEST,
+ IOAddress("192.0.2.56"),
+ IOAddress("192.0.2.67"));
+}
- // Check that the requested options are returned.
- optionsCheck(ack);
+// Verifies that the non-relayed REQUEST is processed correctly when
+// client source address is specified.
+TEST_F(Dhcpv4SrvTest, processRequestNoRelay) {
+ testDiscoverRequest(DHCPREQUEST,
+ IOAddress("0.0.0.0"),
+ IOAddress("192.0.2.67"));
+}
- delete srv;
+// Verified that the non-relayed REQUEST is processed correctly when
+// client source address is not specified.
+TEST_F(Dhcpv4SrvTest, processRequestNoClientAddr) {
+ testDiscoverRequest(DHCPREQUEST,
+ IOAddress("0.0.0.0"),
+ IOAddress("0.0.0.0"));
}
TEST_F(Dhcpv4SrvTest, processRelease) {
- NakedDhcpv4Srv* srv = new NakedDhcpv4Srv();
-
- boost::shared_ptr<Pkt4> pkt(new Pkt4(DHCPRELEASE, 1234));
+ NakedDhcpv4Srv srv;
+ Pkt4Ptr pkt(new Pkt4(DHCPRELEASE, 1234));
// Should not throw
- EXPECT_NO_THROW(
- srv->processRelease(pkt);
- );
-
- delete srv;
+ EXPECT_NO_THROW(srv.processRelease(pkt));
}
TEST_F(Dhcpv4SrvTest, processDecline) {
- NakedDhcpv4Srv* srv = new NakedDhcpv4Srv();
-
- boost::shared_ptr<Pkt4> pkt(new Pkt4(DHCPDECLINE, 1234));
+ NakedDhcpv4Srv srv;
+ Pkt4Ptr pkt(new Pkt4(DHCPDECLINE, 1234));
// Should not throw
- EXPECT_NO_THROW(
- srv->processDecline(pkt);
- );
-
- delete srv;
+ EXPECT_NO_THROW(srv.processDecline(pkt));
}
TEST_F(Dhcpv4SrvTest, processInform) {
- NakedDhcpv4Srv* srv = new NakedDhcpv4Srv();
-
- boost::shared_ptr<Pkt4> pkt(new Pkt4(DHCPINFORM, 1234));
+ NakedDhcpv4Srv srv;
+ Pkt4Ptr pkt(new Pkt4(DHCPINFORM, 1234));
// Should not throw
- EXPECT_NO_THROW(
- srv->processInform(pkt);
- );
+ EXPECT_NO_THROW(srv.processInform(pkt));
// Should return something
- EXPECT_TRUE(srv->processInform(pkt));
+ EXPECT_TRUE(srv.processInform(pkt));
// @todo Implement more reasonable tests before starting
// work on processSomething() method.
-
- delete srv;
}
TEST_F(Dhcpv4SrvTest, serverReceivedPacketName) {
diff --git a/src/bin/dhcp6/config_parser.cc b/src/bin/dhcp6/config_parser.cc
index 76ed228..74012ac 100644
--- a/src/bin/dhcp6/config_parser.cc
+++ b/src/bin/dhcp6/config_parser.cc
@@ -22,6 +22,7 @@
#include <dhcpsrv/cfgmgr.h>
#include <dhcpsrv/dbaccess_parser.h>
#include <dhcpsrv/dhcp_config_parser.h>
+#include <dhcpsrv/dhcp_parsers.h>
#include <dhcpsrv/pool.h>
#include <dhcpsrv/subnet.h>
#include <dhcpsrv/triplet.h>
@@ -49,1432 +50,227 @@ using namespace isc::asiolink;
namespace {
-// Forward declarations of some of the parser classes.
-// They are used to define pointer types for these classes.
-class BooleanParser;
-class StringParser;
-class Uint32Parser;
-
// Pointers to various parser objects.
typedef boost::shared_ptr<BooleanParser> BooleanParserPtr;
typedef boost::shared_ptr<StringParser> StringParserPtr;
typedef boost::shared_ptr<Uint32Parser> Uint32ParserPtr;
-/// @brief Factory method that will create a parser for a given element name
-typedef isc::dhcp::DhcpConfigParser* ParserFactory(const std::string& config_id);
-
-/// @brief Collection of factories that create parsers for specified element names
-typedef std::map<std::string, ParserFactory*> FactoryMap;
-
-/// @brief Storage for parsed boolean values.
-typedef std::map<string, bool> BooleanStorage;
-
-/// @brief Collection of elements that store uint32 values (e.g. renew-timer = 900).
-typedef std::map<string, uint32_t> Uint32Storage;
-
-/// @brief Collection of elements that store string values.
-typedef std::map<string, string> StringStorage;
-
-/// @brief Storage for option definitions.
-typedef OptionSpaceContainer<OptionDefContainer,
- OptionDefinitionPtr> OptionDefStorage;
-
-/// @brief Collection of address pools.
-///
-/// This type is used as intermediate storage, when pools are parsed, but there is
-/// no subnet object created yet to store them.
-typedef std::vector<isc::dhcp::Pool6Ptr> PoolStorage;
-
-/// Collection of containers holding option spaces. Each container within
-/// a particular option space holds so-called option descriptors.
-typedef OptionSpaceContainer<Subnet::OptionContainer,
- Subnet::OptionDescriptor> OptionStorage;
-
-/// @brief Global uint32 parameters that will be used as defaults.
-Uint32Storage uint32_defaults;
-
-/// @brief global string parameters that will be used as defaults.
-StringStorage string_defaults;
-
-/// @brief Global storage for options that will be used as defaults.
-OptionStorage option_defaults;
-
-/// @brief Global storage for option definitions.
-OptionDefStorage option_def_intermediate;
-
-/// @brief a dummy configuration parser
-///
-/// This is a debugging parser. It does not configure anything,
-/// will accept any configuration and will just print it out
-/// on commit. Useful for debugging existing configurations and
-/// adding new ones.
-class DebugParser : public DhcpConfigParser {
-public:
-
- /// @brief Constructor
- ///
- /// See @ref DhcpConfigParser class for details.
- ///
- /// @param param_name name of the parsed parameter
- DebugParser(const std::string& param_name)
- :param_name_(param_name) {
- }
-
- /// @brief builds parameter value
- ///
- /// See @ref DhcpConfigParser class for details.
- ///
- /// @param new_config pointer to the new configuration
- virtual void build(ConstElementPtr new_config) {
- std::cout << "Build for token: [" << param_name_ << "] = ["
- << value_->str() << "]" << std::endl;
- value_ = new_config;
- }
-
- /// @brief Pretends to apply the configuration.
- ///
- /// This is a method required by the base class. It pretends to apply the
- /// configuration, but in fact it only prints the parameter out.
- ///
- /// See @ref DhcpConfigParser class for details.
- virtual void commit() {
- // Debug message. The whole DebugParser class is used only for parser
- // debugging, and is not used in production code. It is very convenient
- // to keep it around. Please do not turn this cout into logger calls.
- std::cout << "Commit for token: [" << param_name_ << "] = ["
- << value_->str() << "]" << std::endl;
- }
-
- /// @brief factory that constructs DebugParser objects
- ///
- /// @param param_name name of the parameter to be parsed
- static DhcpConfigParser* factory(const std::string& param_name) {
- return (new DebugParser(param_name));
- }
-
-private:
- /// name of the parsed parameter
- std::string param_name_;
-
- /// pointer to the actual value of the parameter
- ConstElementPtr value_;
-};
-
-
-/// @brief A boolean value parser.
-///
-/// This parser handles configuration values of the boolean type.
-/// Parsed values are stored in a provided storage. If no storage
-/// is provided then the build function throws an exception.
-class BooleanParser : public DhcpConfigParser {
-public:
- /// @brief Constructor.
- ///
- /// @param param_name name of the parameter.
- BooleanParser(const std::string& param_name)
- : storage_(NULL),
- param_name_(param_name),
- value_(false) {
- // Empty parameter name is invalid.
- if (param_name_.empty()) {
- isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
- << "empty parameter name provided");
- }
- }
-
- /// @brief Parse a boolean value.
- ///
- /// @param value a value to be parsed.
- ///
- /// @throw isc::InvalidOperation if a storage has not been set
- /// prior to calling this function
- /// @throw isc::dhcp::DhcpConfigError if a provided parameter's
- /// name is empty.
- virtual void build(ConstElementPtr value) {
- if (storage_ == NULL) {
- isc_throw(isc::InvalidOperation, "parser logic error:"
- << " storage for the " << param_name_
- << " value has not been set");
- } else if (param_name_.empty()) {
- isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
- << "empty parameter name provided");
- }
- // The Config Manager checks if user specified a
- // valid value for a boolean parameter: True or False.
- // It is then ok to assume that if str() does not return
- // 'true' the value is 'false'.
- value_ = (value->str() == "true") ? true : false;
- }
-
- /// @brief Put a parsed value to the storage.
- virtual void commit() {
- if (storage_ != NULL && !param_name_.empty()) {
- (*storage_)[param_name_] = value_;
- }
- }
-
- /// @brief Create an instance of the boolean parser.
- ///
- /// @param param_name name of the parameter for which the
- /// parser is created.
- static DhcpConfigParser* factory(const std::string& param_name) {
- return (new BooleanParser(param_name));
- }
-
- /// @brief Set the storage for parsed value.
- ///
- /// This function must be called prior to calling build.
- ///
- /// @param storage a pointer to the storage where parsed data
- /// is to be stored.
- void setStorage(BooleanStorage* storage) {
- storage_ = storage;
- }
-
-private:
- /// Pointer to the storage where parsed value is stored.
- BooleanStorage* storage_;
- /// Name of the parameter which value is parsed with this parser.
- std::string param_name_;
- /// Parsed value.
- bool value_;
-};
-
-/// @brief Configuration parser for uint32 parameters
-///
-/// This class is a generic parser that is able to handle any uint32 integer
-/// type. By default it stores the value in external global container
-/// (uint32_defaults). If used in smaller scopes (e.g. to parse parameters
-/// in subnet config), it can be pointed to a different storage, using
-/// setStorage() method. This class follows the parser interface, laid out
-/// in its base class, @ref DhcpConfigParser.
-///
-/// For overview of usability of this generic purpose parser, see
-/// @ref dhcpv6ConfigInherit page.
-///
-/// @todo this class should be turned into the template class which
-/// will handle all uintX_types of data (see ticket #2415).
-class Uint32Parser : public DhcpConfigParser {
-public:
-
- /// @brief constructor for Uint32Parser
- ///
- /// @param param_name name of the configuration parameter being parsed
- Uint32Parser(const std::string& param_name)
- : storage_(&uint32_defaults),
- param_name_(param_name) {
- // Empty parameter name is invalid.
- if (param_name_.empty()) {
- isc_throw(DhcpConfigError, "parser logic error:"
- << "empty parameter name provided");
- }
- }
-
- /// @brief Parses configuration configuration parameter as uint32_t.
- ///
- /// @param value pointer to the content of parsed values
- /// @throw isc::dhcp::DhcpConfigError if failed to parse
- /// the configuration parameter as uint32_t value.
- virtual void build(ConstElementPtr value) {
- if (param_name_.empty()) {
- isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
- << "empty parameter name provided");
- }
-
- bool parse_error = false;
- // Cast the provided value to int64 value to check.
- int64_t int64value = 0;
- try {
- // Parsing the value as a int64 value allows to
- // check if the provided value is within the range
- // of uint32_t (is not negative or greater than
- // maximal uint32_t value).
- int64value = boost::lexical_cast<int64_t>(value->str());
- } catch (const boost::bad_lexical_cast&) {
- parse_error = true;
- }
- if (!parse_error) {
- // Check that the value is not out of bounds.
- if ((int64value < 0) ||
- (int64value > std::numeric_limits<uint32_t>::max())) {
- parse_error = true;
- } else {
- // A value is not out of bounds so let's cast it to
- // the uint32_t type.
- value_ = static_cast<uint32_t>(int64value);
- }
-
- }
- // Invalid value provided.
- if (parse_error) {
- isc_throw(isc::dhcp::DhcpConfigError, "Failed to parse value " << value->str()
- << " as unsigned 32-bit integer.");
- }
- }
-
- /// @brief Stores the parsed uint32_t value in a storage.
- virtual void commit() {
- if (storage_ != NULL) {
- // If a given parameter already exists in the storage we override
- // its value. If it doesn't we insert a new element.
- (*storage_)[param_name_] = value_;
- }
- }
-
- /// @brief Factory that constructs Uint32Parser objects.
- ///
- /// @param param_name name of the parameter to be parsed.
- static DhcpConfigParser* factory(const std::string& param_name) {
- return (new Uint32Parser(param_name));
- }
-
- /// @brief Sets storage for value of this parameter.
- ///
- /// See @ref dhcpv6ConfigInherit for details.
- ///
- /// @param storage pointer to the storage container.
- void setStorage(Uint32Storage* storage) {
- storage_ = storage;
- }
-
-private:
- /// pointer to the storage, where parsed value will be stored
- Uint32Storage* storage_;
- /// name of the parameter to be parsed
- std::string param_name_;
- /// the actual parsed value
- uint32_t value_;
-};
-
-/// @brief Configuration parser for string parameters
-///
-/// This class is a generic parser that is able to handle any string
-/// parameter. By default it stores the value in an external global container
-/// (string_defaults). If used in smaller scopes (e.g. to parse parameters
-/// in subnet config), it can be pointed to a different storage, using the
-/// setStorage() method. This class follows the parser interface, laid out
-/// in its base class, @ref DhcpConfigParser.
-///
-/// For overview of usability of this generic purpose parser, see
-/// @ref dhcpv6ConfigInherit page.
-class StringParser : public DhcpConfigParser {
-public:
-
- /// @brief constructor for StringParser
- ///
- /// @param param_name name of the configuration parameter being parsed
- StringParser(const std::string& param_name)
- : storage_(&string_defaults),
- param_name_(param_name) {
- // Empty parameter name is invalid.
- if (param_name_.empty()) {
- isc_throw(DhcpConfigError, "parser logic error:"
- << "empty parameter name provided");
- }
- }
-
- /// @brief parses parameter value
- ///
- /// Parses configuration parameter's value as string.
- ///
- /// @param value pointer to the content of parsed values
- /// @throws DhcpConfigError if the parsed parameter's name is empty.
- virtual void build(ConstElementPtr value) {
- if (param_name_.empty()) {
- isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
- << "empty parameter name provided");
- }
- value_ = value->str();
- boost::erase_all(value_, "\"");
- }
-
- /// @brief Stores the parsed value in a storage.
- virtual void commit() {
- if (storage_ != NULL && !param_name_.empty()) {
- // If a given parameter already exists in the storage we override
- // its value. If it doesn't we insert a new element.
- (*storage_)[param_name_] = value_;
- }
- }
-
- /// @brief Factory that constructs StringParser objects
- ///
- /// @param param_name name of the parameter to be parsed
- static DhcpConfigParser* factory(const std::string& param_name) {
- return (new StringParser(param_name));
- }
-
- /// @brief Sets storage for value of this parameter.
- ///
- /// See @ref dhcpv6ConfigInherit for details.
- ///
- /// @param storage pointer to the storage container
- void setStorage(StringStorage* storage) {
- storage_ = storage;
- }
-
-private:
- /// Pointer to the storage, where parsed value will be stored
- StringStorage* storage_;
- /// Name of the parameter to be parsed
- std::string param_name_;
- /// The actual parsed value
- std::string value_;
-};
-
-/// @brief parser for interface list definition
-///
-/// This parser handles Dhcp6/interface entry.
-/// It contains a list of network interfaces that the server listens on.
-/// In particular, it can contain an entry called "all" or "any" that
-/// designates all interfaces.
-///
-/// It is useful for parsing Dhcp6/interface parameter.
-class InterfaceListConfigParser : public DhcpConfigParser {
-public:
-
- /// @brief constructor
- ///
- /// As this is a dedicated parser, it must be used to parse
- /// "interface" parameter only. All other types will throw exception.
- ///
- /// @param param_name name of the configuration parameter being parsed
- /// @throw BadValue if supplied parameter name is not "interface"
- InterfaceListConfigParser(const std::string& param_name) {
- if (param_name != "interface") {
- isc_throw(isc::BadValue, "Internal error. Interface configuration "
- "parser called for the wrong parameter: " << param_name);
- }
- }
-
- /// @brief parses parameters value
- ///
- /// Parses configuration entry (list of parameters) and stores it in
- /// storage.
- ///
- /// @param value pointer to the content of parsed values
- virtual void build(ConstElementPtr value) {
- BOOST_FOREACH(ConstElementPtr iface, value->listValue()) {
- interfaces_.push_back(iface->str());
- }
- }
-
- /// @brief commits interfaces list configuration
- virtual void commit() {
- /// @todo: Implement per interface listening. Currently always listening
- /// on all interfaces.
- }
-
- /// @brief factory that constructs InterfaceListConfigParser objects
- ///
- /// @param param_name name of the parameter to be parsed
- static DhcpConfigParser* factory(const std::string& param_name) {
- return (new InterfaceListConfigParser(param_name));
- }
-
-private:
- /// contains list of network interfaces
- vector<string> interfaces_;
-};
-
-/// @brief parser for pool definition
-///
-/// This parser handles pool definitions, i.e. a list of entries of one
-/// of two syntaxes: min-max and prefix/len. Pool6 objects are created
-/// and stored in chosen PoolStorage container.
-///
-/// As there are no default values for pool, setStorage() must be called
-/// before build(). Otherwise an exception will be thrown.
-///
-/// It is useful for parsing Dhcp6/subnet6[X]/pool parameters.
-class PoolParser : public DhcpConfigParser {
-public:
-
- /// @brief constructor.
- PoolParser(const std::string& /*param_name*/)
- : pools_(NULL) {
- // ignore parameter name, it is always Dhcp6/subnet6[X]/pool
- }
-
- /// @brief parses the actual list
- ///
- /// This method parses the actual list of interfaces.
- /// No validation is done at this stage, everything is interpreted as
- /// interface name.
- /// @param pools_list list of pools defined for a subnet
- /// @throw isc::InvalidOperation if storage was not specified
- /// (setStorage() not called)
- void build(ConstElementPtr pools_list) {
-
- // setStorage() should have been called before build
- if (!pools_) {
- isc_throw(isc::InvalidOperation, "parser logic error: no pool storage set,"
- " but pool parser asked to parse pools");
- }
-
- BOOST_FOREACH(ConstElementPtr text_pool, pools_list->listValue()) {
-
- // That should be a single pool representation. It should contain
- // text in the form prefix/len or first - last. Note that spaces
- // are allowed
- string txt = text_pool->stringValue();
-
- // first let's remove any whitespaces
- boost::erase_all(txt, " "); // space
- boost::erase_all(txt, "\t"); // tabulation
-
- // Is this prefix/len notation?
- size_t pos = txt.find("/");
- if (pos != string::npos) {
- IOAddress addr("::");
- uint8_t len = 0;
- try {
- addr = IOAddress(txt.substr(0, pos));
-
- // start with the first character after /
- string prefix_len = txt.substr(pos + 1);
-
- // It is lexically cast to int and then downcast to uint8_t.
- // Direct cast to uint8_t (which is really an unsigned char)
- // will result in interpreting the first digit as output
- // value and throwing exception if length is written on two
- // digits (because there are extra characters left over).
-
- // No checks for values over 128. Range correctness will
- // be checked in Pool6 constructor.
- len = boost::lexical_cast<int>(prefix_len);
- } catch (...) {
- isc_throw(DhcpConfigError, "failed to parse pool "
- "definition: " << text_pool->stringValue());
- }
-
- Pool6Ptr pool(new Pool6(Pool6::TYPE_IA, addr, len));
- local_pools_.push_back(pool);
- continue;
- }
-
- // Is this min-max notation?
- pos = txt.find("-");
- if (pos != string::npos) {
- // using min-max notation
- IOAddress min(txt.substr(0, pos));
- IOAddress max(txt.substr(pos + 1));
-
- Pool6Ptr pool(new Pool6(Pool6::TYPE_IA, min, max));
-
- local_pools_.push_back(pool);
- continue;
- }
-
- isc_throw(DhcpConfigError, "failed to parse pool definition:"
- << text_pool->stringValue() <<
- ". Does not contain - (for min-max) nor / (prefix/len)");
- }
- }
-
- /// @brief sets storage for value of this parameter
- ///
- /// See @ref dhcpv6ConfigInherit for details.
- ///
- /// @param storage pointer to the storage container
- void setStorage(PoolStorage* storage) {
- pools_ = storage;
- }
-
- /// @brief Stores the parsed values in a storage provided
- /// by an upper level parser.
- virtual void commit() {
- if (pools_) {
- // local_pools_ holds the values produced by the build function.
- // At this point parsing should have completed successfuly so
- // we can append new data to the supplied storage.
- pools_->insert(pools_->end(), local_pools_.begin(),
- local_pools_.end());
- }
- }
-
- /// @brief factory that constructs PoolParser objects
- ///
- /// @param param_name name of the parameter to be parsed
- static DhcpConfigParser* factory(const std::string& param_name) {
- return (new PoolParser(param_name));
- }
-
-private:
- /// @brief pointer to the actual Pools storage
- ///
- /// This is typically a storage somewhere in Subnet parser
- /// (an upper level parser).
- PoolStorage* pools_;
- /// A temporary storage for pools configuration. It is a
- /// storage where pools are stored by build function.
- PoolStorage local_pools_;
-};
-
-
-/// @brief Parser for option data value.
+/// @brief Parser for DHCP6 option data value.
///
/// This parser parses configuration entries that specify value of
-/// a single option. These entries include option name, option code
-/// and data carried by the option. The option data can be specified
-/// in one of the two available formats: binary value represented as
-/// a string of hexadecimal digits or a list of comma separated values.
-/// The format being used is controlled by csv-format configuration
-/// parameter. When setting this value to True, the latter format is
-/// used. The subsequent values in the CSV format apply to relevant
-/// option data fields in the configured option. For example the
-/// configuration: "data" : "192.168.2.0, 56, hello world" can be
-/// used to set values for the option comprising IPv4 address,
-/// integer and string data field. Note that order matters. If the
-/// order of values does not match the order of data fields within
-/// an option the configuration will not be accepted. If parsing
-/// is successful then an instance of an option is created and
-/// added to the storage provided by the calling class.
-class OptionDataParser : public DhcpConfigParser {
+/// a single option specific to DHCP6. It provides the DHCP6-specific
+/// implementation of the abstract class OptionDataParser.
+class Dhcp6OptionDataParser : public OptionDataParser {
public:
-
/// @brief Constructor.
///
- /// Class constructor.
- OptionDataParser(const std::string&)
- : options_(NULL),
- // initialize option to NULL ptr
- option_descriptor_(false) { }
-
- /// @brief Parses the single option data.
- ///
- /// This method parses the data of a single option from the configuration.
- /// The option data includes option name, option code and data being
- /// carried by this option. Eventually it creates the instance of the
- /// option.
- ///
- /// @warning setStorage must be called with valid storage pointer prior
- /// to calling this method.
- ///
- /// @param option_data_entries collection of entries that define value
- /// for a particular option.
- /// @throw DhcpConfigError if invalid parameter specified in
- /// the configuration.
- /// @throw isc::InvalidOperation if failed to set storage prior to
- /// calling build.
- virtual void build(ConstElementPtr option_data_entries) {
-
- if (options_ == NULL) {
- isc_throw(isc::InvalidOperation, "Parser logic error: storage must be set before "
- "parsing option data.");
- }
- BOOST_FOREACH(ConfigPair param, option_data_entries->mapValue()) {
- ParserPtr parser;
- if (param.first == "name" || param.first == "data" ||
- param.first == "space") {
- boost::shared_ptr<StringParser>
- name_parser(dynamic_cast<StringParser*>(StringParser::factory(param.first)));
- if (name_parser) {
- name_parser->setStorage(&string_values_);
- parser = name_parser;
- }
- } else if (param.first == "code") {
- boost::shared_ptr<Uint32Parser>
- code_parser(dynamic_cast<Uint32Parser*>(Uint32Parser::factory(param.first)));
- if (code_parser) {
- code_parser->setStorage(&uint32_values_);
- parser = code_parser;
- }
- } else if (param.first == "csv-format") {
- boost::shared_ptr<BooleanParser>
- value_parser(dynamic_cast<BooleanParser*>(BooleanParser::factory(param.first)));
- if (value_parser) {
- value_parser->setStorage(&boolean_values_);
- parser = value_parser;
- }
- } else {
- isc_throw(DhcpConfigError,
- "parser error: option-data parameter not supported: "
- << param.first);
- }
- parser->build(param.second);
- // Before we can create an option we need to get the data from
- // the child parsers. The only way to do it is to invoke commit
- // on them so as they store the values in appropriate storages
- // that this class provided to them. Note that this will not
- // modify values stored in the global storages so the configuration
- // will remain consistent even parsing fails somewhere further on.
- parser->commit();
- }
- // Try to create the option instance.
- createOption();
+ /// @param dummy first param, option names are always "Dhcp6/option-data[n]"
+ /// @param options is the option storage in which to store the parsed option
+ /// upon "commit".
+ /// @param global_context is a pointer to the global context which
+ /// stores global scope parameters, options, option defintions.
+ Dhcp6OptionDataParser(const std::string&, OptionStoragePtr options,
+ ParserContextPtr global_context)
+ :OptionDataParser("", options, global_context) {
}
- /// @brief Commits option value.
- ///
- /// This function adds a new option to the storage or replaces an existing option
- /// with the same code.
- ///
- /// @throw isc::InvalidOperation if failed to set pointer to storage or failed
- /// to call build() prior to commit. If that happens data in the storage
- /// remain un-modified.
- virtual void commit() {
- if (options_ == NULL) {
- isc_throw(isc::InvalidOperation, "parser logic error: storage must be set before "
- "committing option data.");
- } else if (!option_descriptor_.option) {
- // Before we can commit the new option should be configured. If it is not
- // than somebody must have called commit() before build().
- isc_throw(isc::InvalidOperation, "parser logic error: no option has been configured and"
- " thus there is nothing to commit. Has build() been called?");
- }
- uint16_t opt_type = option_descriptor_.option->getType();
- Subnet::OptionContainerPtr options = options_->getItems(option_space_);
- // The getItems() should never return NULL pointer. If there are no
- // options configured for the particular option space a pointer
- // to an empty container should be returned.
- assert(options);
- Subnet::OptionContainerTypeIndex& idx = options->get<1>();
- // Try to find options with the particular option code in the main
- // storage. If found, remove these options because they will be
- // replaced with new one.
- Subnet::OptionContainerTypeRange range =
- idx.equal_range(opt_type);
- if (std::distance(range.first, range.second) > 0) {
- idx.erase(range.first, range.second);
- }
- // Append new option to the main storage.
- options_->addItem(option_descriptor_, option_space_);
- }
-
- /// @brief Set storage for the parser.
- ///
- /// Sets storage for the parser. This storage points to the
- /// vector of options and is used by multiple instances of
- /// OptionDataParser. Each instance creates exactly one object
- /// of dhcp::Option or derived type and appends it to this
- /// storage.
- ///
- /// @param storage pointer to the options storage
- void setStorage(OptionStorage* storage) {
- options_ = storage;
- }
-
-private:
-
- /// @brief Create option instance.
- ///
- /// Creates an instance of an option and adds it to the provided
- /// options storage. If the option data parsed by \ref build function
- /// are invalid or insufficient this function emits an exception.
- ///
- /// @warning this function does not check if options_ storage pointer
- /// is intitialized but this check is not needed here because it is done
- /// in the \ref build function.
+ /// @brief static factory method for instantiating Dhcp4OptionDataParsers
///
- /// @throw DhcpConfigError if parameters provided in the configuration
- /// are invalid.
- void createOption() {
-
- // Option code is held in the uint32_t storage but is supposed to
- // be uint16_t value. We need to check that value in the configuration
- // does not exceed range of uint16_t and is not zero.
- uint32_t option_code = getParam<uint32_t>("code", uint32_values_);
- if (option_code == 0) {
- isc_throw(DhcpConfigError, "option code must not be zero."
- << " Option code '0' is reserved in DHCPv6.");
- } else if (option_code > std::numeric_limits<uint16_t>::max()) {
- isc_throw(DhcpConfigError, "invalid option code '" << option_code
- << "', it must not exceed '"
- << std::numeric_limits<uint16_t>::max() << "'");
- }
- // Check that the option name has been specified, is non-empty and does not
- // contain spaces.
- std::string option_name = getParam<std::string>("name", string_values_);
- if (option_name.empty()) {
- isc_throw(DhcpConfigError, "name of the option with code '"
- << option_code << "' is empty");
- } else if (option_name.find(" ") != std::string::npos) {
- isc_throw(DhcpConfigError, "invalid option name '" << option_name
- << "', space character is not allowed");
- }
-
- std::string option_space = getParam<std::string>("space", string_values_);
- if (!OptionSpace::validateName(option_space)) {
- isc_throw(DhcpConfigError, "invalid option space name '"
- << option_space << "' specified for option '"
- << option_name << "' (code '" << option_code
- << "')");
- }
-
+ /// @param param_name name of the parameter to be parsed.
+ /// @param options storage where the parameter value is to be stored.
+ /// @param global_context is a pointer to the global context which
+ /// stores global scope parameters, options, option defintions.
+ /// @return returns a pointer to a new OptionDataParser. Caller is
+ /// is responsible for deleting it when it is no longer needed.
+ static OptionDataParser* factory(const std::string& param_name,
+ OptionStoragePtr options, ParserContextPtr global_context) {
+ return (new Dhcp6OptionDataParser(param_name, options, global_context));
+ }
+
+
+protected:
+ /// @brief Finds an option definition within the server's option space
+ ///
+ /// Given an option space and an option code, find the correpsonding
+ /// option defintion within the server's option defintion storage.
+ ///
+ /// @param option_space name of the parameter option space
+ /// @param option_code numeric value of the parameter to find
+ /// @return OptionDefintionPtr of the option defintion or an
+ /// empty OptionDefinitionPtr if not found.
+ /// @throw DhcpConfigError if the option space requested is not valid
+ /// for this server.
+ virtual OptionDefinitionPtr findServerSpaceOptionDefinition (
+ std::string& option_space, uint32_t option_code) {
OptionDefinitionPtr def;
if (option_space == "dhcp6" &&
LibDHCP::isStandardOption(Option::V6, option_code)) {
def = LibDHCP::getOptionDef(Option::V6, option_code);
-
} else if (option_space == "dhcp4") {
isc_throw(DhcpConfigError, "'dhcp4' option space name is reserved"
- << " for DHCPv4 server");
- } else {
- // If we are not dealing with a standard option then we
- // need to search for its definition among user-configured
- // options. They are expected to be in the global storage
- // already.
- OptionDefContainerPtr defs = option_def_intermediate.getItems(option_space);
- // The getItems() should never return the NULL pointer. If there are
- // no option definitions for the particular option space a pointer
- // to an empty container should be returned.
- assert(defs);
- const OptionDefContainerTypeIndex& idx = defs->get<1>();
- OptionDefContainerTypeRange range = idx.equal_range(option_code);
- if (std::distance(range.first, range.second) > 0) {
- def = *range.first;
- }
- if (!def) {
- isc_throw(DhcpConfigError, "definition for the option '"
- << option_space << "." << option_name
- << "' having code '" << option_code
- << "' does not exist");
- }
-
- }
-
- // Get option data from the configuration database ('data' field).
- const std::string option_data = getParam<std::string>("data", string_values_);
- const bool csv_format = getParam<bool>("csv-format", boolean_values_);
-
- // Transform string of hexadecimal digits into binary format.
- std::vector<uint8_t> binary;
- std::vector<std::string> data_tokens;
-
- if (csv_format) {
- // If the option data is specified as a string of comma
- // separated values then we need to split this string into
- // individual values - each value will be used to initialize
- // one data field of an option.
- data_tokens = isc::util::str::tokens(option_data, ",");
- } else {
- // Otherwise, the option data is specified as a string of
- // hexadecimal digits that we have to turn into binary format.
- try {
- util::encode::decodeHex(option_data, binary);
- } catch (...) {
- isc_throw(DhcpConfigError, "Parser error: option data is not a valid"
- << " string of hexadecimal digits: " << option_data);
- }
+ << " for DHCPv4 server");
}
- OptionPtr option;
- if (!def) {
- if (csv_format) {
- isc_throw(DhcpConfigError, "the CSV option data format can be"
- " used to specify values for an option that has a"
- " definition. The option with code " << option_code
- << " does not have a definition.");
- }
-
- // @todo We have a limited set of option definitions intiialized at the moment.
- // In the future we want to initialize option definitions for all options.
- // Consequently an error will be issued if an option definition does not exist
- // for a particular option code. For now it is ok to create generic option
- // if definition does not exist.
- OptionPtr option(new Option(Option::V6, static_cast<uint16_t>(option_code),
- binary));
- // The created option is stored in option_descriptor_ class member until the
- // commit stage when it is inserted into the main storage. If an option with the
- // same code exists in main storage already the old option is replaced.
- option_descriptor_.option = option;
- option_descriptor_.persistent = false;
- } else {
-
- // Option name should match the definition. The option name
- // may seem to be redundant but in the future we may want
- // to reference options and definitions using their names
- // and/or option codes so keeping the option name in the
- // definition of option value makes sense.
- if (def->getName() != option_name) {
- isc_throw(DhcpConfigError, "specified option name '"
- << option_name << "' does not match the "
- << "option definition: '" << option_space
- << "." << def->getName() << "'");
- }
-
- // Option definition has been found so let's use it to create
- // an instance of our option.
- try {
- OptionPtr option = csv_format ?
- def->optionFactory(Option::V6, option_code, data_tokens) :
- def->optionFactory(Option::V6, option_code, binary);
- Subnet::OptionDescriptor desc(option, false);
- option_descriptor_.option = option;
- option_descriptor_.persistent = false;
- } catch (const isc::Exception& ex) {
- isc_throw(DhcpConfigError, "option data does not match"
- << " option definition (space: " << option_space
- << ", code: " << option_code << "): "
- << ex.what());
- }
- }
- // All went good, so we can set the option space name.
- option_space_ = option_space;
+ return def;
}
-
- /// Storage for uint32 values (e.g. option code).
- Uint32Storage uint32_values_;
- /// Storage for string values (e.g. option name or data).
- StringStorage string_values_;
- /// Storage for boolean values.
- BooleanStorage boolean_values_;
- /// Pointer to options storage. This storage is provided by
- /// the calling class and is shared by all OptionDataParser objects.
- OptionStorage* options_;
- /// Option descriptor holds newly configured option.
- isc::dhcp::Subnet::OptionDescriptor option_descriptor_;
- /// Option space name where the option belongs to.
- std::string option_space_;
};
-/// @brief Parser for option data values within a subnet.
+/// @brief Parser for IPv4 pool definitions.
///
-/// This parser iterates over all entries that define options
-/// data for a particular subnet and creates a collection of options.
-/// If parsing is successful, all these options are added to the Subnet
-/// object.
-class OptionDataListParser : public DhcpConfigParser {
-public:
-
- /// @brief Constructor.
- ///
- /// Unless otherwise specified, parsed options will be stored in
- /// a global option container (option_default). That storage location
- /// is overriden on a subnet basis.
- OptionDataListParser(const std::string&)
- : options_(&option_defaults), local_options_() { }
-
- /// @brief Parses entries that define options' data for a subnet.
- ///
- /// This method iterates over all entries that define option data
- /// for options within a single subnet and creates options' instances.
- ///
- /// @param option_data_list pointer to a list of options' data sets.
- /// @throw DhcpConfigError if option parsing failed.
- void build(ConstElementPtr option_data_list) {
- BOOST_FOREACH(ConstElementPtr option_value, option_data_list->listValue()) {
- boost::shared_ptr<OptionDataParser> parser(new OptionDataParser("option-data"));
- // options_ member will hold instances of all options thus
- // each OptionDataParser takes it as a storage.
- parser->setStorage(&local_options_);
- // Build the instance of a single option.
- parser->build(option_value);
- // Store a parser as it will be used to commit.
- parsers_.push_back(parser);
- }
- }
-
- /// @brief Set storage for option instances.
- ///
- /// @param storage pointer to options storage.
- void setStorage(OptionStorage* storage) {
- options_ = storage;
- }
-
-
- /// @brief Commit all option values.
- ///
- /// This function invokes commit for all option values.
- void commit() {
- BOOST_FOREACH(ParserPtr parser, parsers_) {
- parser->commit();
- }
- // Parsing was successful and we have all configured
- // options in local storage. We can now replace old values
- // with new values.
- std::swap(local_options_, *options_);
- }
-
- /// @brief Create DhcpDataListParser object
- ///
- /// @param param_name param name.
- ///
- /// @return DhcpConfigParser object.
- static DhcpConfigParser* factory(const std::string& param_name) {
- return (new OptionDataListParser(param_name));
- }
-
- /// Pointer to options instances storage.
- OptionStorage* options_;
- /// Intermediate option storage. This storage is used by
- /// lower level parsers to add new options. Values held
- /// in this storage are assigned to main storage (options_)
- /// if overall parsing was successful.
- OptionStorage local_options_;
- /// Collection of parsers;
- ParserCollection parsers_;
-};
-
-/// @brief Parser for a single option definition.
+/// This is the IPv6 derivation of the PoolParser class and handles pool
+/// definitions, i.e. a list of entries of one of two syntaxes: min-max and
+/// prefix/len for IPv6 pools. Pool6 objects are created and stored in chosen
+/// PoolStorage container.
///
-/// This parser creates an instance of a single option definition.
-class OptionDefParser: DhcpConfigParser {
+/// It is useful for parsing Dhcp6/subnet6[X]/pool parameters.
+class Pool6Parser : public PoolParser {
public:
/// @brief Constructor.
///
- /// This constructor sets the pointer to the option definitions
- /// storage to NULL. It must be set to point to the actual storage
- /// before \ref build is called.
- OptionDefParser(const std::string&)
- : storage_(NULL) {
- }
-
- /// @brief Parses an entry that describes single option definition.
- ///
- /// @param option_def a configuration entry to be parsed.
- ///
- /// @throw DhcpConfigError if parsing was unsuccessful.
- void build(ConstElementPtr option_def) {
- if (storage_ == NULL) {
- isc_throw(DhcpConfigError, "parser logic error: storage must be set"
- " before parsing option definition data");
- }
- // Parse the elements that make up the option definition.
- BOOST_FOREACH(ConfigPair param, option_def->mapValue()) {
- std::string entry(param.first);
- ParserPtr parser;
- if (entry == "name" || entry == "type" || entry == "record-types" ||
- entry == "space" || entry == "encapsulate") {
- StringParserPtr
- str_parser(dynamic_cast<StringParser*>(StringParser::factory(entry)));
- if (str_parser) {
- str_parser->setStorage(&string_values_);
- parser = str_parser;
- }
- } else if (entry == "code") {
- Uint32ParserPtr
- code_parser(dynamic_cast<Uint32Parser*>(Uint32Parser::factory(entry)));
- if (code_parser) {
- code_parser->setStorage(&uint32_values_);
- parser = code_parser;
- }
- } else if (entry == "array") {
- BooleanParserPtr
- array_parser(dynamic_cast<BooleanParser*>(BooleanParser::factory(entry)));
- if (array_parser) {
- array_parser->setStorage(&boolean_values_);
- parser = array_parser;
- }
- } else {
- isc_throw(DhcpConfigError, "invalid parameter: " << entry);
- }
-
- parser->build(param.second);
- parser->commit();
- }
-
- // Create an instance of option definition.
- createOptionDef();
-
- // Get all items we collected so far for the particular option space.
- OptionDefContainerPtr defs = storage_->getItems(option_space_name_);
- // Check if there are any items with option code the same as the
- // one specified for the definition we are now creating.
- const OptionDefContainerTypeIndex& idx = defs->get<1>();
- const OptionDefContainerTypeRange& range =
- idx.equal_range(option_definition_->getCode());
- // If there are any items with this option code already we need
- // to issue an error because we don't allow duplicates for
- // option definitions within an option space.
- if (std::distance(range.first, range.second) > 0) {
- isc_throw(DhcpConfigError, "duplicated option definition for"
- << " code '" << option_definition_->getCode() << "'");
- }
- }
-
- /// @brief Stores the parsed option definition in the data store.
- void commit() {
- if (storage_ && option_definition_ &&
- OptionSpace::validateName(option_space_name_)) {
- storage_->addItem(option_definition_, option_space_name_);
- }
- }
-
- /// @brief Sets a pointer to the data store.
- ///
- /// The newly created instance of an option definition will be
- /// added to the data store given by the argument.
- ///
- /// @param storage pointer to the data store where the option definition
- /// will be added to.
- void setStorage(OptionDefStorage* storage) {
- storage_ = storage;
+ /// @param param_name name of the parameter. Note, it is passed through
+ /// but unused, parameter is currently always "Dhcp6/subnet6[X]/pool"
+ /// @param pools storage container in which to store the parsed pool
+ /// upon "commit"
+ Pool6Parser(const std::string& param_name, PoolStoragePtr pools)
+ :PoolParser(param_name, pools) {
+ }
+
+protected:
+ /// @brief Creates a Pool6 object given a IPv6 prefix and the prefix length.
+ ///
+ /// @param addr is the IPv6 prefix of the pool.
+ /// @param len is the prefix length.
+ /// @param ptype is the type of IPv6 pool (Pool6::Pool6Type). Note this is
+ /// passed in as an int32_t and cast to Pool6Type to accommodate a
+ /// polymorphic interface.
+ /// @return returns a PoolPtr to the new Pool4 object.
+ PoolPtr poolMaker (IOAddress &addr, uint32_t len, int32_t ptype)
+ {
+ return (PoolPtr(new Pool6(static_cast<isc::dhcp::Pool6::Pool6Type>
+ (ptype), addr, len)));
+ }
+
+ /// @brief Creates a Pool6 object given starting and ending IPv6 addresses.
+ ///
+ /// @param min is the first IPv6 address in the pool.
+ /// @param max is the last IPv6 address in the pool.
+ /// @param ptype is the type of IPv6 pool (Pool6::Pool6Type). Note this is
+ /// passed in as an int32_t and cast to Pool6Type to accommodate a
+ /// polymorphic interface.
+ /// @return returns a PoolPtr to the new Pool4 object.
+ PoolPtr poolMaker (IOAddress &min, IOAddress &max, int32_t ptype)
+ {
+ return (PoolPtr(new Pool6(static_cast<isc::dhcp::Pool6::Pool6Type>
+ (ptype), min, max)));
}
-
-private:
-
- /// @brief Create option definition from the parsed parameters.
- void createOptionDef() {
- // Get the option space name and validate it.
- std::string space = getParam<std::string>("space", string_values_);
- if (!OptionSpace::validateName(space)) {
- isc_throw(DhcpConfigError, "invalid option space name '"
- << space << "'");
- }
-
- // Get other parameters that are needed to create the
- // option definition.
- std::string name = getParam<std::string>("name", string_values_);
- uint32_t code = getParam<uint32_t>("code", uint32_values_);
- std::string type = getParam<std::string>("type", string_values_);
- bool array_type = getParam<bool>("array", boolean_values_);
- std::string encapsulates = getParam<std::string>("encapsulate",
- string_values_);
-
- // Create option definition.
- OptionDefinitionPtr def;
- // We need to check if user has set encapsulated option space
- // name. If so, different constructor will be used.
- if (!encapsulates.empty()) {
- // Arrays can't be used together with sub-options.
- if (array_type) {
- isc_throw(DhcpConfigError, "option '" << space << "."
- << "name" << "', comprising an array of data"
- << " fields may not encapsulate any option space");
-
- } else if (encapsulates == space) {
- isc_throw(DhcpConfigError, "option must not encapsulate"
- << " an option space it belongs to: '"
- << space << "." << name << "' is set to"
- << " encapsulate '" << space << "'");
-
- } else {
- def.reset(new OptionDefinition(name, code, type,
- encapsulates.c_str()));
- }
-
- } else {
- def.reset(new OptionDefinition(name, code, type, array_type));
-
- }
-
- // The record-types field may carry a list of comma separated names
- // of data types that form a record.
- std::string record_types = getParam<std::string>("record-types",
- string_values_);
- // Split the list of record types into tokens.
- std::vector<std::string> record_tokens =
- isc::util::str::tokens(record_types, ",");
- // Iterate over each token and add a record type into
- // option definition.
- BOOST_FOREACH(std::string record_type, record_tokens) {
- try {
- boost::trim(record_type);
- if (!record_type.empty()) {
- def->addRecordField(record_type);
- }
- } catch (const Exception& ex) {
- isc_throw(DhcpConfigError, "invalid record type values"
- << " specified for the option definition: "
- << ex.what());
- }
- }
-
- // Check the option definition parameters are valid.
- try {
- def->validate();
- } catch (const isc::Exception& ex) {
- isc_throw(DhcpConfigError, "invalid option definition"
- << " parameters: " << ex.what());
- }
- // Option definition has been created successfully.
- option_space_name_ = space;
- option_definition_ = def;
- }
-
- /// Instance of option definition being created by this parser.
- OptionDefinitionPtr option_definition_;
- /// Name of the space the option definition belongs to.
- std::string option_space_name_;
-
- /// Pointer to a storage where the option definition will be
- /// added when \ref commit is called.
- OptionDefStorage* storage_;
-
- /// Storage for boolean values.
- BooleanStorage boolean_values_;
- /// Storage for string values.
- StringStorage string_values_;
- /// Storage for uint32 values.
- Uint32Storage uint32_values_;
};
-/// @brief Parser for a list of option definitions.
+/// @brief This class parses a single IPv6 subnet.
///
-/// This parser iterates over all configuration entries that define
-/// option definitions and creates instances of these definitions.
-/// If the parsing is successful, the collection of created definitions
-/// is put into the provided storage.
-class OptionDefListParser : DhcpConfigParser {
+/// This is the IPv6 derivation of the SubnetConfigParser class and it parses
+/// the whole subnet definition. It creates parsersfor received configuration
+/// parameters as needed.
+class Subnet6ConfigParser : public SubnetConfigParser {
public:
- /// @brief Constructor.
+ /// @brief Constructor
///
- /// This constructor initializes the pointer to option definitions
- /// storage to NULL value. This pointer has to be set to point to
- /// the actual storage before the \ref build function is called.
- OptionDefListParser(const std::string&) {
+ /// @param ignored first parameter
+ /// stores global scope parameters, options, option defintions.
+ Subnet6ConfigParser(const std::string&)
+ :SubnetConfigParser("", globalContext()) {
}
- /// @brief Parse configuration entries.
- ///
- /// This function parses configuration entries and creates instances
- /// of option definitions.
- ///
- /// @param option_def_list pointer to an element that holds entries
- /// that define option definitions.
- /// @throw DhcpConfigError if configuration parsing fails.
- void build(ConstElementPtr option_def_list) {
- // Clear existing items in the global storage.
- // We are going to replace all of them.
- option_def_intermediate.clearItems();
-
- if (!option_def_list) {
- isc_throw(DhcpConfigError, "parser error: a pointer to a list of"
- << " option definitions is NULL");
- }
-
- BOOST_FOREACH(ConstElementPtr option_def, option_def_list->listValue()) {
- boost::shared_ptr<OptionDefParser>
- parser(new OptionDefParser("single-option-def"));
- parser->setStorage(&option_def_intermediate);
- parser->build(option_def);
- parser->commit();
- }
- }
-
- /// @brief Stores option definitions in the CfgMgr.
+ /// @brief Adds the created subnet to a server's configuration.
+ /// @throw throws Unexpected if dynamic cast fails.
void commit() {
-
- CfgMgr& cfg_mgr = CfgMgr::instance();
-
- cfg_mgr.deleteOptionDefs();
-
- // We need to move option definitions from the temporary
- // storage to the global storage.
- std::list<std::string> space_names =
- option_def_intermediate.getOptionSpaceNames();
- BOOST_FOREACH(std::string space_name, space_names) {
-
- BOOST_FOREACH(OptionDefinitionPtr def,
- *option_def_intermediate.getItems(space_name)) {
- // All option definitions should be initialized to non-NULL
- // values. The validation is expected to be made by the
- // OptionDefParser when creating an option definition.
- assert(def);
- cfg_mgr.addOptionDef(def, space_name);
+ if (subnet_) {
+ Subnet6Ptr sub6ptr = boost::dynamic_pointer_cast<Subnet6>(subnet_);
+ if (!sub6ptr) {
+ // If we hit this, it is a programming error.
+ isc_throw(Unexpected,
+ "Invalid cast in Subnet4ConfigParser::commit");
}
+ isc::dhcp::CfgMgr::instance().addSubnet6(sub6ptr);
}
}
- /// @brief Create an OptionDefListParser object.
- ///
- /// @param param_name configuration entry holding option definitions.
- ///
- /// @return OptionDefListParser object.
- static DhcpConfigParser* factory(const std::string& param_name) {
- return (new OptionDefListParser(param_name));
- }
-
-};
+protected:
-/// @brief this class parses a single subnet
-///
-/// This class parses the whole subnet definition. It creates parsers
-/// for received configuration parameters as needed.
-class Subnet6ConfigParser : public DhcpConfigParser {
-public:
-
- /// @brief constructor
- Subnet6ConfigParser(const std::string& ) {
- // The parameter should always be "subnet", but we don't check
- // against that here in case some wants to reuse this parser somewhere.
- }
-
- /// @brief parses parameter value
+ /// @brief creates parsers for entries in subnet definition
///
- /// @param subnet pointer to the content of subnet definition
+ /// @param config_id name of the entry
///
- /// @throw isc::DhcpConfigError if subnet configuration parsing failed.
- void build(ConstElementPtr subnet) {
-
- BOOST_FOREACH(ConfigPair param, subnet->mapValue()) {
- ParserPtr parser(createSubnet6ConfigParser(param.first));
- // The actual type of the parser is unknown here. We have to discover
- // the parser type here to invoke the corresponding setStorage function
- // on it. We discover parser type by trying to cast the parser to various
- // parser types and checking which one was successful. For this one
- // a setStorage and build methods are invoked.
-
- // Try uint32 type parser.
- if (!buildParser<Uint32Parser, Uint32Storage >(parser, uint32_values_,
- param.second) &&
- // Try string type parser.
- !buildParser<StringParser, StringStorage >(parser, string_values_,
- param.second) &&
- // Try pool parser.
- !buildParser<PoolParser, PoolStorage >(parser, pools_,
- param.second) &&
- // Try option data parser.
- !buildParser<OptionDataListParser, OptionStorage >(parser, options_,
- param.second)) {
- // Appropriate parsers are created in the createSubnet6ConfigParser
- // and they should be limited to those that we check here for. Thus,
- // if we fail to find a matching parser here it is a programming error.
- isc_throw(DhcpConfigError, "failed to find suitable parser");
- }
- }
-
- // In order to create new subnet we need to get the data out
- // of the child parsers first. The only way to do it is to
- // invoke commit on them because it will make them write
- // parsed data into storages we have supplied.
- // Note that triggering commits on child parsers does not
- // affect global data because we supplied pointers to storages
- // local to this object. Thus, even if this method fails
- // later on, the configuration remains consistent.
- BOOST_FOREACH(ParserPtr parser, parsers_) {
- parser->commit();
+ /// @return parser object for specified entry name. Note the caller is
+ /// responsible for deleting the parser created.
+ /// @throw isc::dhcp::DhcpConfigError if trying to create a parser
+ /// for unknown config element
+ DhcpConfigParser* createSubnetConfigParser(const std::string& config_id) {
+ DhcpConfigParser* parser = NULL;
+ if ((config_id.compare("preferred-lifetime") == 0) ||
+ (config_id.compare("valid-lifetime") == 0) ||
+ (config_id.compare("renew-timer") == 0) ||
+ (config_id.compare("rebind-timer") == 0)) {
+ parser = new Uint32Parser(config_id, uint32_values_);
+ } else if ((config_id.compare("subnet") == 0) ||
+ (config_id.compare("interface") == 0) ||
+ (config_id.compare("interface-id") == 0)) {
+ parser = new StringParser(config_id, string_values_);
+ } else if (config_id.compare("pool") == 0) {
+ parser = new Pool6Parser(config_id, pools_);
+ } else if (config_id.compare("option-data") == 0) {
+ parser = new OptionDataListParser(config_id, options_,
+ global_context_,
+ Dhcp6OptionDataParser::factory);
+ } else {
+ isc_throw(NotImplemented,
+ "parser error: Subnet6 parameter not supported: " << config_id);
}
- // Create a subnet.
- createSubnet();
- }
-
- /// @brief Adds the created subnet to a server's configuration.
- void commit() {
- if (subnet_) {
- isc::dhcp::CfgMgr::instance().addSubnet6(subnet_);
- }
+ return (parser);
}
-private:
- /// @brief Set storage for a parser and invoke build.
+ /// @brief Determines if the given option space name and code describe
+ /// a standard option for the DHCP6 server.
///
- /// This helper method casts the provided parser pointer to the specified
- /// type. If the cast is successful it sets the corresponding storage for
- /// this parser, invokes build on it and saves the parser.
- ///
- /// @tparam T parser type to which parser argument should be cast.
- /// @tparam Y storage type for the specified parser type.
- /// @param parser parser on which build must be invoked.
- /// @param storage reference to a storage that will be set for a parser.
- /// @param subnet subnet element read from the configuration and being parsed.
- /// @return true if parser pointer was successfully cast to specialized
- /// parser type provided as Y.
- template<typename T, typename Y>
- bool buildParser(const ParserPtr& parser, Y& storage, const ConstElementPtr& subnet) {
- // We need to cast to T in order to set storage for the parser.
- boost::shared_ptr<T> cast_parser = boost::dynamic_pointer_cast<T>(parser);
- // It is common that this cast is not successful because we try to cast to all
- // supported parser types as we don't know the type of a parser in advance.
- if (cast_parser) {
- // Cast, successful so we go ahead with setting storage and actual parse.
- cast_parser->setStorage(&storage);
- parser->build(subnet);
- parsers_.push_back(parser);
- // We indicate that cast was successful so as the calling function
- // may skip attempts to cast to other parser types and proceed to
- // next element.
- return (true);
- }
- // It was not successful. Indicate that another parser type
- // should be tried.
- return (false);
+ /// @param option_space is the name of the option space to consider
+ /// @param code is the numeric option code to consider
+ /// @return returns true if the space and code are part of the server's
+ /// standard options.
+ bool isServerStdOption(std::string option_space, uint32_t code) {
+ return ((option_space.compare("dhcp6") == 0)
+ && LibDHCP::isStandardOption(Option::V6, code));
}
- /// @brief Append sub-options to an option.
- ///
- /// @param option_space a name of the encapsulated option space.
- /// @param option option instance to append sub-options to.
- void appendSubOptions(const std::string& option_space, OptionPtr& option) {
- // Only non-NULL options are stored in option container.
- // If this option pointer is NULL this is a serious error.
- assert(option);
-
- OptionDefinitionPtr def;
- if (option_space == "dhcp6" &&
- LibDHCP::isStandardOption(Option::V6, option->getType())) {
- def = LibDHCP::getOptionDef(Option::V6, option->getType());
- // Definitions for some of the standard options hasn't been
- // implemented so it is ok to leave here.
- if (!def) {
- return;
- }
- } else {
- const OptionDefContainerPtr defs =
- option_def_intermediate.getItems(option_space);
- const OptionDefContainerTypeIndex& idx = defs->get<1>();
- const OptionDefContainerTypeRange& range =
- idx.equal_range(option->getType());
- // There is no definition so we have to leave.
- if (std::distance(range.first, range.second) == 0) {
- return;
- }
-
- def = *range.first;
-
- // If the definition exists, it must be non-NULL.
- // Otherwise it is a programming error.
- assert(def);
- }
-
- // We need to get option definition for the particular option space
- // and code. This definition holds the information whether our
- // option encapsulates any option space.
- // Get the encapsulated option space name.
- std::string encapsulated_space = def->getEncapsulatedSpace();
- // If option space name is empty it means that our option does not
- // encapsulate any option space (does not include sub-options).
- if (!encapsulated_space.empty()) {
- // Get the sub-options that belong to the encapsulated
- // option space.
- const Subnet::OptionContainerPtr sub_opts =
- option_defaults.getItems(encapsulated_space);
- // Append sub-options to the option.
- BOOST_FOREACH(Subnet::OptionDescriptor desc, *sub_opts) {
- if (desc.option) {
- option->addOption(desc.option);
- }
- }
- }
+ /// @brief Returns the option definition for a given option code from
+ /// the DHCP6 server's standard set of options.
+ /// @param code is the numeric option code of the desired option definition.
+ /// @return returns a pointer the option definition
+ OptionDefinitionPtr getServerStdOptionDefinition (uint32_t code) {
+ return (LibDHCP::getOptionDef(Option::V6, code));
}
- /// @brief Create a new subnet using a data from child parsers.
- ///
- /// @throw isc::dhcp::DhcpConfigError if subnet configuration parsing failed.
- void createSubnet() {
-
- // Find a subnet string.
- StringStorage::const_iterator it = string_values_.find("subnet");
- if (it == string_values_.end()) {
- isc_throw(DhcpConfigError,
- "Mandatory subnet definition in subnet missing");
- }
- // Remove any spaces or tabs.
- string subnet_txt = it->second;
- boost::erase_all(subnet_txt, " ");
- boost::erase_all(subnet_txt, "\t");
- // The subnet format is prefix/len. We are going to extract
- // the prefix portion of a subnet string to create IOAddress
- // object from it. IOAddress will be passed to the Subnet's
- // constructor later on. In order to extract the prefix we
- // need to get all characters preceding "/".
- size_t pos = subnet_txt.find("/");
- if (pos == string::npos) {
- isc_throw(DhcpConfigError,
- "Invalid subnet syntax (prefix/len expected):" << it->second);
- }
-
- // Try to create the address object. It also validates that
- // the address syntax is ok.
- IOAddress addr(subnet_txt.substr(0, pos));
- uint8_t len = boost::lexical_cast<unsigned int>(subnet_txt.substr(pos + 1));
+ /// @brief Issues a DHCP6 server specific warning regarding duplicate subnet
+ /// options.
+ ///
+ /// @param code is the numeric option code of the duplicate option
+ /// @param addr is the subnet address
+ /// @todo A means to know the correct logger and perhaps a common
+ /// message would allow this message to be emitted by the base class.
+ virtual void duplicate_option_warning(uint32_t code,
+ isc::asiolink::IOAddress& addr) {
+ LOG_WARN(dhcp6_logger, DHCP6_CONFIG_OPTION_DUPLICATE)
+ .arg(code).arg(addr.toText());
+ }
+ /// @brief Instantiates the IPv6 Subnet based on a given IPv6 address
+ /// and prefix length.
+ ///
+ /// @param addr is IPv6 prefix of the subnet.
+ /// @param len is the prefix length
+ void initSubnet(isc::asiolink::IOAddress addr, uint8_t len) {
// Get all 'time' parameters using inheritance.
// If the subnet-specific value is defined then use it, else
// use the global value. The global value must always be
@@ -1485,187 +281,59 @@ private:
Triplet<uint32_t> pref = getParam("preferred-lifetime");
Triplet<uint32_t> valid = getParam("valid-lifetime");
- // Get interface name. If it is defined, then the subnet is available
- // directly over specified network interface.
+ // Get interface-id option content. For now we support string
+ // represenation only
+ std::string ifaceid;
+ try {
+ ifaceid = string_values_->getParam("interface-id");
+ } catch (const DhcpConfigError &) {
+ // interface-id is not mandatory
+ }
- string iface;
- StringStorage::const_iterator iface_iter = string_values_.find("interface");
- if (iface_iter != string_values_.end()) {
- iface = iface_iter->second;
+ // Specifying both interface for locally reachable subnets and
+ // interface id for relays is mutually exclusive. Need to test for
+ // this condition.
+ if (!ifaceid.empty()) {
+ std::string iface;
+ try {
+ iface = string_values_->getParam("interface");
+ } catch (const DhcpConfigError &) {
+ // iface not mandatory
+ }
+
+ if (!iface.empty()) {
+ isc_throw(isc::dhcp::DhcpConfigError,
+ "parser error: interface (defined for locally reachable "
+ "subnets) and interface-id (defined for subnets reachable"
+ " via relays) cannot be defined at the same time for "
+ "subnet " << addr.toText() << "/" << (int)len);
+ }
}
- /// @todo: Convert this to logger once the parser is working reliably
stringstream tmp;
- tmp << addr.toText() << "/" << (int)len
+ tmp << addr.toText() << "/" << static_cast<int>(len)
<< " with params t1=" << t1 << ", t2=" << t2 << ", pref="
<< pref << ", valid=" << valid;
LOG_INFO(dhcp6_logger, DHCP6_CONFIG_NEW_SUBNET).arg(tmp.str());
// Create a new subnet.
- subnet_.reset(new Subnet6(addr, len, t1, t2, pref, valid));
-
- // Add pools to it.
- for (PoolStorage::iterator it = pools_.begin(); it != pools_.end(); ++it) {
- subnet_->addPool(*it);
- }
-
- // Configure interface, if defined
- if (!iface.empty()) {
- if (!IfaceMgr::instance().getIface(iface)) {
- isc_throw(DhcpConfigError, "Specified interface name " << iface
- << " for subnet " << subnet_->toText() << " is not present"
- << " in the system.");
- }
-
- subnet_->setIface(iface);
- }
-
- // We are going to move configured options to the Subnet object.
- // Configured options reside in the container where options
- // are grouped by space names. Thus we need to get all space names
- // and iterate over all options that belong to them.
- std::list<std::string> space_names = options_.getOptionSpaceNames();
- BOOST_FOREACH(std::string option_space, space_names) {
- // Get all options within a particular option space.
- BOOST_FOREACH(Subnet::OptionDescriptor desc,
- *options_.getItems(option_space)) {
- // The pointer should be non-NULL. The validation is expected
- // to be performed by the OptionDataParser before adding an
- // option descriptor to the container.
- assert(desc.option);
- // We want to check whether an option with the particular
- // option code has been already added. If so, we want
- // to issue a warning.
- Subnet::OptionDescriptor existing_desc =
- subnet_->getOptionDescriptor("option_space",
- desc.option->getType());
- if (existing_desc.option) {
- LOG_WARN(dhcp6_logger, DHCP6_CONFIG_OPTION_DUPLICATE)
- .arg(desc.option->getType()).arg(addr.toText());
- }
- // Add sub-options (if any).
- appendSubOptions(option_space, desc.option);
- // In any case, we add the option to the subnet.
- subnet_->addOption(desc.option, false, option_space);
- }
- }
-
- // Check all global options and add them to the subnet object if
- // they have been configured in the global scope. If they have been
- // configured in the subnet scope we don't add global option because
- // the one configured in the subnet scope always takes precedence.
- space_names = option_defaults.getOptionSpaceNames();
- BOOST_FOREACH(std::string option_space, space_names) {
- // Get all global options for the particular option space.
- BOOST_FOREACH(Subnet::OptionDescriptor desc,
- *option_defaults.getItems(option_space)) {
- // The pointer should be non-NULL. The validation is expected
- // to be performed by the OptionDataParser before adding an
- // option descriptor to the container.
- assert(desc.option);
- // Check if the particular option has been already added.
- // This would mean that it has been configured in the
- // subnet scope. Since option values configured in the
- // subnet scope take precedence over globally configured
- // values we don't add option from the global storage
- // if there is one already.
- Subnet::OptionDescriptor existing_desc =
- subnet_->getOptionDescriptor(option_space, desc.option->getType());
- if (!existing_desc.option) {
- // Add sub-options (if any).
- appendSubOptions(option_space, desc.option);
-
- subnet_->addOption(desc.option, false, option_space);
- }
- }
- }
- }
-
- /// @brief creates parsers for entries in subnet definition
- ///
- /// @param config_id name od the entry
- ///
- /// @return parser object for specified entry name
- /// @throw isc::dhcp::DhcpConfigError if trying to create a parser
- /// for unknown config element
- DhcpConfigParser* createSubnet6ConfigParser(const std::string& config_id) {
- FactoryMap factories;
-
- factories["preferred-lifetime"] = Uint32Parser::factory;
- factories["valid-lifetime"] = Uint32Parser::factory;
- factories["renew-timer"] = Uint32Parser::factory;
- factories["rebind-timer"] = Uint32Parser::factory;
- factories["subnet"] = StringParser::factory;
- factories["pool"] = PoolParser::factory;
- factories["option-data"] = OptionDataListParser::factory;
- factories["interface"] = StringParser::factory;
-
- FactoryMap::iterator f = factories.find(config_id);
- if (f == factories.end()) {
- // Used for debugging only.
- // return new DebugParser(config_id);
-
- isc_throw(isc::dhcp::DhcpConfigError,
- "parser error: subnet6 parameter not supported: "
- << config_id);
- }
- return (f->second(config_id));
- }
+ Subnet6* subnet6 = new Subnet6(addr, len, t1, t2, pref, valid);
- /// @brief Returns value for a given parameter (after using inheritance)
- ///
- /// This method implements inheritance. For a given parameter name, it first
- /// checks if there is a global value for it and overwrites it with specific
- /// value if such value was defined in subnet.
- ///
- /// @param name name of the parameter
- /// @return triplet with the parameter name
- /// @throw DhcpConfigError when requested parameter is not present
- isc::dhcp::Triplet<uint32_t> getParam(const std::string& name) {
- uint32_t value = 0;
- bool found = false;
- Uint32Storage::iterator global = uint32_defaults.find(name);
- if (global != uint32_defaults.end()) {
- value = global->second;
- found = true;
+ // Configure interface-id for remote interfaces, if defined
+ if (!ifaceid.empty()) {
+ OptionBuffer tmp(ifaceid.begin(), ifaceid.end());
+ OptionPtr opt(new Option(Option::V6, D6O_INTERFACE_ID, tmp));
+ subnet6->setInterfaceId(opt);
}
- Uint32Storage::iterator local = uint32_values_.find(name);
- if (local != uint32_values_.end()) {
- value = local->second;
- found = true;
- }
-
- if (found) {
- return (isc::dhcp::Triplet<uint32_t>(value));
- } else {
- isc_throw(isc::dhcp::DhcpConfigError, "Mandatory parameter " << name
- << " missing (no global default and no subnet-"
- << "specific value)");
- }
+ subnet_.reset(subnet6);
}
- /// storage for subnet-specific uint32 values
- Uint32Storage uint32_values_;
-
- /// storage for subnet-specific integer values
- StringStorage string_values_;
-
- /// storage for pools belonging to this subnet
- PoolStorage pools_;
-
- /// storage for options belonging to this subnet
- OptionStorage options_;
-
- /// parsers are stored here
- ParserCollection parsers_;
-
- /// Pointer to the created subnet object.
- isc::dhcp::Subnet6Ptr subnet_;
};
-/// @brief this class parses a list of subnets
+
+/// @brief this class parses a list of DHCP6 subnets
///
/// This is a wrapper parser that handles the whole list of Subnet6
/// definitions. It iterates over all entries and creates Subnet6ConfigParser
@@ -1675,8 +343,9 @@ public:
/// @brief constructor
///
+ /// @param dummy first argument, always ignored. All parsers accept a
+ /// string parameter "name" as their first argument.
Subnets6ListConfigParser(const std::string&) {
- /// parameter name is ignored
}
/// @brief parses contents of the list
@@ -1686,12 +355,7 @@ public:
///
/// @param subnets_list pointer to a list of IPv6 subnets
void build(ConstElementPtr subnets_list) {
-
- // No need to define FactoryMap here. There's only one type
- // used: Subnet6ConfigParser
-
BOOST_FOREACH(ConstElementPtr subnet, subnets_list->listValue()) {
-
ParserPtr parser(new Subnet6ConfigParser("subnet"));
parser->build(subnet);
subnets_.push_back(parser);
@@ -1701,8 +365,8 @@ public:
/// @brief commits subnets definitions.
///
- /// Iterates over all Subnet6 parsers. Each parser contains definitions
- /// of a single subnet and its parameters and commits each subnet separately.
+ /// Iterates over all Subnet6 parsers. Each parser contains definitions of
+ /// a single subnet and its parameters and commits each subnet separately.
void commit() {
// @todo: Implement more subtle reconfiguration than toss
// the old one and replace with the new one.
@@ -1739,53 +403,63 @@ namespace dhcp {
///
/// @param config_id pointer to received global configuration entry
/// @return parser for specified global DHCPv6 parameter
-/// @throw NotImplemented if trying to create a parser for unknown config element
-DhcpConfigParser* createGlobalDhcpConfigParser(const std::string& config_id) {
- FactoryMap factories;
-
- factories["preferred-lifetime"] = Uint32Parser::factory;
- factories["valid-lifetime"] = Uint32Parser::factory;
- factories["renew-timer"] = Uint32Parser::factory;
- factories["rebind-timer"] = Uint32Parser::factory;
- factories["interface"] = InterfaceListConfigParser::factory;
- factories["subnet6"] = Subnets6ListConfigParser::factory;
- factories["option-data"] = OptionDataListParser::factory;
- factories["option-def"] = OptionDefListParser::factory;
- factories["version"] = StringParser::factory;
- factories["lease-database"] = DbAccessParser::factory;
-
- FactoryMap::iterator f = factories.find(config_id);
- if (f == factories.end()) {
- // Used for debugging only.
- // return new DebugParser(config_id);
-
+/// @throw NotImplemented if trying to create a parser for unknown config
+/// element
+DhcpConfigParser* createGlobal6DhcpConfigParser(const std::string& config_id) {
+ DhcpConfigParser* parser = NULL;
+ if ((config_id.compare("preferred-lifetime") == 0) ||
+ (config_id.compare("valid-lifetime") == 0) ||
+ (config_id.compare("renew-timer") == 0) ||
+ (config_id.compare("rebind-timer") == 0)) {
+ parser = new Uint32Parser(config_id,
+ globalContext()->uint32_values_);
+ } else if (config_id.compare("interface") == 0) {
+ parser = new InterfaceListConfigParser(config_id);
+ } else if (config_id.compare("subnet6") == 0) {
+ parser = new Subnets6ListConfigParser(config_id);
+ } else if (config_id.compare("option-data") == 0) {
+ parser = new OptionDataListParser(config_id,
+ globalContext()->options_,
+ globalContext(),
+ Dhcp6OptionDataParser::factory);
+ } else if (config_id.compare("option-def") == 0) {
+ parser = new OptionDefListParser(config_id,
+ globalContext()->option_defs_);
+ } else if (config_id.compare("version") == 0) {
+ parser = new StringParser(config_id,
+ globalContext()->string_values_);
+ } else if (config_id.compare("lease-database") == 0) {
+ parser = new DbAccessParser(config_id);
+ } else {
isc_throw(NotImplemented,
- "Parser error: Global configuration parameter not supported: "
- << config_id);
+ "Parser error: Global configuration parameter not supported: "
+ << config_id);
}
- return (f->second(config_id));
+
+ return (parser);
}
-ConstElementPtr
-configureDhcp6Server(Dhcpv6Srv&, ConstElementPtr config_set) {
+isc::data::ConstElementPtr
+configureDhcp6Server(Dhcpv6Srv&, isc::data::ConstElementPtr config_set) {
if (!config_set) {
ConstElementPtr answer = isc::config::createAnswer(1,
string("Can't parse NULL config"));
return (answer);
}
- /// @todo: append most essential info here (like "2 new subnets configured")
+ /// @todo: Append most essential info here (like "2 new subnets configured")
string config_details;
- LOG_DEBUG(dhcp6_logger, DBG_DHCP6_COMMAND, DHCP6_CONFIG_START).arg(config_set->str());
+ LOG_DEBUG(dhcp6_logger, DBG_DHCP6_COMMAND,
+ DHCP6_CONFIG_START).arg(config_set->str());
// Some of the values specified in the configuration depend on
- // other values. Typically, the values in the subnet4 structure
+ // other values. Typically, the values in the subnet6 structure
// depend on the global values. Also, option values configuration
// must be performed after the option definitions configurations.
// Thus we group parsers and will fire them in the right order:
- // all parsers other than subnet4 and option-data parser,
- // option-data parser, subnet4 parser.
+ // all parsers other than subnet6 and option-data parser,
+ // option-data parser, subnet6 parser.
ParserCollection independent_parsers;
ParserPtr subnet_parser;
ParserPtr option_parser;
@@ -1797,10 +471,7 @@ configureDhcp6Server(Dhcpv6Srv&, ConstElementPtr config_set) {
// parsing operation fails after the global storage has been
// modified. We need to preserve the original global data here
// so as we can rollback changes when an error occurs.
- Uint32Storage uint32_local(uint32_defaults);
- StringStorage string_local(string_defaults);
- OptionStorage option_local(option_defaults);
- OptionDefStorage option_def_local(option_def_intermediate);
+ ParserContext original_context(*globalContext());
// answer will hold the result.
ConstElementPtr answer;
@@ -1817,15 +488,13 @@ configureDhcp6Server(Dhcpv6Srv&, ConstElementPtr config_set) {
const std::map<std::string, ConstElementPtr>& values_map =
config_set->mapValue();
BOOST_FOREACH(config_pair, values_map) {
- ParserPtr parser(createGlobalDhcpConfigParser(config_pair.first));
+ ParserPtr parser(createGlobal6DhcpConfigParser(config_pair.first));
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_PARSER_CREATED)
.arg(config_pair.first);
if (config_pair.first == "subnet6") {
subnet_parser = parser;
-
} else if (config_pair.first == "option-data") {
option_parser = parser;
-
} else {
// Those parsers should be started before other
// parsers so we can call build straight away.
@@ -1898,10 +567,7 @@ configureDhcp6Server(Dhcpv6Srv&, ConstElementPtr config_set) {
// Rollback changes as the configuration parsing failed.
if (rollback) {
- std::swap(uint32_defaults, uint32_local);
- std::swap(string_defaults, string_local);
- std::swap(option_defaults, option_local);
- std::swap(option_def_intermediate, option_def_local);
+ globalContext().reset(new ParserContext(original_context));
return (answer);
}
@@ -1912,5 +578,10 @@ configureDhcp6Server(Dhcpv6Srv&, ConstElementPtr config_set) {
return (answer);
}
+ParserContextPtr& globalContext() {
+ static ParserContextPtr global_context_ptr(new ParserContext(Option::V6));
+ return (global_context_ptr);
+}
+
}; // end of isc::dhcp namespace
}; // end of isc namespace
diff --git a/src/bin/dhcp6/config_parser.h b/src/bin/dhcp6/config_parser.h
index 6d7a807..2dce79e 100644
--- a/src/bin/dhcp6/config_parser.h
+++ b/src/bin/dhcp6/config_parser.h
@@ -20,6 +20,8 @@
#include <cc/data.h>
#include <exceptions/exceptions.h>
+#include <dhcpsrv/dhcp_parsers.h>
+
#include <string>
namespace isc {
@@ -29,9 +31,9 @@ class Dhcpv6Srv;
/// @brief Configures DHCPv6 server
///
-/// This function is called every time a new configuration is received. The extra
-/// parameter is a reference to DHCPv6 server component. It is currently not used
-/// and CfgMgr::instance() is accessed instead.
+/// This function is called every time a new configuration is received. The
+/// extra parameter is a reference to DHCPv6 server component. It is currently
+/// not used and CfgMgr::instance() is accessed instead.
///
/// This method does not throw. It catches all exceptions and returns them as
/// reconfiguration statuses. It may return the following response codes:
@@ -47,6 +49,11 @@ class Dhcpv6Srv;
isc::data::ConstElementPtr
configureDhcp6Server(Dhcpv6Srv& server, isc::data::ConstElementPtr config_set);
+/// @brief Returns the global context
+///
+/// @returns a reference to the global context
+ParserContextPtr& globalContext();
+
}; // end of isc::dhcp namespace
}; // end of isc namespace
diff --git a/src/bin/dhcp6/ctrl_dhcp6_srv.h b/src/bin/dhcp6/ctrl_dhcp6_srv.h
index 908304d..ffd43c3 100644
--- a/src/bin/dhcp6/ctrl_dhcp6_srv.h
+++ b/src/bin/dhcp6/ctrl_dhcp6_srv.h
@@ -95,7 +95,7 @@ protected:
/// @brief A dummy configuration handler that always returns success.
///
/// This configuration handler does not perform configuration
- /// parsing and always returns success. A dummy hanlder should
+ /// parsing and always returns success. A dummy handler should
/// be installed using \ref isc::config::ModuleCCSession ctor
/// to get the initial configuration. This initial configuration
/// comprises values for only those elements that were modified
diff --git a/src/bin/dhcp6/dhcp6.spec b/src/bin/dhcp6/dhcp6.spec
index 1129aec..bb5de0a 100644
--- a/src/bin/dhcp6/dhcp6.spec
+++ b/src/bin/dhcp6/dhcp6.spec
@@ -199,6 +199,12 @@
"item_default": ""
},
+ { "item_name": "interface-id",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ },
+
{ "item_name": "renew-timer",
"item_type": "integer",
"item_optional": false,
diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc
index 4c19e74..86f4d8f 100644
--- a/src/bin/dhcp6/dhcp6_srv.cc
+++ b/src/bin/dhcp6/dhcp6_srv.cc
@@ -329,7 +329,7 @@ Dhcpv6Srv::generateServerID() {
// we will grow knobs to selectively turn them on or off. Also,
// this code is used only *once* during first start on a new machine
// and then server-id is stored. (or at least it will be once
- // DUID storage is implemente
+ // DUID storage is implemented)
// I wish there was a this_is_a_real_physical_interface flag...
@@ -403,8 +403,13 @@ Dhcpv6Srv::copyDefaultOptions(const Pkt6Ptr& question, Pkt6Ptr& answer) {
if (clientid) {
answer->addOption(clientid);
}
+ /// @todo: Should throw if there is no client-id (except anonymous INF-REQUEST)
+
+ // If this is a relayed message, we need to copy relay information
+ if (!question->relay_info_.empty()) {
+ answer->copyRelayInfo(question);
+ }
- // TODO: Should throw if there is no client-id (except anonymous INF-REQUEST)
}
void
@@ -523,16 +528,37 @@ Dhcpv6Srv::sanityCheck(const Pkt6Ptr& pkt, RequirementLevel clientid,
Subnet6Ptr
Dhcpv6Srv::selectSubnet(const Pkt6Ptr& question) {
- /// @todo: pass interface information only if received direct (non-relayed) message
+ Subnet6Ptr subnet;
- // Try to find a subnet if received packet from a directly connected client
- Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(question->getIface());
- if (subnet) {
- return (subnet);
- }
+ if (question->relay_info_.empty()) {
+ // This is a direct (non-relayed) message
+
+ // Try to find a subnet if received packet from a directly connected client
+ subnet = CfgMgr::instance().getSubnet6(question->getIface());
+ if (!subnet) {
+ // If no subnet was found, try to find it based on remote address
+ subnet = CfgMgr::instance().getSubnet6(question->getRemoteAddr());
+ }
+ } else {
- // If no subnet was found, try to find it based on remote address
- subnet = CfgMgr::instance().getSubnet6(question->getRemoteAddr());
+ // This is a relayed message
+ OptionPtr interface_id = question->getAnyRelayOption(D6O_INTERFACE_ID,
+ Pkt6::RELAY_GET_FIRST);
+ if (interface_id) {
+ subnet = CfgMgr::instance().getSubnet6(interface_id);
+ }
+
+ if (!subnet) {
+ // If no interface-id was specified (or not configured on server), let's
+ // try address matching
+ IOAddress link_addr = question->relay_info_.back().linkaddr_;
+
+ // if relay filled in link_addr field, then let's use it
+ if (link_addr != IOAddress("::")) {
+ subnet = CfgMgr::instance().getSubnet6(link_addr);
+ }
+ }
+ }
return (subnet);
}
diff --git a/src/bin/dhcp6/dhcp6_srv.h b/src/bin/dhcp6/dhcp6_srv.h
index bdcb560..c7b1f0f 100644
--- a/src/bin/dhcp6/dhcp6_srv.h
+++ b/src/bin/dhcp6/dhcp6_srv.h
@@ -40,7 +40,7 @@ namespace dhcp {
/// packets, processes them, manages leases assignment and generates
/// appropriate responses.
///
-/// @note Only one instance of this class is instantated as it encompasses
+/// @note Only one instance of this class is instantiated as it encompasses
/// the whole operation of the server. Nothing, however, enforces the
/// singleton status of the object.
class Dhcpv6Srv : public boost::noncopyable {
@@ -69,7 +69,7 @@ public:
/// @brief Destructor. Used during DHCPv6 service shutdown.
virtual ~Dhcpv6Srv();
- /// @brief Returns server-intentifier option.
+ /// @brief Returns server-indentifier option.
///
/// @return server-id option
OptionPtr getServerID() { return serverid_; }
diff --git a/src/bin/dhcp6/tests/Makefile.am b/src/bin/dhcp6/tests/Makefile.am
index feb4bfa..a2e2ed0 100644
--- a/src/bin/dhcp6/tests/Makefile.am
+++ b/src/bin/dhcp6/tests/Makefile.am
@@ -7,7 +7,7 @@ EXTRA_DIST = $(PYTESTS)
# about python not being able to load liblog library.
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/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/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:$$$(ENV_LIBRARY_PATH)
endif
# test using command-line arguments, so use check-local target instead of TESTS
diff --git a/src/bin/dhcp6/tests/config_parser_unittest.cc b/src/bin/dhcp6/tests/config_parser_unittest.cc
index 63fc980..d9c5859 100644
--- a/src/bin/dhcp6/tests/config_parser_unittest.cc
+++ b/src/bin/dhcp6/tests/config_parser_unittest.cc
@@ -88,7 +88,7 @@ public:
/// option value. These parameters are: "name", "code", "data" and
/// "csv-format".
///
- /// @param param_value string holiding option parameter value to be
+ /// @param param_value string holding option parameter value to be
/// injected into the configuration string.
/// @param parameter name of the parameter to be configured with
/// param value.
@@ -277,13 +277,13 @@ public:
expected_data_len));
}
- int rcode_;
- Dhcpv6Srv srv_;
+ int rcode_; ///< return core (see @ref isc::config::parseAnswer)
+ Dhcpv6Srv srv_; ///< instance of the Dhcp6Srv used during tests
- ConstElementPtr comment_;
+ ConstElementPtr comment_; ///< comment (see @ref isc::config::parseAnswer)
- string valid_iface_;
- string bogus_iface_;
+ string valid_iface_; ///< name of a valid network interface (present in system)
+ string bogus_iface_; ///< name of a invalid network interface (not present in system)
};
// Goal of this test is a verification if a very simple config update
@@ -500,6 +500,104 @@ TEST_F(Dhcp6ParserTest, interfaceGlobal) {
EXPECT_EQ(1, rcode_);
}
+
+// This test checks if it is possible to define a subnet with an
+// interface-id option defined.
+TEST_F(Dhcp6ParserTest, subnetInterfaceId) {
+
+ const string valid_interface_id = "foobar";
+ const string bogus_interface_id = "blah";
+
+ // There should be at least one interface
+
+ const string config = "{ "
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"pool\": [ \"2001:db8:1::1 - 2001:db8:1::ffff\" ],"
+ " \"interface-id\": \"" + valid_interface_id + "\","
+ " \"subnet\": \"2001:db8:1::/64\" } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ElementPtr json = Element::fromJSON(config);
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+
+ // Returned value should be 0 (configuration success)
+ ASSERT_TRUE(status);
+ comment_ = parseAnswer(rcode_, status);
+ EXPECT_EQ(0, rcode_);
+
+ // Try to get a subnet based on bogus interface-id option
+ OptionBuffer tmp(bogus_interface_id.begin(), bogus_interface_id.end());
+ OptionPtr ifaceid(new Option(Option::V6, D6O_INTERFACE_ID, tmp));
+ Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(ifaceid);
+ EXPECT_FALSE(subnet);
+
+ // Now try to get subnet for valid interface-id value
+ tmp = OptionBuffer(valid_interface_id.begin(), valid_interface_id.end());
+ ifaceid.reset(new Option(Option::V6, D6O_INTERFACE_ID, tmp));
+ subnet = CfgMgr::instance().getSubnet6(ifaceid);
+ ASSERT_TRUE(subnet);
+ EXPECT_TRUE(ifaceid->equal(subnet->getInterfaceId()));
+}
+
+
+// This test checks if it is not allowed to define global interface
+// parameter.
+TEST_F(Dhcp6ParserTest, interfaceIdGlobal) {
+
+ const string config = "{ \"interface\": [ \"all\" ],"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"interface-id\": \"foobar\"," // Not valid. Can be defined in subnet only
+ "\"subnet6\": [ { "
+ " \"pool\": [ \"2001:db8:1::1 - 2001:db8:1::ffff\" ],"
+ " \"subnet\": \"2001:db8:1::/64\" } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ElementPtr json = Element::fromJSON(config);
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+
+ // Returned value should be 1 (parse error)
+ ASSERT_TRUE(status);
+ comment_ = parseAnswer(rcode_, status);
+ EXPECT_EQ(1, rcode_);
+}
+
+// This test checks if it is not possible to define a subnet with an
+// interface (i.e. local subnet) and interface-id (remote subnet) defined.
+TEST_F(Dhcp6ParserTest, subnetInterfaceAndInterfaceId) {
+
+ const string config = "{ \"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"pool\": [ \"2001:db8:1::1 - 2001:db8:1::ffff\" ],"
+ " \"interface\": \"" + valid_iface_ + "\","
+ " \"interface-id\": \"foobar\","
+ " \"subnet\": \"2001:db8:1::/64\" } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ElementPtr json = Element::fromJSON(config);
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+
+ // Returned value should be 1 (configuration error)
+ ASSERT_TRUE(status);
+ comment_ = parseAnswer(rcode_, status);
+ EXPECT_EQ(1, rcode_);
+
+}
+
+
+
// Test verifies that a subnet with pool values that do not belong to that
// pool are rejected.
TEST_F(Dhcp6ParserTest, poolOutOfSubnet) {
diff --git a/src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc b/src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc
index ea359fc..9209de6 100644
--- a/src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc
+++ b/src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -18,6 +18,7 @@
#include <dhcp6/ctrl_dhcp6_srv.h>
#include <config/ccsession.h>
+#include <boost/scoped_ptr.hpp>
#include <gtest/gtest.h>
#include <iostream>
@@ -52,12 +53,12 @@ public:
TEST_F(CtrlDhcpv6SrvTest, commands) {
- ControlledDhcpv6Srv* srv = NULL;
- ASSERT_NO_THROW({
- srv = new ControlledDhcpv6Srv(DHCP6_SERVER_PORT + 10000);
- });
+ boost::scoped_ptr<ControlledDhcpv6Srv> srv;
+ ASSERT_NO_THROW(
+ srv.reset(new ControlledDhcpv6Srv(DHCP6_SERVER_PORT + 10000))
+ );
- // use empty parameters list
+ // Use empty parameters list
ElementPtr params(new isc::data::MapElement());
int rcode = -1;
@@ -78,10 +79,7 @@ TEST_F(CtrlDhcpv6SrvTest, commands) {
// case 3: send shutdown command with 1 parameter: pid
result = ControlledDhcpv6Srv::execDhcpv6ServerCommand("shutdown", params);
comment = parseAnswer(rcode, result);
- EXPECT_EQ(0, rcode); // expect success
-
-
- delete srv;
+ EXPECT_EQ(0, rcode); // Expect success
}
} // end of anonymous namespace
diff --git a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
index b742a13..328a3c5 100644
--- a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
+++ b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
@@ -79,20 +79,12 @@ public:
static const char* DUID_FILE = "server-id-test.txt";
-class Dhcpv6SrvTest : public ::testing::Test {
+// test fixture for any tests requiring blank/empty configuration
+// serves as base class for additional tests
+class NakedDhcpv6SrvTest : public ::testing::Test {
public:
- /// Name of the server-id file (used in server-id tests)
-
- // these are empty for now, but let's keep them around
- Dhcpv6SrvTest() : rcode_(-1) {
- subnet_ = Subnet6Ptr(new Subnet6(IOAddress("2001:db8:1::"), 48, 1000,
- 2000, 3000, 4000));
- pool_ = Pool6Ptr(new Pool6(Pool6::TYPE_IA, IOAddress("2001:db8:1:1::"), 64));
- subnet_->addPool(pool_);
-
- CfgMgr::instance().deleteSubnets6();
- CfgMgr::instance().addSubnet6(subnet_);
+ NakedDhcpv6SrvTest() : rcode_(-1) {
// it's ok if that fails. There should not be such a file anyway
unlink(DUID_FILE);
}
@@ -106,6 +98,16 @@ public:
return (ia);
}
+ /// @brief generates interface-id option, based on text
+ ///
+ /// @param iface_id textual representation of the interface-id content
+ ///
+ /// @return pointer to the option object
+ OptionPtr generateInterfaceId(const string& iface_id) {
+ OptionBuffer tmp(iface_id.begin(), iface_id.end());
+ return OptionPtr(new Option(Option::V6, D6O_INTERFACE_ID, tmp));
+ }
+
// Generate client-id option
OptionPtr generateClientId(size_t duid_size = 32) {
@@ -142,25 +144,22 @@ public:
EXPECT_TRUE(expected_clientid->getData() == tmp->getData());
}
- // Checks that server response (ADVERTISE or REPLY) contains proper IA_NA option
- // It returns IAADDR option for each chaining with checkIAAddr method.
- boost::shared_ptr<Option6IAAddr> checkIA_NA(const Pkt6Ptr& rsp, uint32_t expected_iaid,
- uint32_t expected_t1, uint32_t expected_t2) {
- OptionPtr tmp = rsp->getOption(D6O_IA_NA);
- // Can't use ASSERT_TRUE() in method that returns something
- if (!tmp) {
- ADD_FAILURE() << "IA_NA option not present in response";
- return (boost::shared_ptr<Option6IAAddr>());
- }
+ // Checks if server response is a NAK
+ void checkNakResponse(const Pkt6Ptr& rsp, uint8_t expected_message_type,
+ uint32_t expected_transid,
+ uint16_t expected_status_code) {
+ // Check if we get response at all
+ checkResponse(rsp, expected_message_type, expected_transid);
- boost::shared_ptr<Option6IA> ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
- EXPECT_EQ(expected_iaid, ia->getIAID() );
- EXPECT_EQ(expected_t1, ia->getT1());
- EXPECT_EQ(expected_t2, ia->getT2());
+ // Check that IA_NA was returned
+ OptionPtr option_ia_na = rsp->getOption(D6O_IA_NA);
+ ASSERT_TRUE(option_ia_na);
- tmp = ia->getOption(D6O_IAADDR);
- boost::shared_ptr<Option6IAAddr> addr = boost::dynamic_pointer_cast<Option6IAAddr>(tmp);
- return (addr);
+ // check that the status is no address available
+ boost::shared_ptr<Option6IA> ia = boost::dynamic_pointer_cast<Option6IA>(option_ia_na);
+ ASSERT_TRUE(ia);
+
+ checkIA_NAStatusCode(ia, expected_status_code);
}
// Checks that server rejected IA_NA, i.e. that it has no addresses and
@@ -180,7 +179,7 @@ public:
EXPECT_EQ(0, ia->getT1());
EXPECT_EQ(0, ia->getT2());
- boost::shared_ptr<OptionCustom> status =
+ OptionCustomPtr status =
boost::dynamic_pointer_cast<OptionCustom>(ia->getOption(D6O_STATUS_CODE));
// It is ok to not include status success as this is the default behavior
@@ -199,9 +198,8 @@ public:
}
}
-
void checkMsgStatusCode(const Pkt6Ptr& msg, uint16_t expected_status) {
- boost::shared_ptr<OptionCustom> status =
+ OptionCustomPtr status =
boost::dynamic_pointer_cast<OptionCustom>(msg->getOption(D6O_STATUS_CODE));
// It is ok to not include status success as this is the default behavior
@@ -219,7 +217,71 @@ public:
}
}
- // Check that generated IAADDR option contains expected address.
+ // Basic checks for generated response (message type and transaction-id).
+ void checkResponse(const Pkt6Ptr& rsp, uint8_t expected_message_type,
+ uint32_t expected_transid) {
+ ASSERT_TRUE(rsp);
+ EXPECT_EQ(expected_message_type, rsp->getType());
+ EXPECT_EQ(expected_transid, rsp->getTransid());
+ }
+
+ virtual ~NakedDhcpv6SrvTest() {
+ // Let's clean up if there is such a file.
+ unlink(DUID_FILE);
+ };
+
+ // A DUID used in most tests (typically as client-id)
+ DuidPtr duid_;
+
+ int rcode_;
+ ConstElementPtr comment_;
+};
+
+// Provides suport for tests against a preconfigured subnet6
+// extends upon NakedDhcp6SrvTest
+class Dhcpv6SrvTest : public NakedDhcpv6SrvTest {
+public:
+ /// Name of the server-id file (used in server-id tests)
+
+ // these are empty for now, but let's keep them around
+ Dhcpv6SrvTest() {
+ subnet_ = Subnet6Ptr(new Subnet6(IOAddress("2001:db8:1::"), 48, 1000,
+ 2000, 3000, 4000));
+ pool_ = Pool6Ptr(new Pool6(Pool6::TYPE_IA, IOAddress("2001:db8:1:1::"), 64));
+ subnet_->addPool(pool_);
+
+ CfgMgr::instance().deleteSubnets6();
+ CfgMgr::instance().addSubnet6(subnet_);
+ }
+
+ // Checks that server response (ADVERTISE or REPLY) contains proper IA_NA option
+ // It returns IAADDR option for each chaining with checkIAAddr method.
+ boost::shared_ptr<Option6IAAddr> checkIA_NA(const Pkt6Ptr& rsp, uint32_t expected_iaid,
+ uint32_t expected_t1, uint32_t expected_t2) {
+ OptionPtr tmp = rsp->getOption(D6O_IA_NA);
+ // Can't use ASSERT_TRUE() in method that returns something
+ if (!tmp) {
+ ADD_FAILURE() << "IA_NA option not present in response";
+ return (boost::shared_ptr<Option6IAAddr>());
+ }
+
+ boost::shared_ptr<Option6IA> ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
+ if (!ia) {
+ ADD_FAILURE() << "IA_NA cannot convert option ptr to Option6";
+ return (boost::shared_ptr<Option6IAAddr>());
+ }
+
+ EXPECT_EQ(expected_iaid, ia->getIAID());
+ EXPECT_EQ(expected_t1, ia->getT1());
+ EXPECT_EQ(expected_t2, ia->getT2());
+
+ tmp = ia->getOption(D6O_IAADDR);
+ boost::shared_ptr<Option6IAAddr> addr = boost::dynamic_pointer_cast<Option6IAAddr>(tmp);
+ return (addr);
+ }
+
+ // Check that generated IAADDR option contains expected address
+ // and lifetime values match the configured subnet
void checkIAAddr(const boost::shared_ptr<Option6IAAddr>& addr,
const IOAddress& expected_addr,
uint32_t /* expected_preferred */,
@@ -235,15 +297,8 @@ public:
EXPECT_EQ(addr->getValid(), subnet_->getValid());
}
- // Basic checks for generated response (message type and transaction-id).
- void checkResponse(const Pkt6Ptr& rsp, uint8_t expected_message_type,
- uint32_t expected_transid) {
- ASSERT_TRUE(rsp);
- EXPECT_EQ(expected_message_type, rsp->getType());
- EXPECT_EQ(expected_transid, rsp->getTransid());
- }
-
// Checks if the lease sent to client is present in the database
+ // and is valid when checked agasint the configured subnet
Lease6Ptr checkLease(const DuidPtr& duid, const OptionPtr& ia_na,
boost::shared_ptr<Option6IAAddr> addr) {
boost::shared_ptr<Option6IA> ia = boost::dynamic_pointer_cast<Option6IA>(ia_na);
@@ -265,9 +320,6 @@ public:
~Dhcpv6SrvTest() {
CfgMgr::instance().deleteSubnets6();
-
- // Let's clean up if there is such a file.
- unlink(DUID_FILE);
};
// A subnet used in most tests
@@ -275,13 +327,132 @@ public:
// A pool used in most tests
Pool6Ptr pool_;
+};
- // A DUID used in most tests (typically as client-id)
- DuidPtr duid_;
+// This test verifies that incoming SOLICIT can be handled properly when
+// there are no subnets configured.
+//
+// This test sends a SOLICIT and the expected response
+// is an ADVERTISE with STATUS_NoAddrsAvail and no address provided in the
+// response
+TEST_F(NakedDhcpv6SrvTest, SolicitNoSubnet) {
+ NakedDhcpv6Srv srv(0);
+
+ Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+ sol->setRemoteAddr(IOAddress("fe80::abcd"));
+ sol->addOption(generateIA(234, 1500, 3000));
+ OptionPtr clientid = generateClientId();
+ sol->addOption(clientid);
+
+ // Pass it to the server and get an advertise
+ Pkt6Ptr reply = srv.processSolicit(sol);
+
+ // check that we get the right NAK
+ checkNakResponse (reply, DHCPV6_ADVERTISE, 1234, STATUS_NoAddrsAvail);
+}
+
+// This test verifies that incoming REQUEST can be handled properly when
+// there are no subnets configured.
+//
+// This test sends a REQUEST and the expected response
+// is an REPLY with STATUS_NoAddrsAvail and no address provided in the
+// response
+TEST_F(NakedDhcpv6SrvTest, RequestNoSubnet) {
+ NakedDhcpv6Srv srv(0);
+
+ // Let's create a REQUEST
+ Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234));
+ req->setRemoteAddr(IOAddress("fe80::abcd"));
+ boost::shared_ptr<Option6IA> ia = generateIA(234, 1500, 3000);
+
+ // with a hint
+ IOAddress hint("2001:db8:1:1::dead:beef");
+ OptionPtr hint_opt(new Option6IAAddr(D6O_IAADDR, hint, 300, 500));
+ ia->addOption(hint_opt);
+ req->addOption(ia);
+ OptionPtr clientid = generateClientId();
+ req->addOption(clientid);
+
+ // server-id is mandatory in REQUEST
+ req->addOption(srv.getServerID());
+
+ // Pass it to the server and hope for a REPLY
+ Pkt6Ptr reply = srv.processRequest(req);
+
+ // check that we get the right NAK
+ checkNakResponse (reply, DHCPV6_REPLY, 1234, STATUS_NoAddrsAvail);
+}
+
+// This test verifies that incoming RENEW can be handled properly, even when
+// no subnets are configured.
+//
+// This test sends a RENEW and the expected response
+// is an REPLY with STATUS_NoBinding and no address provided in the
+// response
+TEST_F(NakedDhcpv6SrvTest, RenewNoSubnet) {
+ NakedDhcpv6Srv srv(0);
+
+ const IOAddress addr("2001:db8:1:1::cafe:babe");
+ const uint32_t iaid = 234;
+
+ // Generate client-id also duid_
+ OptionPtr clientid = generateClientId();
+
+ // Let's create a RENEW
+ Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_RENEW, 1234));
+ req->setRemoteAddr(IOAddress("fe80::abcd"));
+ boost::shared_ptr<Option6IA> ia = generateIA(iaid, 1500, 3000);
+
+ OptionPtr renewed_addr_opt(new Option6IAAddr(D6O_IAADDR, addr, 300, 500));
+ ia->addOption(renewed_addr_opt);
+ req->addOption(ia);
+ req->addOption(clientid);
+
+ // Server-id is mandatory in RENEW
+ req->addOption(srv.getServerID());
+
+ // Pass it to the server and hope for a REPLY
+ Pkt6Ptr reply = srv.processRenew(req);
+
+ // check that we get the right NAK
+ checkNakResponse (reply, DHCPV6_REPLY, 1234, STATUS_NoBinding);
+}
+
+// This test verifies that incoming RELEASE can be handled properly, even when
+// no subnets are configured.
+//
+// This test sends a RELEASE and the expected response
+// is an REPLY with STATUS_NoBinding and no address provided in the
+// response
+TEST_F(NakedDhcpv6SrvTest, ReleaseNoSubnet) {
+ NakedDhcpv6Srv srv(0);
+
+ const IOAddress addr("2001:db8:1:1::cafe:babe");
+ const uint32_t iaid = 234;
+
+ // Generate client-id also duid_
+ OptionPtr clientid = generateClientId();
+
+ // Let's create a RELEASE
+ Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_RELEASE, 1234));
+ req->setRemoteAddr(IOAddress("fe80::abcd"));
+ boost::shared_ptr<Option6IA> ia = generateIA(iaid, 1500, 3000);
+
+ OptionPtr released_addr_opt(new Option6IAAddr(D6O_IAADDR, addr, 300, 500));
+ ia->addOption(released_addr_opt);
+ req->addOption(ia);
+ req->addOption(clientid);
+
+ // Server-id is mandatory in RELEASE
+ req->addOption(srv.getServerID());
+
+ // Pass it to the server and hope for a REPLY
+ Pkt6Ptr reply = srv.processRelease(req);
+
+ // check that we get the right NAK
+ checkNakResponse (reply, DHCPV6_REPLY, 1234, STATUS_NoBinding);
+}
- int rcode_;
- ConstElementPtr comment_;
-};
// Test verifies that the Dhcpv6_srv class can be instantiated. It checks a mode
// without open sockets and with sockets opened on a high port (to not require
@@ -425,7 +596,7 @@ TEST_F(Dhcpv6SrvTest, advertiseOptions) {
sol->addOption(clientid);
// Pass it to the server and get an advertise
- boost::shared_ptr<Pkt6> adv = srv.processSolicit(sol);
+ Pkt6Ptr adv = srv.processSolicit(sol);
// check if we get response at all
ASSERT_TRUE(adv);
@@ -517,6 +688,7 @@ TEST_F(Dhcpv6SrvTest, SolicitBasic) {
// check that IA_NA was returned and that there's an address included
boost::shared_ptr<Option6IAAddr> addr = checkIA_NA(reply, 234, subnet_->getT1(),
subnet_->getT2());
+ ASSERT_TRUE(addr);
// Check that the assigned address is indeed from the configured pool
checkIAAddr(addr, addr->getAddress(), subnet_->getPreferred(), subnet_->getValid());
@@ -570,6 +742,7 @@ TEST_F(Dhcpv6SrvTest, SolicitHint) {
// check that IA_NA was returned and that there's an address included
boost::shared_ptr<Option6IAAddr> addr = checkIA_NA(reply, 234, subnet_->getT1(),
subnet_->getT2());
+ ASSERT_TRUE(addr);
// check that we've got the address we requested
checkIAAddr(addr, hint, subnet_->getPreferred(), subnet_->getValid());
@@ -618,6 +791,7 @@ TEST_F(Dhcpv6SrvTest, SolicitInvalidHint) {
// check that IA_NA was returned and that there's an address included
boost::shared_ptr<Option6IAAddr> addr = checkIA_NA(reply, 234, subnet_->getT1(),
subnet_->getT2());
+ ASSERT_TRUE(addr);
// Check that the assigned address is indeed from the configured pool
checkIAAddr(addr, addr->getAddress(), subnet_->getPreferred(), subnet_->getValid());
@@ -679,6 +853,9 @@ TEST_F(Dhcpv6SrvTest, ManySolicits) {
subnet_->getT2());
boost::shared_ptr<Option6IAAddr> addr3 = checkIA_NA(reply3, 3, subnet_->getT1(),
subnet_->getT2());
+ ASSERT_TRUE(addr1);
+ ASSERT_TRUE(addr2);
+ ASSERT_TRUE(addr3);
// Check that the assigned address is indeed from the configured pool
checkIAAddr(addr1, addr1->getAddress(), subnet_->getPreferred(), subnet_->getValid());
@@ -749,6 +926,7 @@ TEST_F(Dhcpv6SrvTest, RequestBasic) {
// check that IA_NA was returned and that there's an address included
boost::shared_ptr<Option6IAAddr> addr = checkIA_NA(reply, 234, subnet_->getT1(),
subnet_->getT2());
+ ASSERT_TRUE(addr);
// check that we've got the address we requested
checkIAAddr(addr, hint, subnet_->getPreferred(), subnet_->getValid());
@@ -773,6 +951,8 @@ TEST_F(Dhcpv6SrvTest, RequestBasic) {
TEST_F(Dhcpv6SrvTest, ManyRequests) {
NakedDhcpv6Srv srv(0);
+ ASSERT_TRUE(subnet_);
+
Pkt6Ptr req1 = Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234));
Pkt6Ptr req2 = Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 2345));
Pkt6Ptr req3 = Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 3456));
@@ -817,6 +997,10 @@ TEST_F(Dhcpv6SrvTest, ManyRequests) {
boost::shared_ptr<Option6IAAddr> addr3 = checkIA_NA(reply3, 3, subnet_->getT1(),
subnet_->getT2());
+ ASSERT_TRUE(addr1);
+ ASSERT_TRUE(addr2);
+ ASSERT_TRUE(addr3);
+
// Check that the assigned address is indeed from the configured pool
checkIAAddr(addr1, addr1->getAddress(), subnet_->getPreferred(), subnet_->getValid());
checkIAAddr(addr2, addr2->getAddress(), subnet_->getPreferred(), subnet_->getValid());
@@ -905,6 +1089,8 @@ TEST_F(Dhcpv6SrvTest, RenewBasic) {
boost::shared_ptr<Option6IAAddr> addr_opt = checkIA_NA(reply, 234, subnet_->getT1(),
subnet_->getT2());
+ ASSERT_TRUE(addr_opt);
+
// Check that we've got the address we requested
checkIAAddr(addr_opt, addr, subnet_->getPreferred(), subnet_->getValid());
@@ -1431,6 +1617,113 @@ TEST_F(Dhcpv6SrvTest, selectSubnetIface) {
EXPECT_EQ(subnet3, srv.selectSubnet(pkt));
}
+// This test verifies if selectSubnet() selects proper subnet for a given
+// linkaddr in RELAY-FORW message
+TEST_F(Dhcpv6SrvTest, selectSubnetRelayLinkaddr) {
+ NakedDhcpv6Srv srv(0);
+
+ Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:1::"), 48, 1, 2, 3, 4));
+ Subnet6Ptr subnet2(new Subnet6(IOAddress("2001:db8:2::"), 48, 1, 2, 3, 4));
+ Subnet6Ptr subnet3(new Subnet6(IOAddress("2001:db8:3::"), 48, 1, 2, 3, 4));
+
+ Pkt6::RelayInfo relay;
+ relay.linkaddr_ = IOAddress("2001:db8:2::1234");
+ relay.peeraddr_ = IOAddress("fe80::1");
+
+ // CASE 1: We have only one subnet defined and we received relayed traffic.
+ // The only available subnet should NOT be selected.
+ CfgMgr::instance().deleteSubnets6();
+ CfgMgr::instance().addSubnet6(subnet1); // just a single subnet
+
+ Pkt6Ptr pkt = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+ pkt->relay_info_.push_back(relay);
+
+ Subnet6Ptr selected = srv.selectSubnet(pkt);
+ EXPECT_FALSE(selected);
+
+ // CASE 2: We have three subnets defined and we received relayed traffic.
+ // Nothing should be selected.
+ CfgMgr::instance().deleteSubnets6();
+ CfgMgr::instance().addSubnet6(subnet1);
+ CfgMgr::instance().addSubnet6(subnet2);
+ CfgMgr::instance().addSubnet6(subnet3);
+ selected = srv.selectSubnet(pkt);
+ EXPECT_EQ(selected, subnet2);
+
+ // CASE 3: We have three subnets defined and we received relayed traffic
+ // that came out of subnet 2. We should select subnet2 then
+ CfgMgr::instance().deleteSubnets6();
+ CfgMgr::instance().addSubnet6(subnet1);
+ CfgMgr::instance().addSubnet6(subnet2);
+ CfgMgr::instance().addSubnet6(subnet3);
+
+ // Source of the packet should have no meaning. Selection is based
+ // on linkaddr field in the relay
+ pkt->setRemoteAddr(IOAddress("2001:db8:1::baca"));
+ selected = srv.selectSubnet(pkt);
+ EXPECT_EQ(selected, subnet2);
+
+ // CASE 4: We have three subnets defined and we received relayed traffic
+ // that came out of undefined subnet. We should select nothing
+ CfgMgr::instance().deleteSubnets6();
+ CfgMgr::instance().addSubnet6(subnet1);
+ CfgMgr::instance().addSubnet6(subnet2);
+ CfgMgr::instance().addSubnet6(subnet3);
+ pkt->relay_info_.clear();
+ relay.linkaddr_ = IOAddress("2001:db8:4::1234");
+ pkt->relay_info_.push_back(relay);
+ selected = srv.selectSubnet(pkt);
+ EXPECT_FALSE(selected);
+
+}
+
+// This test verifies if selectSubnet() selects proper subnet for a given
+// interface-id option
+TEST_F(Dhcpv6SrvTest, selectSubnetRelayInterfaceId) {
+ NakedDhcpv6Srv srv(0);
+
+ Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:1::"), 48, 1, 2, 3, 4));
+ Subnet6Ptr subnet2(new Subnet6(IOAddress("2001:db8:2::"), 48, 1, 2, 3, 4));
+ Subnet6Ptr subnet3(new Subnet6(IOAddress("2001:db8:3::"), 48, 1, 2, 3, 4));
+
+ subnet1->setInterfaceId(generateInterfaceId("relay1"));
+ subnet2->setInterfaceId(generateInterfaceId("relay2"));
+
+ // CASE 1: We have only one subnet defined and it is for interface-id "relay1"
+ // Packet came with interface-id "relay2". We should not select subnet1
+ CfgMgr::instance().deleteSubnets6();
+ CfgMgr::instance().addSubnet6(subnet1); // just a single subnet
+
+ Pkt6Ptr pkt = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+ Pkt6::RelayInfo relay;
+ relay.linkaddr_ = IOAddress("2001:db8:2::1234");
+ relay.peeraddr_ = IOAddress("fe80::1");
+ OptionPtr opt = generateInterfaceId("relay2");
+ relay.options_.insert(make_pair(opt->getType(), opt));
+ pkt->relay_info_.push_back(relay);
+
+ // There is only one subnet configured and we are outside of that subnet
+ Subnet6Ptr selected = srv.selectSubnet(pkt);
+ EXPECT_FALSE(selected);
+
+ // CASE 2: We have only one subnet defined and it is for interface-id "relay2"
+ // Packet came with interface-id "relay2". We should select it
+ CfgMgr::instance().deleteSubnets6();
+ CfgMgr::instance().addSubnet6(subnet2); // just a single subnet
+ selected = srv.selectSubnet(pkt);
+ EXPECT_EQ(selected, subnet2);
+
+ // CASE 3: We have only 3 subnets defined: one remote for interface-id "relay1",
+ // one remote for interface-id "relay2" and third local
+ // packet comes with interface-id "relay2". We should select subnet2
+ CfgMgr::instance().deleteSubnets6();
+ CfgMgr::instance().addSubnet6(subnet1);
+ CfgMgr::instance().addSubnet6(subnet2);
+ CfgMgr::instance().addSubnet6(subnet3);
+
+ EXPECT_EQ(subnet2, srv.selectSubnet(pkt));
+}
+
// This test verifies if the server-id disk operations (read, write) are
// working properly.
TEST_F(Dhcpv6SrvTest, ServerID) {
diff --git a/src/bin/loadzone/run_loadzone.sh.in b/src/bin/loadzone/run_loadzone.sh.in
index 178cf11..9064416 100755
--- a/src/bin/loadzone/run_loadzone.sh.in
+++ b/src/bin/loadzone/run_loadzone.sh.in
@@ -25,7 +25,7 @@ export PYTHONPATH
# required by loadable python modules.
SET_ENV_LIBRARY_PATH=@SET_ENV_LIBRARY_PATH@
if test $SET_ENV_LIBRARY_PATH = yes; then
- @ENV_LIBRARY_PATH@=@abs_top_builddir@/src/lib/dns/.libs:@abs_top_builddir@/src/lib/dns/python/.libs:@abs_top_builddir@/src/lib/cryptolink/.libs:@abs_top_builddir@/src/lib/cc/.libs:@abs_top_builddir@/src/lib/config/.libs:@abs_top_builddir@/src/lib/log/.libs:@abs_top_builddir@/src/lib/util/.libs:@abs_top_builddir@/src/lib/util/io/.libs:@abs_top_builddir@/src/lib/exceptions/.libs:@abs_top_builddir@/src/lib/datasrc/.libs:$@ENV_LIBRARY_PATH@
+ @ENV_LIBRARY_PATH@=@abs_top_builddir@/src/lib/dns/.libs:@abs_top_builddir@/src/lib/dns/python/.libs:@abs_top_builddir@/src/lib/cryptolink/.libs:@abs_top_builddir@/src/lib/cc/.libs:@abs_top_builddir@/src/lib/config/.libs:@abs_top_builddir@/src/lib/log/.libs:@abs_top_builddir@/src/lib/util/.libs:@abs_top_builddir@/src/lib/util/threads/.libs:@abs_top_builddir@/src/lib/util/io/.libs:@abs_top_builddir@/src/lib/exceptions/.libs:@abs_top_builddir@/src/lib/datasrc/.libs:$@ENV_LIBRARY_PATH@
export @ENV_LIBRARY_PATH@
fi
diff --git a/src/bin/loadzone/tests/Makefile.am b/src/bin/loadzone/tests/Makefile.am
index 8459f83..235ed22 100644
--- a/src/bin/loadzone/tests/Makefile.am
+++ b/src/bin/loadzone/tests/Makefile.am
@@ -13,7 +13,7 @@ EXTRA_DIST += testdata/example-nons.org.zone
# 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/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)
+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
diff --git a/src/bin/loadzone/tests/correct/Makefile.am b/src/bin/loadzone/tests/correct/Makefile.am
index 7ed500d..366e85c 100644
--- a/src/bin/loadzone/tests/correct/Makefile.am
+++ b/src/bin/loadzone/tests/correct/Makefile.am
@@ -20,7 +20,7 @@ noinst_SCRIPTS = correct_test.sh
# 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/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/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:$$$(ENV_LIBRARY_PATH)
endif
# TODO: maybe use TESTS?
diff --git a/src/bin/loadzone/tests/correct/correct_test.sh.in b/src/bin/loadzone/tests/correct/correct_test.sh.in
index 505411c..627ec68 100755
--- a/src/bin/loadzone/tests/correct/correct_test.sh.in
+++ b/src/bin/loadzone/tests/correct/correct_test.sh.in
@@ -53,7 +53,7 @@ echo "I:test master file BIND 8 compatibility TTL and \$TTL semantics"
echo "I:test master file RFC1035 TTL and \$TTL semantics"
echo "I:test master file BIND8 compatibility and mixed \$INCLUDE with \$TTL semantics"
echo "I:test master file RFC1035 TTL and mixed \$INCLUDE with \$TTL semantics"
-echo "I:test master file BIND9 extenstion of TTL"
+echo "I:test master file BIND9 extension of TTL"
echo "I:test master file RFC1035 missing CLASS, TTL, NAME semantics"
echo "I:test master file comments"
diff --git a/src/bin/loadzone/tests/correct/ttlext.db b/src/bin/loadzone/tests/correct/ttlext.db
index f8b96ea..3aa37e8 100644
--- a/src/bin/loadzone/tests/correct/ttlext.db
+++ b/src/bin/loadzone/tests/correct/ttlext.db
@@ -11,7 +11,7 @@ ns A 10.53.0.1
a TXT "soa minttl 3"
b 2S TXT "explicit ttl 2"
c TXT "soa minttl 3"
-$TTL 10M ; bind9 extention ttl
+$TTL 10M ; bind9 extension ttl
d TXT "default ttl 600"
e 4 TXT "explicit ttl 4"
f TXT "default ttl 600"
diff --git a/src/bin/msgq/Makefile.am b/src/bin/msgq/Makefile.am
index a49b125..185d47a 100644
--- a/src/bin/msgq/Makefile.am
+++ b/src/bin/msgq/Makefile.am
@@ -10,6 +10,7 @@ b10_msgq_DATA = msgq.spec
CLEANFILES = b10-msgq msgq.pyc
CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/msgq_messages.py
CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/msgq_messages.pyc
+CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/msgq_messages.pyo
man_MANS = b10-msgq.8
DISTCLEANFILES = $(man_MANS)
diff --git a/src/bin/msgq/msgq.py.in b/src/bin/msgq/msgq.py.in
index 6435c92..efa3cbd 100755
--- a/src/bin/msgq/msgq.py.in
+++ b/src/bin/msgq/msgq.py.in
@@ -143,7 +143,7 @@ class SubscriptionManager:
this group, instance pair. This includes wildcard subscriptions."""
target = (group, instance)
partone = self.find_sub(group, instance)
- parttwo = self.find_sub(group, "*")
+ parttwo = self.find_sub(group, CC_INSTANCE_WILDCARD)
return list(set(partone + parttwo))
class MsgQ:
@@ -429,19 +429,19 @@ class MsgQ:
"""Process a single command. This will split out into one of the
other functions."""
logger.debug(TRACE_DETAIL, MSGQ_RECV_HDR, routing)
- cmd = routing["type"]
- if cmd == 'send':
+ cmd = routing[CC_HEADER_TYPE]
+ if cmd == CC_COMMAND_SEND:
self.process_command_send(sock, routing, data)
- elif cmd == 'subscribe':
+ elif cmd == CC_COMMAND_SUBSCRIBE:
self.process_command_subscribe(sock, routing, data)
- elif cmd == 'unsubscribe':
+ elif cmd == CC_COMMAND_UNSUBSCRIBE:
self.process_command_unsubscribe(sock, routing, data)
- elif cmd == 'getlname':
+ elif cmd == CC_COMMAND_GET_LNAME:
self.process_command_getlname(sock, routing, data)
- elif cmd == 'ping':
+ elif cmd == CC_COMMAND_PING:
# Command for testing purposes
self.process_command_ping(sock, routing, data)
- elif cmd == 'stop':
+ elif cmd == CC_COMMAND_STOP:
self.stop()
else:
logger.error(MSGQ_INVALID_CMD, cmd)
@@ -570,11 +570,12 @@ class MsgQ:
return "%x_%x@%s" % (time.time(), self.connection_counter, self.hostname)
def process_command_ping(self, sock, routing, data):
- self.sendmsg(sock, { "type" : "pong" }, data)
+ self.sendmsg(sock, { CC_HEADER_TYPE : CC_COMMAND_PONG }, data)
def process_command_getlname(self, sock, routing, data):
lname = [ k for k, v in self.lnames.items() if v == sock ][0]
- self.sendmsg(sock, { "type" : "getlname" }, { "lname" : lname })
+ self.sendmsg(sock, { CC_HEADER_TYPE : CC_COMMAND_GET_LNAME },
+ { CC_PAYLOAD_LNAME : lname })
def process_command_send(self, sock, routing, data):
group = routing[CC_HEADER_GROUP]
@@ -638,15 +639,15 @@ class MsgQ:
self.send_prepared_msg(sock, errmsg)
def process_command_subscribe(self, sock, routing, data):
- group = routing["group"]
- instance = routing["instance"]
+ group = routing[CC_HEADER_GROUP]
+ instance = routing[CC_HEADER_INSTANCE]
if group == None or instance == None:
return # ignore invalid packets entirely
self.subs.subscribe(group, instance, sock)
def process_command_unsubscribe(self, sock, routing, data):
- group = routing["group"]
- instance = routing["instance"]
+ 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)
@@ -789,7 +790,7 @@ class MsgQ:
if not self.running:
return
- # TODO: Any config handlig goes here.
+ # TODO: Any config handling goes here.
return isc.config.create_answer(0)
diff --git a/src/bin/msgq/run_msgq.sh.in b/src/bin/msgq/run_msgq.sh.in
index c9fef64..6b175d6 100644
--- a/src/bin/msgq/run_msgq.sh.in
+++ b/src/bin/msgq/run_msgq.sh.in
@@ -20,7 +20,7 @@ export PYTHON_EXEC
MYPATH_PATH=@abs_top_builddir@/src/bin/msgq
-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/log/.libs
+PYTHONPATH=@abs_top_builddir@/src/lib/python/isc/log_messages:@abs_top_builddir@/src/lib/python/isc/cc:@abs_top_srcdir@/src/lib/python:@abs_top_builddir@/src/lib/python:@abs_top_builddir@/src/lib/dns/python/.libs
export PYTHONPATH
# If necessary (rare cases), explicitly specify paths to dynamic libraries
diff --git a/src/bin/msgq/tests/Makefile.am b/src/bin/msgq/tests/Makefile.am
index c9ef5d3..025b84d 100644
--- a/src/bin/msgq/tests/Makefile.am
+++ b/src/bin/msgq/tests/Makefile.am
@@ -1,18 +1,18 @@
-PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
-PYTESTS = msgq_test.py
+PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
+PYTESTS = msgq_test.py msgq_run_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/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/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:$$$(ENV_LIBRARY_PATH)
endif
# test using command-line arguments, so use check-local target instead of TESTS
check-local:
if ENABLE_PYTHON_COVERAGE
- touch $(abs_top_srcdir)/.coverage
+ touch $(abs_top_srcdir)/.coverage
rm -f .coverage
${LN_S} $(abs_top_srcdir)/.coverage .coverage
endif
diff --git a/src/bin/msgq/tests/msgq_run_test.py b/src/bin/msgq/tests/msgq_run_test.py
new file mode 100644
index 0000000..95173e0
--- /dev/null
+++ b/src/bin/msgq/tests/msgq_run_test.py
@@ -0,0 +1,278 @@
+# 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.
+
+"""
+In this test file, we actually start msgq as a process and test it
+as a whole. It may be considered a system test instead of unit test,
+but apart from the terminology, we don't care much. We need to test
+the message queue works as expected, together with the libraries.
+
+In each test, we first start a timeout (because we do some waits
+for messages and if they wouldn't come, the test could block indefinitely).
+The timeout is long, because it is for the case the test fails.
+
+We then start the msgq and wait for the socket file to appear
+(that should indicate it is ready to receive connections). Then the
+actual test starts. After the test, we kill it and remove the test file.
+
+We also register signal handlers for many signals. Even in the case
+the test is interrupted or crashes, we should ensure the message queue
+itself is terminated.
+"""
+
+import unittest
+import os
+import signal
+import sys
+import subprocess
+import time
+
+import isc.log
+import isc.cc.session
+from isc.cc.proto_defs import *
+
+# Due to problems with too long path on build bots, we place the socket
+# into the top-level build directory. That is ugly, but works.
+SOCKET_PATH = os.path.abspath(os.environ['B10_FROM_BUILD'] + '/msgq.sock')
+MSGQ_PATH = os.environ['B10_FROM_BUILD'] + '/src/bin/msgq/run_msgq.sh'
+TIMEOUT = 15 # Some long time (seconds), for single test.
+
+class MsgqRunTest(unittest.TestCase):
+ def setUp(self):
+ """
+ As described above - check the socket file does not exist.
+ Then register signals and timeouts. Finally, launch msgq
+ and wait for it to start.
+ """
+ self.__msgq = None
+ self.__opened_connections = []
+ # A precondition check
+ self.assertFalse(os.path.exists(SOCKET_PATH))
+ signal.alarm(TIMEOUT)
+ self.__orig_signals = {}
+ # Register handlers for many signals. Most of them probably
+ # can't happen in python, but we register them anyway just to be
+ # safe.
+ for sig in [signal.SIGHUP, signal.SIGINT, signal.SIGQUIT,
+ signal.SIGILL, signal.SIGTRAP, signal.SIGABRT, signal.SIGBUS,
+ signal.SIGFPE, signal.SIGALRM, signal.SIGTERM]:
+ self.__orig_signals[sig] = signal.signal(sig, self.__signal)
+ # Start msgq
+ self.__msgq = subprocess.Popen([MSGQ_PATH, '-s', SOCKET_PATH],
+ close_fds=True)
+ # Some testing data
+ self.__no_recpt = {"result": [-1, "No such recipient"]}
+ # Wait for it to become ready (up to the alarm-set timeout)
+ connection = None
+ while not connection:
+ try:
+ # If the msgq is ready, this'll succeed. If not, it'll throw
+ # session error.
+ connection = isc.cc.session.Session(SOCKET_PATH)
+ except isc.cc.session.SessionError:
+ time.sleep(0.1) # Retry after a short time
+ # We have the connection now, that means it works. Close this
+ # connection, we won't use it. Each test gets enough new connections
+ # of its own.
+ connection.close()
+
+ def __message(self, data):
+ """
+ Provide some testing message. The data will be included in it, so
+ several different messages can be created.
+ """
+ return {"Message": "Text", "Data": data}
+
+ def tearDown(self):
+ """
+ Perform cleanup after the test.
+ """
+ self.__cleanup()
+
+ def __signal(self, signal, frame):
+ """
+ Called from a signal handler. We perform some cleanup, output
+ a complain and terminate with error.
+ """
+ self.__cleanup()
+ sys.stderr.write("Test terminating from signal " + str(signal) +
+ " in " + str(frame) + "\n")
+ sys.exit(1)
+
+ def __cleanup(self):
+ """
+ Kill msgq (if running) and restore original signal handlers.
+ """
+ # Remove the socket (as we kill, msgq might not clean up)
+ for conn in self.__opened_connections:
+ conn.close()
+ self.__opened_connections = []
+ if self.__msgq:
+ self.__msgq.kill()
+ self.__msgq = None
+ if os.path.exists(SOCKET_PATH):
+ os.unlink(SOCKET_PATH)
+ for sig in self.__orig_signals:
+ signal.signal(sig, self.__orig_signals[sig])
+ # Cancel timeout (so someone else is not hit by it)
+ signal.alarm(0)
+
+ def __get_connection(self):
+ """
+ Create a connection to the daemon and make sure it is properly closed
+ at the end of the test.
+ """
+ connection = isc.cc.session.Session(SOCKET_PATH)
+ self.__opened_connections.append(connection)
+ return connection
+
+ def test_send_direct(self):
+ """
+ Connect twice to msgq, send a message from one to another using direct
+ l-name and see it comes.
+ """
+ # Create the connections
+ conn1 = self.__get_connection()
+ conn2 = self.__get_connection()
+ # Send the message
+ lname1 = conn1.lname
+ conn2.group_sendmsg(self.__message(1), "*", to=lname1)
+ # Receive the message and see it contains correct data
+ (msg, env) = conn1.group_recvmsg(nonblock=False)
+ self.assertEqual(self.__message(1), msg)
+ # We don't check there are no extra headers, just that none are missing
+ # or wrong.
+ self.assertEqual(lname1, env[CC_HEADER_TO])
+ self.assertEqual(conn2.lname, env[CC_HEADER_FROM])
+ self.assertEqual("*", env[CC_HEADER_GROUP])
+ self.assertEqual(CC_INSTANCE_WILDCARD, env[CC_HEADER_INSTANCE])
+ self.assertEqual(CC_COMMAND_SEND, env[CC_HEADER_TYPE])
+ self.assertFalse(env[CC_HEADER_WANT_ANSWER])
+
+ def __barrier(self, connections):
+ """
+ Make sure all previous commands on all supplied connections are
+ processed, by sending a ping and waiting for an answer.
+ """
+ for c in connections:
+ c.sendmsg({"type": "ping"})
+ for c in connections:
+ pong = c.recvmsg(nonblock=False)
+ self.assertEqual(({"type": "pong"}, None), pong)
+
+ def test_send_group(self):
+ """
+ Create several connections. First, try to send a message to a (empty)
+ group and see an error is bounced back. Then subscribe the others
+ to the group and send it again. Send to a different group and see it
+ bounced back. Unsubscribe and see it is bounced again.
+
+ Then the other connections answer (after unsubscribing, strange, but
+ legal). See both answers come.
+
+ Then, look there are no more waiting messages.
+ """
+ conn_a = self.__get_connection()
+ conn_b = []
+ for i in range(0, 10):
+ conn_b.append(self.__get_connection())
+ # Send a message to empty group and get an error answer
+ seq = conn_a.group_sendmsg(self.__message(1), "group",
+ want_answer=True)
+ (msg, env) = conn_a.group_recvmsg(nonblock=False, seq=seq)
+ self.assertEqual(self.__no_recpt, msg)
+ self.assertEqual(conn_a.lname, env[CC_HEADER_TO])
+ # Subscribe the two connections
+ for c in conn_b:
+ c.group_subscribe("group")
+ # The subscribe doesn't wait for answer, so make sure it is
+ # all processed before continuing.
+ self.__barrier(conn_b)
+ # Send a message to the group (this time not empty)
+ seq = conn_a.group_sendmsg(self.__message(2), "group",
+ want_answer=True)
+ envs = []
+ for c in conn_b:
+ (msg, env) = c.group_recvmsg(nonblock=False)
+ self.assertEqual(self.__message(2), msg)
+ self.assertEqual(conn_a.lname, env[CC_HEADER_FROM])
+ # The daemon does not mangle the headers. Is it OK?
+ self.assertEqual(CC_TO_WILDCARD, env[CC_HEADER_TO])
+ self.assertEqual("group", env[CC_HEADER_GROUP])
+ self.assertEqual(CC_INSTANCE_WILDCARD, env[CC_HEADER_INSTANCE])
+ self.assertEqual(CC_COMMAND_SEND, env[CC_HEADER_TYPE])
+ self.assertTrue(env[CC_HEADER_WANT_ANSWER])
+ envs.append(env)
+ # Send to non-existing group
+ seq_ne = conn_a.group_sendmsg(self.__message(3), "no-group",
+ want_answer=True)
+ (msg, env) = conn_a.group_recvmsg(nonblock=False, seq=seq_ne)
+ self.assertEqual(self.__no_recpt, msg)
+ self.assertEqual(conn_a.lname, env[CC_HEADER_TO])
+ # Unsubscribe the connections
+ for c in conn_b:
+ c.group_unsubscribe("group")
+ # Synchronize the unsubscriptions
+ self.__barrier(conn_b)
+ seq_ne = conn_a.group_sendmsg(self.__message(4), "group",
+ want_answer=True)
+ (msg, env) = conn_a.group_recvmsg(nonblock=False, seq=seq_ne)
+ self.assertEqual(self.__no_recpt, msg)
+ self.assertEqual(conn_a.lname, env[CC_HEADER_TO])
+ # Send answers for the original message that was delivered
+ lnames = set()
+ for (c, env) in zip(conn_b, envs):
+ c.group_reply(env, self.__message("Reply"))
+ lnames.add(c.lname)
+ # Check the both answers come
+ while lnames:
+ # While there are still connections we didn't get the answer from
+ # (the order is not guaranteed, therefore the juggling with set)
+ (msg, env) = conn_a.group_recvmsg(nonblock=False, seq=seq)
+ self.assertEqual(self.__message("Reply"), msg)
+ lname = env[CC_HEADER_FROM]
+ self.assertTrue(lname in lnames)
+ lnames.remove(lname)
+
+ # The barrier makes the msgq process everything we sent. As the
+ # processing is single-threaded in it, any stray message would have
+ # arrived before the barrier ends.
+ self.__barrier(conn_b)
+ self.__barrier([conn_a])
+ for c in conn_b:
+ self.assertEqual((None, None), c.group_recvmsg())
+ self.assertEqual((None, None), conn_a.group_recvmsg())
+
+ def test_conn_disconn(self):
+ """
+ Keep connecting and disconnecting, checking we can still send
+ and receive messages.
+ """
+ conn = self.__get_connection()
+ conn.group_subscribe("group")
+ for i in range(0, 50):
+ new = self.__get_connection()
+ new.group_subscribe("group")
+ self.__barrier([conn, new])
+ new.group_sendmsg(self.__message(i), "group")
+ (msg, env) = conn.group_recvmsg(nonblock=False)
+ self.assertEqual(self.__message(i), msg)
+ conn.close()
+ conn = new
+
+if __name__ == '__main__':
+ isc.log.init("msgq-tests")
+ isc.log.resetUnitTestRootLogger()
+ unittest.main()
diff --git a/src/bin/msgq/tests/msgq_test.py b/src/bin/msgq/tests/msgq_test.py
index 98705bf..e5a5656 100644
--- a/src/bin/msgq/tests/msgq_test.py
+++ b/src/bin/msgq/tests/msgq_test.py
@@ -186,7 +186,7 @@ class MsgQTest(unittest.TestCase):
The test is not exhaustive as it doesn't test all combination
of existence of the recipient, addressing schemes, want_answer
header and the reply header. It is not needed, these should
- be mostly independant. That means, for example, if the message
+ be mostly independent. That means, for example, if the message
is a reply and there's no recipient to send it to, the error
would not be generated no matter if we addressed the recipient
by lname or group. If we included everything, the test would
@@ -338,7 +338,7 @@ class BadSocket:
self.send_exception = send_exception
# completely wrap all calls and member access
- # (except explicitely overridden ones)
+ # (except explicitly overridden ones)
def __getattr__(self, name, *args):
attr = getattr(self.socket, name)
if isinstance(attr, collections.Callable):
@@ -834,7 +834,7 @@ class SocketTests(unittest.TestCase):
self.assertIsNone(self.__killed_socket)
def test_send_data_interrupt(self):
- '''send() is interruptted. send_data() returns 0, sock isn't killed.'''
+ '''send() is interrupted. send_data() returns 0, sock isn't killed.'''
expected_blockings = []
for eno in [errno.EAGAIN, errno.EWOULDBLOCK, errno.EINTR]:
self.__sock_error.errno = eno
diff --git a/src/bin/resolver/Makefile.am b/src/bin/resolver/Makefile.am
index 47e242c..a549e6a 100644
--- a/src/bin/resolver/Makefile.am
+++ b/src/bin/resolver/Makefile.am
@@ -1,4 +1,4 @@
-SUBDIRS = . tests
+SUBDIRS = . tests bench
AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
AM_CPPFLAGS += -I$(top_srcdir)/src/bin -I$(top_builddir)/src/bin
diff --git a/src/bin/resolver/bench/Makefile.am b/src/bin/resolver/bench/Makefile.am
new file mode 100644
index 0000000..e4689fb
--- /dev/null
+++ b/src/bin/resolver/bench/Makefile.am
@@ -0,0 +1,25 @@
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += -I$(top_builddir)/src/lib/dns -I$(top_srcdir)/src/bin
+AM_CPPFLAGS += -I$(top_builddir)/src/lib/cc
+AM_CPPFLAGS += -I$(top_builddir)/src/bin/resolver
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+CLEANFILES = *.gcno *.gcda
+
+noinst_PROGRAMS = resolver-bench
+
+resolver_bench_SOURCES = main.cc
+resolver_bench_SOURCES += fake_resolution.h fake_resolution.cc
+resolver_bench_SOURCES += dummy_work.h dummy_work.cc
+resolver_bench_SOURCES += naive_resolver.h naive_resolver.cc
+
+resolver_bench_LDADD = $(GTEST_LDADD)
+resolver_bench_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
+resolver_bench_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
+
diff --git a/src/bin/resolver/bench/dummy_work.cc b/src/bin/resolver/bench/dummy_work.cc
new file mode 100644
index 0000000..c17bcbf
--- /dev/null
+++ b/src/bin/resolver/bench/dummy_work.cc
@@ -0,0 +1,28 @@
+// 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 <resolver/bench/dummy_work.h>
+
+namespace isc {
+namespace resolver {
+namespace bench {
+
+void
+dummy_work() {
+ // Function left intentonally blank.
+};
+
+}
+}
+}
diff --git a/src/bin/resolver/bench/dummy_work.h b/src/bin/resolver/bench/dummy_work.h
new file mode 100644
index 0000000..81fd0f2
--- /dev/null
+++ b/src/bin/resolver/bench/dummy_work.h
@@ -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.
+
+#ifndef DUMMY_WORK_H
+#define DUMMY_WORK_H
+
+namespace isc {
+namespace resolver {
+namespace bench {
+
+/// \brief An empty function.
+///
+/// An empty function, to fill the CPU with something during the benchmark.
+/// It is expected to be called many times by whatever simulates doing some
+/// real CPU-bound work.
+///
+/// It is defined in separate translation unit, so the compiler does not
+/// know it is empty and can't optimise the call out.
+void dummy_work();
+
+}
+}
+}
+
+#endif
diff --git a/src/bin/resolver/bench/fake_resolution.cc b/src/bin/resolver/bench/fake_resolution.cc
new file mode 100644
index 0000000..e3d54a9
--- /dev/null
+++ b/src/bin/resolver/bench/fake_resolution.cc
@@ -0,0 +1,172 @@
+// 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 <resolver/bench/fake_resolution.h>
+#include <resolver/bench/dummy_work.h>
+
+#include <asiolink/interval_timer.h>
+
+#include <boost/bind.hpp>
+#include <boost/foreach.hpp>
+#include <algorithm>
+#include <stdlib.h> // not cstdlib, which doesn't officially have random()
+
+namespace isc {
+namespace resolver {
+namespace bench {
+
+// Parameters of the generated queries.
+// How much work is each operation?
+const size_t parse_size = 100000;
+const size_t render_size = 100000;
+const size_t send_size = 1000;
+const size_t cache_read_size = 10000;
+const size_t cache_write_size = 10000;
+// How large a change is to terminate in this iteration (either by getting
+// the complete answer, or by finding it in the cache). With 0.5, half the
+// queries are found in the cache directly. Half of the rest needs just one
+// upstream query. Etc.
+const float chance_complete = 0.5;
+// Number of milliseconds an upstream query can take. It picks a random number
+// in between.
+const size_t upstream_time_min = 2;
+const size_t upstream_time_max = 50;
+
+FakeQuery::FakeQuery(FakeInterface& interface) :
+ interface_(&interface),
+ outstanding_(false)
+{
+ // Schedule what tasks are needed.
+ // First, parse the query
+ steps_.push_back(Step(Compute, parse_size));
+ // Look into the cache if it is there
+ steps_.push_back(Step(CacheRead, cache_read_size));
+ while ((1.0 * random()) / RAND_MAX > chance_complete) {
+ // Needs another step of recursion. Render the upstream query.
+ steps_.push_back(Step(Compute, render_size));
+ // Send it and wait for the answer.
+ steps_.push_back(Step(Upstream, upstream_time_min +
+ (random() *
+ (upstream_time_max - upstream_time_min) /
+ RAND_MAX)));
+ // After it comes, parse the answer and store it in the cache.
+ steps_.push_back(Step(Compute, parse_size));
+ steps_.push_back(Step(CacheWrite, cache_write_size));
+ }
+ // Last, render the answer and send it.
+ steps_.push_back(Step(Compute, render_size));
+ steps_.push_back(Step(Send, send_size));
+ // Reverse it, so we can pop_back the tasks as we work on them.
+ std::reverse(steps_.begin(), steps_.end());
+}
+
+void
+FakeQuery::performTask(const StepCallback& callback) {
+ // nextTask also does all the sanity checking we need.
+ if (nextTask() == Upstream) {
+ outstanding_ = true;
+ interface_->scheduleUpstreamAnswer(this, callback,
+ steps_.back().second);
+ steps_.pop_back();
+ } else {
+ for (size_t i = 0; i < steps_.back().second; ++i) {
+ dummy_work();
+ }
+ steps_.pop_back();
+ callback();
+ }
+}
+
+FakeInterface::FakeInterface(size_t query_count) :
+ queries_(query_count)
+{
+ BOOST_FOREACH(FakeQueryPtr& query, queries_) {
+ query = FakeQueryPtr(new FakeQuery(*this));
+ }
+}
+
+void
+FakeInterface::processEvents() {
+ service_.run_one();
+}
+
+namespace {
+
+void
+processDone(bool* flag) {
+ *flag = true;
+}
+
+}
+
+FakeQueryPtr
+FakeInterface::receiveQuery() {
+ // Handle all the events that are already scheduled.
+ // As processEvents blocks until an event happens and we want to terminate
+ // if there are no events, we do a small trick. We post an event to the end
+ // of the queue and work until it is found. This should process all the
+ // events that were there already.
+ bool processed = false;
+ service_.post(boost::bind(&processDone, &processed));
+ while (!processed) {
+ processEvents();
+ }
+
+ // Now, look if there are more queries to return.
+ if (queries_.empty()) {
+ return (FakeQueryPtr());
+ } else {
+ // Take from the back. The order doesn't matter and it's faster from
+ // there.
+ FakeQueryPtr result(queries_.back());
+ queries_.pop_back();
+ return (result);
+ }
+}
+
+class FakeInterface::UpstreamQuery {
+public:
+ UpstreamQuery(FakeQuery* query, const FakeQuery::StepCallback& callback,
+ const boost::shared_ptr<asiolink::IntervalTimer> timer) :
+ query_(query),
+ callback_(callback),
+ timer_(timer)
+ {}
+ void trigger() {
+ query_->answerReceived();
+ callback_();
+ // We are not needed any more.
+ delete this;
+ }
+private:
+ FakeQuery* const query_;
+ const FakeQuery::StepCallback callback_;
+ // Just to hold it alive before the callback is called.
+ const boost::shared_ptr<asiolink::IntervalTimer> timer_;
+};
+
+void
+FakeInterface::scheduleUpstreamAnswer(FakeQuery* query,
+ const FakeQuery::StepCallback& callback,
+ size_t msec)
+{
+ const boost::shared_ptr<asiolink::IntervalTimer>
+ timer(new asiolink::IntervalTimer(service_));
+ UpstreamQuery* q(new UpstreamQuery(query, callback, timer));
+ timer->setup(boost::bind(&UpstreamQuery::trigger, q), msec);
+}
+
+}
+}
+}
diff --git a/src/bin/resolver/bench/fake_resolution.h b/src/bin/resolver/bench/fake_resolution.h
new file mode 100644
index 0000000..cf2219c
--- /dev/null
+++ b/src/bin/resolver/bench/fake_resolution.h
@@ -0,0 +1,228 @@
+// 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 FAKE_RESOLUTION_H
+#define FAKE_RESOLUTION_H
+
+#include <exceptions/exceptions.h>
+#include <asiolink/io_service.h>
+
+#include <boost/function.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include <utility>
+#include <vector>
+
+namespace isc {
+namespace resolver {
+namespace bench {
+
+/// \brief The kind of task a FakeQuery might want to perform.
+///
+/// The benchmark should examine which kind of task the query needs to perform
+/// to progress forward. According to the task, some resources might need to be
+/// locked, something re-scheduled, or such.
+enum Task {
+ /// \brief Some CPU-bound computation.
+ ///
+ /// The query needs to do some computation without any shared resources.
+ /// This might be parsing or rendering of the query, verification of
+ /// signatures, etc.
+ Compute,
+ /// \brief The query needs to read data from cache.
+ CacheRead,
+ /// \brief The query needs to modify the cache.
+ CacheWrite,
+ /// \brief A response is to be sent.
+ ///
+ /// This needs to access the interface/socket. If the socket is shared
+ /// between threads, it might need to lock it.
+ Send,
+ /// \brief An answer from upstream server is needed.
+ ///
+ /// The query needs to send a query to some authoritative server and wait
+ /// for the answer. Something might need to be locked (or not, depending
+ /// on the architecture of the thing that sends and receives). Also, the
+ /// task will not complete immediately, the callback of performTask
+ /// will be called at later time.
+ Upstream
+};
+
+class FakeInterface;
+
+/// \brief Imitation of the work done to resolve a query.
+///
+/// An object of this class represents some fake work that should look like
+/// the work needed to perform resolution of one query. No real work is done,
+/// but several steps are scheduled, with characteristics hopefully
+/// corresponding to steps of the real query.
+///
+/// The idea is that benchmark will repeatedly check if the query is done.
+/// If not, it examines the next task by calling nextTask(). Depending on
+/// the result, it'd lock or prepare any shared resources. After that, it'd
+/// call performTask() to do the task. Once the query calls the callback
+/// passed, it can proceed to the next step.
+///
+/// See naive_resolver.cc for example code how this could be done.
+class FakeQuery {
+private:
+ // The queries come only through an interface. Don't let others create.
+ friend class FakeInterface;
+ /// \brief Constructor
+ FakeQuery(FakeInterface& interface);
+public:
+ /// \brief Is work on the query completely done?
+ ///
+ /// If this returns true, do not call performTask or nextTask any more.
+ /// The resolution is done.
+ ///
+ /// \throw isc::InvalidOperation if upstream query is still in progress.
+ bool done() const {
+ if (outstanding_) {
+ isc_throw(isc::InvalidOperation, "Upstream query outstanding");
+ }
+ return (steps_.empty());
+ }
+ /// \brief Callback to signify a task has been performed.
+ typedef boost::function<void()> StepCallback;
+ /// \brief Perform next step in the resolution.
+ ///
+ /// Do whatever is needed to be done for the next step of resolution.
+ /// Once the step is done, the callback is called.
+ ///
+ /// The callback is usually called from within this call. However, in
+ /// the case when the nextTask() returned `Upstream`, the call to the
+ /// callback is delayed for some period of time after the method
+ /// returns.
+ ///
+ /// \throw isc::InvalidOperation if it is called when done() is true, or
+ /// if an upstream query is still in progress (performTask was called
+ /// before and the callback was not called by the query yet).
+ void performTask(const StepCallback& callback);
+ /// \brief Examine the kind of the next resolution process.
+ ///
+ /// Call this to know what kind of task will performTask do next.
+ ///
+ /// \throw isc::InvalidOperation if it is called when done() is true, or
+ /// if an upstream query is still in progress (performTask was called
+ /// before and the callback was not called by the query yet).
+ Task nextTask() const {
+ // Will check for outstanding_ internally too
+ if (done()) {
+ isc_throw(isc::InvalidOperation, "We are done, no more tasks");
+ }
+ return (steps_.back().first);
+ }
+ /// \brief Move network communication to different interface.
+ ///
+ /// By default, a query does all the "communication" on the interface
+ /// it was born on. This may be used to move a query from one interface
+ /// to another.
+ ///
+ /// You don't have to lock either of the interfaces to do so, this
+ /// only switches the data in the query.
+ ///
+ /// \throw isc::InvalidOperation if it is called while an upstream query
+ /// is in progress.
+ void migrateTo(FakeInterface& dst_interface) {
+ if (outstanding_) {
+ isc_throw(isc::InvalidOperation,
+ "Can't migrate in the middle of query");
+ }
+ interface_ = &dst_interface;
+ }
+ /// \brief The answer for upstream query was received
+ ///
+ /// This should be called from within the FakeInterface only.
+ /// It marks that the query from upstream was answered.
+ void answerReceived() {
+ outstanding_ = false;
+ }
+private:
+ // The scheduled steps for this task.
+ typedef std::pair<Task, size_t> Step;
+ // The scheduled steps. Reversed (first to be done at the end), so we can
+ // pop_back() the completed steps.
+ std::vector<Step> steps_;
+ // The interface to schedule timeouts on.
+ FakeInterface* interface_;
+ // Is an upstream query outstanding?
+ bool outstanding_;
+};
+
+typedef boost::shared_ptr<FakeQuery> FakeQueryPtr;
+
+/// \brief An imitation of interface for receiving queries.
+///
+/// This is effectively a little bit smarter factory for queries. You can
+/// request a new query from it, or let process events (incoming answers).
+///
+/// It contains its own event loop. If the benchmark has more threads, have
+/// one in each of the threads (if the threads ever handles network
+/// communication -- if it accepts queries, sends answers or does upstream
+/// queries).
+///
+/// If the model simulated would share the same interface between multiple
+/// threads, it is better to have one in each thread as well, but lock
+/// access to receiveQuery() so only one is used at once (no idea what happens
+/// if ASIO loop is accessed from multiple threads).
+///
+/// Note that the creation of the queries is not thread safe (due to
+/// the random() function inside). The interface generates all its queries
+/// in advance, on creation time. But you need to create all the needed
+/// interfaces from single thread and then distribute them to your threads.
+class FakeInterface {
+public:
+ /// \brief Constructor
+ ///
+ /// Initiarile the interface and create query_count queries for the
+ /// benchmark. They will be handed out one by one with receiveQuery().
+ FakeInterface(size_t query_count);
+ /// \brief Wait for answers from upstream servers.
+ ///
+ /// Wait until at least one "answer" comes from the remote server. This
+ /// will effectively block the calling thread until it is time to call
+ /// a callback of performTask.
+ ///
+ /// It is not legal to call it without any outstanding upstream queries
+ /// on this interface. However, the situation is not explicitly checked.
+ ///
+ /// \note Due to internal implementation, it is not impossible no or more
+ /// than one callbacks to be called from within this method.
+ void processEvents();
+ /// \brief Accept another query.
+ ///
+ /// Generate a new fake query to resolve.
+ ///
+ /// This method might call callbacks of other queries waiting for upstream
+ /// answer.
+ ///
+ /// This returns a NULL pointer when there are no more queries to answer
+ /// (the number designated for the benchmark was reached).
+ FakeQueryPtr receiveQuery();
+private:
+ class UpstreamQuery;
+ friend class FakeQuery;
+ void scheduleUpstreamAnswer(FakeQuery* query,
+ const FakeQuery::StepCallback& callback,
+ size_t msec);
+ asiolink::IOService service_;
+ std::vector<FakeQueryPtr> queries_;
+};
+
+}
+}
+}
+
+#endif
diff --git a/src/bin/resolver/bench/main.cc b/src/bin/resolver/bench/main.cc
new file mode 100644
index 0000000..3007c40
--- /dev/null
+++ b/src/bin/resolver/bench/main.cc
@@ -0,0 +1,27 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <resolver/bench/naive_resolver.h>
+
+#include <bench/benchmark.h>
+
+const size_t count = 1000; // TODO: We may want to read this from argv.
+
+int main(int, const char**) {
+ // Run the naive implementation
+ isc::resolver::bench::NaiveResolver naive_resolver(count);
+ isc::bench::BenchMark<isc::resolver::bench::NaiveResolver>
+ (1, naive_resolver, true);
+ return 0;
+}
diff --git a/src/bin/resolver/bench/naive_resolver.cc b/src/bin/resolver/bench/naive_resolver.cc
new file mode 100644
index 0000000..1654496
--- /dev/null
+++ b/src/bin/resolver/bench/naive_resolver.cc
@@ -0,0 +1,66 @@
+// 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 <resolver/bench/naive_resolver.h>
+
+#include <cassert>
+#include <boost/bind.hpp>
+
+namespace isc {
+namespace resolver {
+namespace bench {
+
+NaiveResolver::NaiveResolver(size_t query_count) :
+ interface_(query_count),
+ processed_(false)
+{}
+
+namespace {
+
+void
+stepDone(bool* flag) {
+ *flag = true;
+}
+
+}
+
+size_t
+NaiveResolver::run() {
+ assert(!processed_);
+ size_t count = 0;
+ FakeQueryPtr query;
+ // Process a query at a time. As the previous is already handled, the
+ // receiveQuery may never trigger other events.
+ while ((query = interface_.receiveQuery())) {
+ // Handle each step
+ while (!query->done()) {
+ bool done = false; // This step is not yet done.
+ // If there were more queries/threads/whatever, we would examine
+ // the query->nextTask() and lock or prepare resources accordingly.
+ // But as there's just one, we simply do the task, without caring.
+ query->performTask(boost::bind(&stepDone, &done));
+ // We may need to wait for the upstream query.
+ while (!done) {
+ interface_.processEvents();
+ }
+ }
+ count ++;
+ }
+ processed_ = true;
+ return (count);
+}
+
+}
+}
+}
diff --git a/src/bin/resolver/bench/naive_resolver.h b/src/bin/resolver/bench/naive_resolver.h
new file mode 100644
index 0000000..e1deff1
--- /dev/null
+++ b/src/bin/resolver/bench/naive_resolver.h
@@ -0,0 +1,44 @@
+// 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 RESOLVER_BENCH_NAIVE_H
+#define RESOLVER_BENCH_NAIVE_H
+
+#include <resolver/bench/fake_resolution.h>
+
+namespace isc {
+namespace resolver {
+namespace bench {
+
+/// \brief Naive implementation of resolver for the benchmark
+///
+/// This is here mostly to show how to implement the other benchmark
+/// implementations. Look at the code inside how to use the fake
+/// resolution.
+class NaiveResolver {
+public:
+ /// \brief Constructor. Initializes the data.
+ NaiveResolver(size_t query_count);
+ /// \brief Run the resolution.
+ size_t run();
+private:
+ FakeInterface interface_;
+ bool processed_;
+};
+
+}
+}
+}
+
+#endif
diff --git a/src/bin/sockcreator/README b/src/bin/sockcreator/README
index e142d19..48b10dd 100644
--- a/src/bin/sockcreator/README
+++ b/src/bin/sockcreator/README
@@ -35,7 +35,7 @@ must be a socket, not pipe.
The answer to this is either 'S' directly followed by the socket (using
sendmsg) if it is successful. If it fails, 'E' is returned instead, followed
by either 'S' or 'B' (either socket() or bind() call failed). Then there is
- one int (architecture-dependent length and endianess), which is the errno
+ one int (architecture-dependent length and endianness), which is the errno
value after the failure.
The creator may also send these messages at any time (but not in the middle
diff --git a/src/bin/sockcreator/sockcreator.h b/src/bin/sockcreator/sockcreator.h
index 8e32c48..a05cf6a 100644
--- a/src/bin/sockcreator/sockcreator.h
+++ b/src/bin/sockcreator/sockcreator.h
@@ -85,7 +85,7 @@ typedef int (*close_t)(int);
/// \param type The type of socket to create (SOCK_STREAM, SOCK_DGRAM, etc).
/// \param bind_addr The address to bind.
/// \param addr_len The actual length of bind_addr.
-/// \param close_fun The furction used to close a socket if there's an error
+/// \param close_fun The function used to close a socket if there's an error
/// after the creation.
///
/// \return The file descriptor of the newly created socket, if everything
diff --git a/src/bin/stats/stats.py.in b/src/bin/stats/stats.py.in
index 577afe6..7ec530b 100755
--- a/src/bin/stats/stats.py.in
+++ b/src/bin/stats/stats.py.in
@@ -194,7 +194,7 @@ class Stats:
'''Constructor
module_ccsession_class is parameterized so that test can specify
- a mocked class to test the behavior without involing network I/O.
+ a mocked class to test the behavior without involving network I/O.
In other cases this parameter shouldn't be specified.
'''
diff --git a/src/bin/stats/stats_httpd.py.in b/src/bin/stats/stats_httpd.py.in
index fd9ac93..c3cdb76 100755
--- a/src/bin/stats/stats_httpd.py.in
+++ b/src/bin/stats/stats_httpd.py.in
@@ -35,6 +35,7 @@ import re
import isc.cc
import isc.config
import isc.util.process
+from isc.util.address_formatter import AddressFormatter
import isc.log
from isc.log_messages.stats_httpd_messages import *
@@ -325,8 +326,8 @@ class StatsHttpd:
server_address, HttpHandler,
self.xml_handler, self.xsd_handler, self.xsl_handler,
self.write_log)
- logger.info(STATSHTTPD_STARTED, server_address[0],
- server_address[1])
+ logger.info(STATSHTTPD_STARTED,
+ AddressFormatter(server_address, address_family))
return httpd
except (socket.gaierror, socket.error,
OverflowError, TypeError) as err:
@@ -341,8 +342,8 @@ class StatsHttpd:
"""Closes sockets for HTTP"""
while len(self.httpd)>0:
ht = self.httpd.pop()
- logger.info(STATSHTTPD_CLOSING, ht.server_address[0],
- ht.server_address[1])
+ logger.info(STATSHTTPD_CLOSING,
+ AddressFormatter(ht.server_address))
ht.server_close()
def start(self):
@@ -406,7 +407,7 @@ class StatsHttpd:
old_config = self.config.copy()
self.load_config(new_config)
# If the http sockets aren't opened or
- # if new_config doesn't have'listen_on', it returns
+ # if new_config doesn't have 'listen_on', it returns
if len(self.httpd) == 0 or 'listen_on' not in new_config:
return isc.config.ccsession.create_answer(0)
self.close_httpd()
diff --git a/src/bin/stats/stats_httpd_messages.mes b/src/bin/stats/stats_httpd_messages.mes
index 93491b6..930a745 100644
--- a/src/bin/stats/stats_httpd_messages.mes
+++ b/src/bin/stats/stats_httpd_messages.mes
@@ -24,7 +24,7 @@ The stats-httpd module was unable to connect to the BIND 10 command
and control bus. A likely problem is that the message bus daemon
(b10-msgq) is not running. The stats-httpd module will now shut down.
-% STATSHTTPD_CLOSING closing %1#%2
+% STATSHTTPD_CLOSING closing %1
The stats-httpd daemon will stop listening for requests on the given
address and port number.
@@ -80,7 +80,7 @@ and an error is sent back.
% STATSHTTPD_SHUTDOWN shutting down
The stats-httpd daemon is shutting down.
-% STATSHTTPD_STARTED listening on %1#%2
+% STATSHTTPD_STARTED listening on %1
The stats-httpd daemon will now start listening for requests on the
given address and port number.
diff --git a/src/bin/stats/tests/Makefile.am b/src/bin/stats/tests/Makefile.am
index 7484e5d..06d11a1 100644
--- a/src/bin/stats/tests/Makefile.am
+++ b/src/bin/stats/tests/Makefile.am
@@ -1,7 +1,7 @@
SUBDIRS = testdata .
PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
-PYTESTS = b10-stats_test.py b10-stats-httpd_test.py
+PYTESTS = stats_test.py stats-httpd_test.py
EXTRA_DIST = $(PYTESTS) test_utils.py
CLEANFILES = test_utils.pyc
@@ -9,7 +9,7 @@ CLEANFILES = test_utils.pyc
# 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/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/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/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/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
endif
# test using command-line arguments, so use check-local target instead of TESTS
diff --git a/src/bin/stats/tests/b10-stats-httpd_test.py b/src/bin/stats/tests/b10-stats-httpd_test.py
deleted file mode 100644
index 9a463ab..0000000
--- a/src/bin/stats/tests/b10-stats-httpd_test.py
+++ /dev/null
@@ -1,1082 +0,0 @@
-# Copyright (C) 2011-2012 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.
-
-"""
-In each of these tests we start several virtual components. They are
-not the real components, no external processes are started. They are
-just simple mock objects running each in its own thread and pretending
-to be bind10 modules. This helps testing the stats http server in a
-close to real environment.
-"""
-
-import unittest
-import os
-import imp
-import socket
-import errno
-import select
-import string
-import time
-import threading
-import http.client
-import xml.etree.ElementTree
-import random
-import urllib.parse
-import sys
-# load this module for xml validation with xsd. For this test, an
-# installation of lxml is required in advance. See http://lxml.de/.
-try:
- from lxml import etree as lxml_etree
-except ImportError:
- lxml_etree = None
-
-import isc
-import isc.log
-import stats_httpd
-import stats
-from test_utils import BaseModules, ThreadingServerManager, MyStats,\
- MyStatsHttpd, SignalHandler,\
- send_command, CONST_BASETIME
-from isc.testutils.ccsession_mock import MockModuleCCSession
-
-# This test suite uses xml.etree.ElementTree.XMLParser via
-# xml.etree.ElementTree.parse. On the platform where expat isn't
-# installed, ImportError is raised and it's failed. Check expat is
-# available before the test invocation. Skip this test if it's
-# unavailable.
-try:
- # ImportError raised if xpat is unavailable
- xml_parser = xml.etree.ElementTree.XMLParser()
-except ImportError:
- xml_parser = None
-
-# set XML Namespaces for testing
-XMLNS_XSL = "http://www.w3.org/1999/XSL/Transform"
-XMLNS_XHTML = "http://www.w3.org/1999/xhtml"
-XMLNS_XSD = "http://www.w3.org/2001/XMLSchema"
-XMLNS_XSI = stats_httpd.XMLNS_XSI
-
-DUMMY_DATA = {
- 'Init' : {
- "boot_time": time.strftime('%Y-%m-%dT%H:%M:%SZ', CONST_BASETIME)
- },
- 'Auth' : {
- "queries.tcp": 6,
- "queries.udp": 4,
- "queries.perzone": [{
- "zonename": "test1.example",
- "queries.tcp": 10,
- "queries.udp": 8
- }, {
- "zonename": "test2.example",
- "queries.tcp": 8,
- "queries.udp": 6
- }],
- "nds_queries.perzone": {
- "test10.example": {
- "queries.tcp": 10,
- "queries.udp": 8
- },
- "test20.example": {
- "queries.tcp": 8,
- "queries.udp": 6
- }
- }
- },
- 'Stats' : {
- "report_time": time.strftime('%Y-%m-%dT%H:%M:%SZ', CONST_BASETIME),
- "boot_time": time.strftime('%Y-%m-%dT%H:%M:%SZ', CONST_BASETIME),
- "last_update_time": time.strftime('%Y-%m-%dT%H:%M:%SZ', CONST_BASETIME),
- "lname": "4d70d40a_c at host",
- "timestamp": time.mktime(CONST_BASETIME)
- }
- }
-
-def get_availaddr(address='127.0.0.1', port=8001):
- """returns a tuple of address and port which is available to
- listen on the platform. The first argument is a address for
- search. The second argument is a port for search. If a set of
- address and port is failed on the search for the availability, the
- port number is increased and it goes on the next trial until the
- available set of address and port is looked up. If the port number
- reaches over 65535, it may stop the search and raise a
- OverflowError exception."""
- while True:
- for addr in socket.getaddrinfo(
- address, port, 0,
- socket.SOCK_STREAM, socket.IPPROTO_TCP):
- sock = socket.socket(addr[0], socket.SOCK_STREAM)
- try:
- sock.bind((address, port))
- return (address, port)
- except socket.error:
- continue
- finally:
- if sock: sock.close()
- # This address and port number are already in use.
- # next port number is added
- port = port + 1
-
-def is_ipv6_enabled(address='::1', port=8001):
- """checks IPv6 enabled on the platform. address for check is '::1'
- and port for check is random number between 8001 and
- 65535. Retrying is 3 times even if it fails. The built-in socket
- module provides a 'has_ipv6' parameter, but it's not used here
- because there may be a situation where the value is True on an
- environment where the IPv6 config is disabled."""
- for p in random.sample(range(port, 65535), 3):
- try:
- sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
- sock.bind((address, p))
- return True
- except socket.error:
- continue
- finally:
- if sock: sock.close()
- return False
-
-class TestItemNameList(unittest.TestCase):
-
- def test_item_name_list(self):
- # for a one-element list
- self.assertEqual(['a'],
- stats_httpd.item_name_list({'a':1}, 'a'))
- # for a dict under a dict
- self.assertEqual(['a','a/b'],
- stats_httpd.item_name_list({'a':{'b':1}}, 'a'))
- self.assertEqual(['a/b'],
- stats_httpd.item_name_list({'a':{'b':1}}, 'a/b'))
- self.assertEqual(['a','a/b','a/b/c'],
- stats_httpd.item_name_list({'a':{'b':{'c':1}}}, 'a'))
- self.assertEqual(['a/b','a/b/c'],
- stats_httpd.item_name_list({'a':{'b':{'c':1}}},
- 'a/b'))
- self.assertEqual(['a/b/c'],
- stats_httpd.item_name_list({'a':{'b':{'c':1}}},
- 'a/b/c'))
- # for a list under a dict
- self.assertEqual(['a[2]'],
- stats_httpd.item_name_list({'a':[1,2,3]}, 'a[2]'))
- self.assertEqual(['a', 'a[0]', 'a[1]', 'a[2]'],
- stats_httpd.item_name_list({'a':[1,2,3]}, 'a'))
- self.assertEqual(['a', 'a[0]', 'a[1]', 'a[2]'],
- stats_httpd.item_name_list({'a':[1,2,3]}, ''))
- # for a list under a dict under a dict
- self.assertEqual(['a', 'a/b', 'a/b[0]', 'a/b[1]', 'a/b[2]'],
- stats_httpd.item_name_list({'a':{'b':[1,2,3]}}, 'a'))
- self.assertEqual(['a', 'a/b', 'a/b[0]', 'a/b[1]', 'a/b[2]'],
- stats_httpd.item_name_list({'a':{'b':[1,2,3]}}, ''))
- self.assertEqual(['a/b', 'a/b[0]', 'a/b[1]', 'a/b[2]'],
- stats_httpd.item_name_list({'a':{'b':[1,2,3]}}, 'a/b'))
- # for a mixed case of the above
- self.assertEqual(['a', 'a/b', 'a/b[0]', 'a/b[1]', 'a/b[2]', 'a/c'],
- stats_httpd.item_name_list(
- {'a':{'b':[1,2,3], 'c':1}}, 'a'))
- self.assertEqual(['a/b', 'a/b[0]', 'a/b[1]', 'a/b[2]'],
- stats_httpd.item_name_list(
- {'a':{'b':[1,2,3], 'c':1}}, 'a/b'))
- self.assertEqual(['a/c'],
- stats_httpd.item_name_list(
- {'a':{'b':[1,2,3], 'c':1}}, 'a/c'))
- # for specifying a wrong identifier which is not found in
- # element
- self.assertRaises(isc.cc.data.DataNotFoundError,
- stats_httpd.item_name_list, {'x':1}, 'a')
- # for specifying a string in element and an empty string in
- # identifier
- self.assertEqual([],
- stats_httpd.item_name_list('a', ''))
- # for specifying empty strings in element and identifier
- self.assertEqual([],
- stats_httpd.item_name_list('', ''))
- # for specifying wrong element, which is an non-empty string,
- # and an non-empty string in identifier
- self.assertRaises(isc.cc.data.DataTypeError,
- stats_httpd.item_name_list, 'a', 'a')
- # for specifying None in element and identifier
- self.assertRaises(isc.cc.data.DataTypeError,
- stats_httpd.item_name_list, None, None)
- # for specifying non-dict in element
- self.assertRaises(isc.cc.data.DataTypeError,
- stats_httpd.item_name_list, [1,2,3], 'a')
- self.assertRaises(isc.cc.data.DataTypeError,
- stats_httpd.item_name_list, [1,2,3], '')
- # for checking key names sorted which consist of element
- num = 11
- keys = [ 'a', 'aa', 'b' ]
- keys.sort(reverse=True)
- dictlist = dict([ (k, list(range(num))) for k in keys ])
- keys.sort()
- ans = []
- for k in keys:
- ans += [k] + [ '%s[%d]' % (k, i) for i in range(num) ]
- self.assertEqual(ans,
- stats_httpd.item_name_list(dictlist, ''))
-
-class TestHttpHandler(unittest.TestCase):
- """Tests for HttpHandler class"""
- def setUp(self):
- # set the signal handler for deadlock
- self.sig_handler = SignalHandler(self.fail)
- self.base = BaseModules()
- self.stats_server = ThreadingServerManager(MyStats)
- self.stats = self.stats_server.server
- DUMMY_DATA['Stats']['lname'] = self.stats.cc_session.lname
- self.stats_server.run()
- (self.address, self.port) = get_availaddr()
- self.stats_httpd_server = ThreadingServerManager(MyStatsHttpd, (self.address, self.port))
- self.stats_httpd = self.stats_httpd_server.server
- self.stats_httpd_server.run()
- self.client = http.client.HTTPConnection(self.address, self.port)
- self.client._http_vsn_str = 'HTTP/1.0\n'
- self.client.connect()
-
- def tearDown(self):
- self.client.close()
- self.stats_httpd_server.shutdown()
- self.stats_server.shutdown()
- self.base.shutdown()
- # reset the signal handler
- self.sig_handler.reset()
-
- @unittest.skipIf(sys.version_info >= (3, 3), "Unsupported in Python 3.3 or higher")
- @unittest.skipUnless(xml_parser, "skipping the test using XMLParser")
- def test_do_GET(self):
- self.assertTrue(type(self.stats_httpd.httpd) is list)
- self.assertEqual(len(self.stats_httpd.httpd), 1)
- self.assertEqual((self.address, self.port), self.stats_httpd.http_addrs[0])
-
- def check_XML_URL_PATH(path=''):
- url_path = '%s/%s' % (stats_httpd.XML_URL_PATH, path)
- url_path = urllib.parse.quote(url_path)
- self.client.putrequest('GET', url_path)
- self.client.endheaders()
- response = self.client.getresponse()
- self.assertEqual(response.getheader("Content-type"), "text/xml")
- self.assertGreater(int(response.getheader("Content-Length")), 0)
- self.assertEqual(response.status, 200)
- xml_doctype = response.readline().decode()
- xsl_doctype = response.readline().decode()
- self.assertGreater(len(xml_doctype), 0)
- self.assertGreater(len(xsl_doctype), 0)
- root = xml.etree.ElementTree.parse(response).getroot()
- self.assertGreater(root.tag.find('statistics'), 0)
- schema_loc = '{%s}schemaLocation' % XMLNS_XSI
- # check the path of XSD
- self.assertEqual(root.attrib[schema_loc],
- stats_httpd.XSD_NAMESPACE + ' '
- + stats_httpd.XSD_URL_PATH)
- # check the path of XSL
- self.assertTrue(xsl_doctype.startswith(
- '<?xml-stylesheet type="text/xsl" href="' +
- stats_httpd.XSL_URL_PATH
- + '"?>'))
- # check whether the list of 'identifier' attributes in
- # root is same as the list of item names in DUMMY_DATA
- id_list = [ elm.attrib['identifier'] for elm in root ]
- item_list = [ it for it in \
- stats_httpd.item_name_list(DUMMY_DATA, path) \
- if len(it.split('/')) > 1 ]
- self.assertEqual(id_list, item_list)
- for elem in root:
- attr = elem.attrib
- value = isc.cc.data.find(DUMMY_DATA, attr['identifier'])
- # No 'value' attribute should be found in the 'item'
- # element when datatype of the value is list or dict.
- if type(value) is list or type(value) is dict:
- self.assertFalse('value' in attr)
- # The value of the 'value' attribute should be checked
- # after casting it to string type if datatype of the
- # value is int or float. Because attr['value'] returns
- # string type even if its value is int or float.
- elif type(value) is int or type(value) is float:
- self.assertEqual(attr['value'], str(value))
- else:
- self.assertEqual(attr['value'], value)
-
- # URL is '/bind10/statistics/xml'
- check_XML_URL_PATH()
- for path in stats_httpd.item_name_list(DUMMY_DATA, ''):
- check_XML_URL_PATH(path)
-
- def check_XSD_URL_PATH():
- url_path = stats_httpd.XSD_URL_PATH
- url_path = urllib.parse.quote(url_path)
- self.client.putrequest('GET', url_path)
- self.client.endheaders()
- response = self.client.getresponse()
- self.assertEqual(response.getheader("Content-type"), "text/xml")
- self.assertGreater(int(response.getheader("Content-Length")), 0)
- self.assertEqual(response.status, 200)
- root = xml.etree.ElementTree.parse(response).getroot()
- url_xmlschema = '{%s}' % XMLNS_XSD
- self.assertGreater(root.tag.find('schema'), 0)
- self.assertTrue(hasattr(root, 'attrib'))
- self.assertTrue('targetNamespace' in root.attrib)
- self.assertEqual(root.attrib['targetNamespace'],
- stats_httpd.XSD_NAMESPACE)
-
- # URL is '/bind10/statistics/xsd'
- check_XSD_URL_PATH()
-
- def check_XSL_URL_PATH():
- url_path = stats_httpd.XSL_URL_PATH
- url_path = urllib.parse.quote(url_path)
- self.client.putrequest('GET', url_path)
- self.client.endheaders()
- response = self.client.getresponse()
- self.assertEqual(response.getheader("Content-type"), "text/xml")
- self.assertGreater(int(response.getheader("Content-Length")), 0)
- self.assertEqual(response.status, 200)
- root = xml.etree.ElementTree.parse(response).getroot()
- url_trans = '{%s}' % XMLNS_XSL
- url_xhtml = '{%s}' % XMLNS_XHTML
- self.assertEqual(root.tag, url_trans + 'stylesheet')
-
- # URL is '/bind10/statistics/xsl'
- check_XSL_URL_PATH()
-
- # 302 redirect
- self.client._http_vsn_str = 'HTTP/1.1'
- self.client.putrequest('GET', '/')
- self.client.putheader('Host', self.address)
- self.client.endheaders()
- response = self.client.getresponse()
- self.assertEqual(response.status, 302)
- self.assertEqual(response.getheader('Location'),
- "http://%s:%d%s/" % (self.address, self.port, stats_httpd.XML_URL_PATH))
-
- # 404 NotFound (random path)
- self.client._http_vsn_str = 'HTTP/1.0'
- self.client.putrequest('GET', '/path/to/foo/bar')
- self.client.endheaders()
- response = self.client.getresponse()
- self.assertEqual(response.status, 404)
- self.client._http_vsn_str = 'HTTP/1.0'
- self.client.putrequest('GET', '/bind10/foo')
- self.client.endheaders()
- response = self.client.getresponse()
- self.assertEqual(response.status, 404)
- self.client._http_vsn_str = 'HTTP/1.0'
- self.client.putrequest('GET', '/bind10/statistics/foo')
- self.client.endheaders()
- response = self.client.getresponse()
- self.assertEqual(response.status, 404)
- self.client._http_vsn_str = 'HTTP/1.0'
- self.client.putrequest('GET', stats_httpd.XML_URL_PATH + 'Auth') # with no slash
- self.client.endheaders()
- response = self.client.getresponse()
- self.assertEqual(response.status, 404)
-
- # 200 ok
- self.client._http_vsn_str = 'HTTP/1.0'
- self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/')
- self.client.endheaders()
- response = self.client.getresponse()
- self.assertEqual(response.status, 200)
- self.client._http_vsn_str = 'HTTP/1.0'
- self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/#foo')
- self.client.endheaders()
- response = self.client.getresponse()
- self.assertEqual(response.status, 200)
- self.client._http_vsn_str = 'HTTP/1.0'
- self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/?foo=bar')
- self.client.endheaders()
- response = self.client.getresponse()
- self.assertEqual(response.status, 200)
-
- # 404 NotFound (too long path)
- self.client._http_vsn_str = 'HTTP/1.0'
- self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/Init/boot_time/a')
- self.client.endheaders()
- response = self.client.getresponse()
- self.assertEqual(response.status, 404)
-
- # 404 NotFound (nonexistent module name)
- self.client._http_vsn_str = 'HTTP/1.0'
- self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/Foo')
- self.client.endheaders()
- response = self.client.getresponse()
- self.assertEqual(response.status, 404)
- self.client._http_vsn_str = 'HTTP/1.0'
- self.client.putrequest('GET', stats_httpd.XSD_URL_PATH + '/Foo')
- self.client.endheaders()
- response = self.client.getresponse()
- self.assertEqual(response.status, 404)
- self.client._http_vsn_str = 'HTTP/1.0'
- self.client.putrequest('GET', stats_httpd.XSL_URL_PATH + '/Foo')
- self.client.endheaders()
- response = self.client.getresponse()
- self.assertEqual(response.status, 404)
-
- # 404 NotFound (nonexistent item name)
- self.client._http_vsn_str = 'HTTP/1.0'
- self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/Foo/bar')
- self.client.endheaders()
- response = self.client.getresponse()
- self.assertEqual(response.status, 404)
- self.client._http_vsn_str = 'HTTP/1.0'
- self.client.putrequest('GET', stats_httpd.XSD_URL_PATH + '/Foo/bar')
- self.client.endheaders()
- response = self.client.getresponse()
- self.assertEqual(response.status, 404)
- self.client._http_vsn_str = 'HTTP/1.0'
- self.client.putrequest('GET', stats_httpd.XSL_URL_PATH + '/Foo/bar')
- self.client.endheaders()
- response = self.client.getresponse()
- self.assertEqual(response.status, 404)
-
- # 404 NotFound (existent module but nonexistent item name)
- self.client._http_vsn_str = 'HTTP/1.0'
- self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/Auth/bar')
- self.client.endheaders()
- response = self.client.getresponse()
- self.assertEqual(response.status, 404)
- self.client._http_vsn_str = 'HTTP/1.0'
- self.client.putrequest('GET', stats_httpd.XSD_URL_PATH + '/Auth/bar')
- self.client.endheaders()
- response = self.client.getresponse()
- self.assertEqual(response.status, 404)
- self.client._http_vsn_str = 'HTTP/1.0'
- self.client.putrequest('GET', stats_httpd.XSL_URL_PATH + '/Auth/bar')
- self.client.endheaders()
- response = self.client.getresponse()
- self.assertEqual(response.status, 404)
-
- def test_do_GET_failed1(self):
- # checks status
- self.assertEqual(send_command("status", "Stats"),
- (0, "Stats is up. (PID " + str(os.getpid()) + ")"))
- # failure case(Stats is down)
- self.assertTrue(self.stats.running)
- self.assertEqual(send_command("shutdown", "Stats"),
- (0, None)) # Stats is down
- self.assertFalse(self.stats.running)
- self.stats_httpd.cc_session.set_timeout(milliseconds=100)
-
- # request XML
- self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/')
- self.client.endheaders()
- response = self.client.getresponse()
- self.assertEqual(response.status, 500)
-
- # request XSD
- self.client.putrequest('GET', stats_httpd.XSD_URL_PATH)
- self.client.endheaders()
- response = self.client.getresponse()
- self.assertEqual(response.status, 200)
-
- # request XSL
- self.client.putrequest('GET', stats_httpd.XSL_URL_PATH)
- self.client.endheaders()
- response = self.client.getresponse()
- self.assertEqual(response.status, 200)
-
- def test_do_GET_failed2(self):
- # failure case(Stats replies an error)
- self.stats.mccs.set_command_handler(
- lambda cmd, args: \
- isc.config.ccsession.create_answer(1, "specified arguments are incorrect: I have an error.")
- )
-
- # request XML
- self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/')
- self.client.endheaders()
- response = self.client.getresponse()
- self.assertEqual(response.status, 404)
-
- # request XSD
- self.client.putrequest('GET', stats_httpd.XSD_URL_PATH)
- self.client.endheaders()
- response = self.client.getresponse()
- self.assertEqual(response.status, 200)
-
- # request XSL
- self.client.putrequest('GET', stats_httpd.XSL_URL_PATH)
- self.client.endheaders()
- response = self.client.getresponse()
- self.assertEqual(response.status, 200)
-
- def test_do_HEAD(self):
- self.client.putrequest('HEAD', stats_httpd.XML_URL_PATH + '/')
- self.client.endheaders()
- response = self.client.getresponse()
- self.assertEqual(response.status, 200)
-
- self.client.putrequest('HEAD', '/path/to/foo/bar')
- self.client.endheaders()
- response = self.client.getresponse()
- self.assertEqual(response.status, 404)
-
- @unittest.skipUnless(lxml_etree, "skipping XML validation with XSD")
- def test_xml_validation_with_xsd(self):
- """Tests for XML validation with XSD. If lxml is not
- installed, this tests would be skipped."""
- def request_xsd():
- url_path = stats_httpd.XSD_URL_PATH
- url_path = urllib.parse.quote(url_path)
- self.client.putrequest('GET', url_path)
- self.client.endheaders()
- xsd_doc = self.client.getresponse()
- xsd_doc = lxml_etree.parse(xsd_doc)
- return lxml_etree.XMLSchema(xsd_doc)
-
- def request_xmldoc(path=''):
- url_path = '%s/%s' % (stats_httpd.XML_URL_PATH, path)
- url_path = urllib.parse.quote(url_path)
- self.client.putrequest('GET', url_path)
- self.client.endheaders()
- xml_doc = self.client.getresponse()
- return lxml_etree.parse(xml_doc)
-
- # request XSD and XML
- xsd = request_xsd()
- xml_doc = request_xmldoc()
- # do validation
- self.assertTrue(xsd.validate(xml_doc))
-
- # validate each paths in DUMMY_DATA
- for path in stats_httpd.item_name_list(DUMMY_DATA, ''):
- # request XML
- xml_doc = request_xmldoc(path)
- # do validation
- self.assertTrue(xsd.validate(xml_doc))
-
-class TestHttpServerError(unittest.TestCase):
- """Tests for HttpServerError exception"""
- def test_raises(self):
- try:
- raise stats_httpd.HttpServerError('Nothing')
- except stats_httpd.HttpServerError as err:
- self.assertEqual(str(err), 'Nothing')
-
-class TestHttpServer(unittest.TestCase):
- """Tests for HttpServer class"""
- def setUp(self):
- # set the signal handler for deadlock
- self.sig_handler = SignalHandler(self.fail)
- self.base = BaseModules()
-
- def tearDown(self):
- if hasattr(self, "stats_httpd"):
- self.stats_httpd.stop()
- self.base.shutdown()
- # reset the signal handler
- self.sig_handler.reset()
-
- def test_httpserver(self):
- self.stats_httpd = MyStatsHttpd(get_availaddr())
- self.assertEqual(type(self.stats_httpd.httpd), list)
- self.assertEqual(len(self.stats_httpd.httpd), 1)
- for httpd in self.stats_httpd.httpd:
- self.assertTrue(isinstance(httpd, stats_httpd.HttpServer))
-
-class TestStatsHttpdError(unittest.TestCase):
- """Tests for StatsHttpdError exception"""
-
- def test_raises1(self):
- try:
- raise stats_httpd.StatsHttpdError('Nothing')
- except stats_httpd.StatsHttpdError as err:
- self.assertEqual(str(err), 'Nothing')
-
- def test_raises2(self):
- try:
- raise stats_httpd.StatsHttpdDataError('Nothing')
- except stats_httpd.StatsHttpdDataError as err:
- self.assertEqual(str(err), 'Nothing')
-
-class TestStatsHttpd(unittest.TestCase):
- """Tests for StatsHttpd class"""
-
- def setUp(self):
- # set the signal handler for deadlock
- self.sig_handler = SignalHandler(self.fail)
- self.base = BaseModules()
- self.stats_server = ThreadingServerManager(MyStats)
- self.stats_server.run()
- # checking IPv6 enabled on this platform
- self.ipv6_enabled = is_ipv6_enabled()
- # instantiation of StatsHttpd indirectly calls gethostbyaddr(), which
- # can block for an uncontrollable period, leading many undesirable
- # results. We should rather eliminate the reliance, but until we
- # can make such fundamental cleanup we replace it with a faked method;
- # in our test scenario the return value doesn't matter.
- self.__gethostbyaddr_orig = socket.gethostbyaddr
- socket.gethostbyaddr = lambda x: ('test.example.', [], None)
-
- def tearDown(self):
- socket.gethostbyaddr = self.__gethostbyaddr_orig
- if hasattr(self, "stats_httpd"):
- self.stats_httpd.stop()
- self.stats_server.shutdown()
- self.base.shutdown()
- # reset the signal handler
- self.sig_handler.reset()
-
- def test_init(self):
- server_address = get_availaddr()
- self.stats_httpd = MyStatsHttpd(server_address)
- self.assertEqual(self.stats_httpd.running, False)
- self.assertEqual(self.stats_httpd.poll_intval, 0.5)
- self.assertNotEqual(len(self.stats_httpd.httpd), 0)
- self.assertEqual(type(self.stats_httpd.mccs), isc.config.ModuleCCSession)
- self.assertEqual(type(self.stats_httpd.cc_session), isc.cc.Session)
- self.assertEqual(len(self.stats_httpd.config), 2)
- self.assertTrue('listen_on' in self.stats_httpd.config)
- self.assertEqual(len(self.stats_httpd.config['listen_on']), 1)
- self.assertTrue('address' in self.stats_httpd.config['listen_on'][0])
- self.assertTrue('port' in self.stats_httpd.config['listen_on'][0])
- self.assertTrue(server_address in set(self.stats_httpd.http_addrs))
- ans = send_command(
- isc.config.ccsession.COMMAND_GET_MODULE_SPEC,
- "ConfigManager", {"module_name":"StatsHttpd"})
- # assert StatsHttpd is added to ConfigManager
- self.assertNotEqual(ans, (0,{}))
- self.assertTrue(ans[1]['module_name'], 'StatsHttpd')
-
- def test_init_hterr(self):
- orig_open_httpd = stats_httpd.StatsHttpd.open_httpd
- def err_open_httpd(arg): raise stats_httpd.HttpServerError
- stats_httpd.StatsHttpd.open_httpd = err_open_httpd
- self.assertRaises(stats_httpd.HttpServerError, stats_httpd.StatsHttpd)
- ans = send_command(
- isc.config.ccsession.COMMAND_GET_MODULE_SPEC,
- "ConfigManager", {"module_name":"StatsHttpd"})
- # assert StatsHttpd is removed from ConfigManager
- self.assertEqual(ans, (0,{}))
- stats_httpd.StatsHttpd.open_httpd = orig_open_httpd
-
- def test_openclose_mccs(self):
- self.stats_httpd = MyStatsHttpd(get_availaddr())
- mccs = MockModuleCCSession()
- self.stats_httpd.mccs = mccs
- self.assertFalse(self.stats_httpd.mccs.stopped)
- self.assertFalse(self.stats_httpd.mccs.closed)
- self.stats_httpd.close_mccs()
- self.assertTrue(mccs.stopped)
- self.assertTrue(mccs.closed)
- self.assertEqual(self.stats_httpd.mccs, None)
- self.stats_httpd.open_mccs()
- self.assertIsNotNone(self.stats_httpd.mccs)
- self.stats_httpd.mccs = None
- self.assertEqual(self.stats_httpd.mccs, None)
- self.assertEqual(self.stats_httpd.close_mccs(), None)
-
- def test_mccs(self):
- self.stats_httpd = MyStatsHttpd(get_availaddr())
- self.assertIsNotNone(self.stats_httpd.mccs.get_socket())
- self.assertTrue(
- isinstance(self.stats_httpd.mccs.get_socket(), socket.socket))
- self.assertTrue(
- isinstance(self.stats_httpd.cc_session, isc.cc.session.Session))
- statistics_spec = self.stats_httpd.get_stats_spec()
- for mod in DUMMY_DATA:
- self.assertTrue(mod in statistics_spec)
- for cfg in statistics_spec[mod]:
- self.assertTrue('item_name' in cfg)
- self.assertTrue(cfg['item_name'] in DUMMY_DATA[mod])
- self.assertTrue(len(statistics_spec[mod]), len(DUMMY_DATA[mod]))
- self.stats_httpd.close_mccs()
- self.assertIsNone(self.stats_httpd.mccs)
-
- def test_httpd(self):
- # dual stack (addresses is ipv4 and ipv6)
- if self.ipv6_enabled:
- server_addresses = (get_availaddr('::1'), get_availaddr())
- self.stats_httpd = MyStatsHttpd(*server_addresses)
- for ht in self.stats_httpd.httpd:
- self.assertTrue(isinstance(ht, stats_httpd.HttpServer))
- self.assertTrue(ht.address_family in set([socket.AF_INET, socket.AF_INET6]))
- self.assertTrue(isinstance(ht.socket, socket.socket))
-
- # dual stack (address is ipv6)
- if self.ipv6_enabled:
- server_addresses = get_availaddr('::1')
- self.stats_httpd = MyStatsHttpd(server_addresses)
- for ht in self.stats_httpd.httpd:
- self.assertTrue(isinstance(ht, stats_httpd.HttpServer))
- self.assertEqual(ht.address_family, socket.AF_INET6)
- self.assertTrue(isinstance(ht.socket, socket.socket))
-
- # dual/single stack (address is ipv4)
- server_addresses = get_availaddr()
- self.stats_httpd = MyStatsHttpd(server_addresses)
- for ht in self.stats_httpd.httpd:
- self.assertTrue(isinstance(ht, stats_httpd.HttpServer))
- self.assertEqual(ht.address_family, socket.AF_INET)
- self.assertTrue(isinstance(ht.socket, socket.socket))
-
- def test_httpd_anyIPv4(self):
- # any address (IPv4)
- server_addresses = get_availaddr(address='0.0.0.0')
- self.stats_httpd = MyStatsHttpd(server_addresses)
- for ht in self.stats_httpd.httpd:
- self.assertTrue(isinstance(ht, stats_httpd.HttpServer))
- self.assertEqual(ht.address_family,socket.AF_INET)
- self.assertTrue(isinstance(ht.socket, socket.socket))
-
- def test_httpd_anyIPv6(self):
- # any address (IPv6)
- if self.ipv6_enabled:
- server_addresses = get_availaddr(address='::')
- self.stats_httpd = MyStatsHttpd(server_addresses)
- for ht in self.stats_httpd.httpd:
- self.assertTrue(isinstance(ht, stats_httpd.HttpServer))
- self.assertEqual(ht.address_family,socket.AF_INET6)
- self.assertTrue(isinstance(ht.socket, socket.socket))
-
- def test_httpd_failed(self):
- # existent hostname
- self.assertRaises(stats_httpd.HttpServerError, MyStatsHttpd,
- get_availaddr(address='localhost'))
-
- # nonexistent hostname
- self.assertRaises(stats_httpd.HttpServerError, MyStatsHttpd, ('my.host.domain', 8000))
-
- # over flow of port number
- self.assertRaises(stats_httpd.HttpServerError, MyStatsHttpd, ('127.0.0.1', 80000))
-
- # negative
- self.assertRaises(stats_httpd.HttpServerError, MyStatsHttpd, ('127.0.0.1', -8000))
-
- # alphabet
- self.assertRaises(stats_httpd.HttpServerError, MyStatsHttpd, ('127.0.0.1', 'ABCDE'))
-
- # Address already in use
- server_addresses = get_availaddr()
- self.stats_httpd_server = ThreadingServerManager(MyStatsHttpd, server_addresses)
- self.stats_httpd_server.run()
- self.assertRaises(stats_httpd.HttpServerError, MyStatsHttpd, server_addresses)
- send_command("shutdown", "StatsHttpd")
-
- def test_running(self):
- self.stats_httpd_server = ThreadingServerManager(MyStatsHttpd, get_availaddr())
- self.stats_httpd = self.stats_httpd_server.server
- self.assertFalse(self.stats_httpd.running)
- self.stats_httpd_server.run()
- self.assertEqual(send_command("status", "StatsHttpd"),
- (0, "Stats Httpd is up. (PID " + str(os.getpid()) + ")"))
- self.assertTrue(self.stats_httpd.running)
- self.assertEqual(send_command("shutdown", "StatsHttpd"), (0, None))
- self.assertFalse(self.stats_httpd.running)
- self.stats_httpd_server.shutdown()
-
- # failure case
- self.stats_httpd = MyStatsHttpd(get_availaddr())
- self.stats_httpd.cc_session.close()
- self.assertRaises(ValueError, self.stats_httpd.start)
-
- def test_failure_with_a_select_error (self):
- """checks select.error is raised if the exception except
- errno.EINTR is raised while it's selecting"""
- def raise_select_except(*args):
- raise select.error('dummy error')
- orig_select = stats_httpd.select.select
- stats_httpd.select.select = raise_select_except
- self.stats_httpd = MyStatsHttpd(get_availaddr())
- self.assertRaises(select.error, self.stats_httpd.start)
- stats_httpd.select.select = orig_select
-
- def test_nofailure_with_errno_EINTR(self):
- """checks no exception is raised if errno.EINTR is raised
- while it's selecting"""
- def raise_select_except(*args):
- raise select.error(errno.EINTR)
- orig_select = stats_httpd.select.select
- stats_httpd.select.select = raise_select_except
- self.stats_httpd_server = ThreadingServerManager(MyStatsHttpd, get_availaddr())
- self.stats_httpd_server.run()
- self.stats_httpd_server.shutdown()
- stats_httpd.select.select = orig_select
-
- def test_open_template(self):
- self.stats_httpd = MyStatsHttpd(get_availaddr())
- # successful conditions
- tmpl = self.stats_httpd.open_template(stats_httpd.XML_TEMPLATE_LOCATION)
- self.assertTrue(isinstance(tmpl, string.Template))
- opts = dict(
- xml_string="<dummy></dummy>",
- xsl_url_path="/path/to/")
- lines = tmpl.substitute(opts)
- for n in opts:
- self.assertGreater(lines.find(opts[n]), 0)
- tmpl = self.stats_httpd.open_template(stats_httpd.XSD_TEMPLATE_LOCATION)
- self.assertTrue(isinstance(tmpl, string.Template))
- opts = dict(xsd_namespace="http://host/path/to/")
- lines = tmpl.substitute(opts)
- for n in opts:
- self.assertGreater(lines.find(opts[n]), 0)
- tmpl = self.stats_httpd.open_template(stats_httpd.XSL_TEMPLATE_LOCATION)
- self.assertTrue(isinstance(tmpl, string.Template))
- opts = dict(xsd_namespace="http://host/path/to/")
- lines = tmpl.substitute(opts)
- for n in opts:
- self.assertGreater(lines.find(opts[n]), 0)
- # unsuccessful condition
- self.assertRaises(
- stats_httpd.StatsHttpdDataError,
- self.stats_httpd.open_template, '/path/to/foo/bar')
-
- def test_commands(self):
- self.stats_httpd = MyStatsHttpd(get_availaddr())
- self.assertEqual(self.stats_httpd.command_handler("status", None),
- isc.config.ccsession.create_answer(
- 0, "Stats Httpd is up. (PID " + str(os.getpid()) + ")"))
- self.stats_httpd.running = True
- self.assertEqual(self.stats_httpd.command_handler("shutdown", None),
- isc.config.ccsession.create_answer(0))
- self.assertFalse(self.stats_httpd.running)
- self.assertEqual(
- self.stats_httpd.command_handler("__UNKNOWN_COMMAND__", None),
- isc.config.ccsession.create_answer(
- 1, "Unknown command: __UNKNOWN_COMMAND__"))
-
- def test_config(self):
- self.stats_httpd = MyStatsHttpd(get_availaddr())
- self.assertEqual(
- self.stats_httpd.config_handler(dict(_UNKNOWN_KEY_=None)),
- isc.config.ccsession.create_answer(
- 1, "unknown item _UNKNOWN_KEY_"))
-
- addresses = get_availaddr()
- self.assertEqual(
- self.stats_httpd.config_handler(
- dict(listen_on=[dict(address=addresses[0],port=addresses[1])])),
- isc.config.ccsession.create_answer(0))
- self.assertTrue("listen_on" in self.stats_httpd.config)
- for addr in self.stats_httpd.config["listen_on"]:
- self.assertTrue("address" in addr)
- self.assertTrue("port" in addr)
- self.assertTrue(addr["address"] == addresses[0])
- self.assertTrue(addr["port"] == addresses[1])
-
- if self.ipv6_enabled:
- addresses = get_availaddr("::1")
- self.assertEqual(
- self.stats_httpd.config_handler(
- dict(listen_on=[dict(address=addresses[0],port=addresses[1])])),
- isc.config.ccsession.create_answer(0))
- self.assertTrue("listen_on" in self.stats_httpd.config)
- for addr in self.stats_httpd.config["listen_on"]:
- self.assertTrue("address" in addr)
- self.assertTrue("port" in addr)
- self.assertTrue(addr["address"] == addresses[0])
- self.assertTrue(addr["port"] == addresses[1])
-
- addresses = get_availaddr()
- self.assertEqual(
- self.stats_httpd.config_handler(
- dict(listen_on=[dict(address=addresses[0],port=addresses[1])])),
- isc.config.ccsession.create_answer(0))
- self.assertTrue("listen_on" in self.stats_httpd.config)
- for addr in self.stats_httpd.config["listen_on"]:
- self.assertTrue("address" in addr)
- self.assertTrue("port" in addr)
- self.assertTrue(addr["address"] == addresses[0])
- self.assertTrue(addr["port"] == addresses[1])
- (ret, arg) = isc.config.ccsession.parse_answer(
- self.stats_httpd.config_handler(
- dict(listen_on=[dict(address="1.2.3.4",port=543210)]))
- )
- self.assertEqual(ret, 1)
-
- @unittest.skipUnless(xml_parser, "skipping the test using XMLParser")
- def test_xml_handler(self):
- self.stats_httpd = MyStatsHttpd(get_availaddr())
- module_name = 'Dummy'
- stats_spec = \
- { module_name :
- [{
- "item_name": "foo",
- "item_type": "string",
- "item_optional": False,
- "item_default": "bar",
- "item_description": "foo is bar",
- "item_title": "Foo"
- },
- {
- "item_name": "foo2",
- "item_type": "list",
- "item_optional": False,
- "item_default": [
- {
- "zonename" : "test1",
- "queries.udp" : 1,
- "queries.tcp" : 2
- },
- {
- "zonename" : "test2",
- "queries.udp" : 3,
- "queries.tcp" : 4
- }
- ],
- "item_title": "Foo bar",
- "item_description": "Foo bar",
- "list_item_spec": {
- "item_name": "foo2-1",
- "item_type": "map",
- "item_optional": False,
- "item_default": {},
- "map_item_spec": [
- {
- "item_name": "foo2-1-1",
- "item_type": "string",
- "item_optional": False,
- "item_default": "",
- "item_title": "Foo2 1 1",
- "item_description": "Foo bar"
- },
- {
- "item_name": "foo2-1-2",
- "item_type": "integer",
- "item_optional": False,
- "item_default": 0,
- "item_title": "Foo2 1 2",
- "item_description": "Foo bar"
- },
- {
- "item_name": "foo2-1-3",
- "item_type": "integer",
- "item_optional": False,
- "item_default": 0,
- "item_title": "Foo2 1 3",
- "item_description": "Foo bar"
- }
- ]
- }
- }]
- }
- stats_data = \
- { module_name : { 'foo':'bar',
- 'foo2': [
- {
- "foo2-1-1" : "bar1",
- "foo2-1-2" : 10,
- "foo2-1-3" : 9
- },
- {
- "foo2-1-1" : "bar2",
- "foo2-1-2" : 8,
- "foo2-1-3" : 7
- }
- ] } }
- self.stats_httpd.get_stats_spec = lambda x,y: stats_spec
- self.stats_httpd.get_stats_data = lambda x,y: stats_data
- xml_string = self.stats_httpd.xml_handler()
- stats_xml = xml.etree.ElementTree.fromstring(xml_string)
- schema_loc = '{%s}schemaLocation' % XMLNS_XSI
- self.assertEqual(stats_xml.attrib[schema_loc],
- stats_httpd.XML_ROOT_ATTRIB['xsi:schemaLocation'])
- stats_data = stats_data[module_name]
- stats_spec = stats_spec[module_name]
- names = stats_httpd.item_name_list(stats_data, '')
- for i in range(0, len(names)):
- self.assertEqual('%s/%s' % (module_name, names[i]), stats_xml[i].attrib['identifier'])
- value = isc.cc.data.find(stats_data, names[i])
- if type(value) is int:
- value = str(value)
- if type(value) is dict or type(value) is list:
- self.assertFalse('value' in stats_xml[i].attrib)
- else:
- self.assertEqual(value, stats_xml[i].attrib['value'])
- self.assertEqual(module_name, stats_xml[i].attrib['owner'])
- self.assertEqual(urllib.parse.quote('%s/%s/%s' % (stats_httpd.XML_URL_PATH,
- module_name, names[i])),
- stats_xml[i].attrib['uri'])
- spec = isc.config.find_spec_part(stats_spec, names[i])
- self.assertEqual(spec['item_name'], stats_xml[i].attrib['name'])
- self.assertEqual(spec['item_type'], stats_xml[i].attrib['type'])
- self.assertEqual(spec['item_description'], stats_xml[i].attrib['description'])
- self.assertEqual(spec['item_title'], stats_xml[i].attrib['title'])
- self.assertEqual(str(spec['item_optional']).lower(), stats_xml[i].attrib['optional'])
- default = spec['item_default']
- if type(default) is int:
- default = str(default)
- if type(default) is dict or type(default) is list:
- self.assertFalse('default' in stats_xml[i].attrib)
- else:
- self.assertEqual(default, stats_xml[i].attrib['default'])
- self.assertFalse('item_format' in spec)
- self.assertFalse('format' in stats_xml[i].attrib)
-
- @unittest.skipUnless(xml_parser, "skipping the test using XMLParser")
- def test_xsd_handler(self):
- self.stats_httpd = MyStatsHttpd(get_availaddr())
- xsd_string = self.stats_httpd.xsd_handler()
- stats_xsd = xml.etree.ElementTree.fromstring(xsd_string)
- ns = '{%s}' % XMLNS_XSD
- stats_xsd = stats_xsd[1].find('%scomplexType/%ssequence/%selement' % ((ns,)*3))
- self.assertEqual('item', stats_xsd.attrib['name'])
- stats_xsd = stats_xsd.find('%scomplexType' % ns)
- type_types = ('boolean', 'integer', 'real', 'string', 'map', \
- 'list', 'named_set', 'any')
- attribs = [('identifier', 'string', 'required'),
- ('value', 'string', 'optional'),
- ('owner', 'string', 'required'),
- ('uri', 'anyURI', 'required'),
- ('name', 'string', 'required'),
- ('type', type_types, 'required'),
- ('description', 'string', 'optional'),
- ('title', 'string', 'optional'),
- ('optional', 'boolean', 'optional'),
- ('default', 'string', 'optional'),
- ('format', 'string', 'optional')]
- for i in range(0, len(attribs)):
- self.assertEqual(attribs[i][0], stats_xsd[i].attrib['name'])
- if attribs[i][0] == 'type':
- stats_xsd_types = \
- stats_xsd[i].find('%ssimpleType/%srestriction' % \
- ((ns,)*2))
- for j in range(0, len(attribs[i][1])):
- self.assertEqual(attribs[i][1][j], \
- stats_xsd_types[j].attrib['value'])
- else:
- self.assertEqual(attribs[i][1], stats_xsd[i].attrib['type'])
- self.assertEqual(attribs[i][2], stats_xsd[i].attrib['use'])
-
- @unittest.skipUnless(xml_parser, "skipping the test using XMLParser")
- def test_xsl_handler(self):
- self.stats_httpd = MyStatsHttpd(get_availaddr())
- xsl_string = self.stats_httpd.xsl_handler()
- stats_xsl = xml.etree.ElementTree.fromstring(xsl_string)
- nst = '{%s}' % XMLNS_XSL
- nsx = '{%s}' % XMLNS_XHTML
- self.assertEqual("bind10:statistics", stats_xsl[2].attrib['match'])
- stats_xsl = stats_xsl[2].find('%stable' % nsx)
- self.assertEqual('item', stats_xsl[1].attrib['select'])
- stats_xsl = stats_xsl[1].find('%str' % nsx)
- self.assertEqual('@uri', stats_xsl[0].find(
- '%selement/%sattribute/%svalue-of' % ((nst,)*3)).attrib['select'])
- self.assertEqual('@identifier', stats_xsl[0].find(
- '%selement/%svalue-of' % ((nst,)*2)).attrib['select'])
- self.assertEqual('@value', stats_xsl[1].find('%sif' % nst).attrib['test'])
- self.assertEqual('@value', stats_xsl[1].find('%sif/%svalue-of' % ((nst,)*2)).attrib['select'])
- self.assertEqual('@description', stats_xsl[2].find('%sif' % nst).attrib['test'])
- self.assertEqual('@description', stats_xsl[2].find('%sif/%svalue-of' % ((nst,)*2)).attrib['select'])
-
- def test_for_without_B10_FROM_SOURCE(self):
- # just lets it go through the code without B10_FROM_SOURCE env
- # variable
- if "B10_FROM_SOURCE" in os.environ:
- tmppath = os.environ["B10_FROM_SOURCE"]
- os.environ.pop("B10_FROM_SOURCE")
- imp.reload(stats_httpd)
- os.environ["B10_FROM_SOURCE"] = tmppath
- imp.reload(stats_httpd)
-
-if __name__ == "__main__":
- isc.log.resetUnitTestRootLogger()
- unittest.main()
diff --git a/src/bin/stats/tests/b10-stats_test.py b/src/bin/stats/tests/b10-stats_test.py
deleted file mode 100644
index f807168..0000000
--- a/src/bin/stats/tests/b10-stats_test.py
+++ /dev/null
@@ -1,1379 +0,0 @@
-# Copyright (C) 2010, 2011, 2012 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.
-
-"""
-In each of these tests we start several virtual components. They are
-not the real components, no external processes are started. They are
-just simple mock objects running each in its own thread and pretending
-to be bind10 modules. This helps testing the stats module in a close
-to real environment.
-"""
-
-import unittest
-import os
-import threading
-import io
-import time
-import imp
-import sys
-
-import stats
-import isc.log
-import isc.cc.session
-from test_utils import BaseModules, ThreadingServerManager, MyStats, \
- SimpleStats, SignalHandler, MyModuleCCSession, send_command
-from isc.testutils.ccsession_mock import MockModuleCCSession
-
-class TestUtilties(unittest.TestCase):
- items = [
- { 'item_name': 'test_int1', 'item_type': 'integer', 'item_default': 12345 },
- { 'item_name': 'test_real1', 'item_type': 'real', 'item_default': 12345.6789 },
- { 'item_name': 'test_bool1', 'item_type': 'boolean', 'item_default': True },
- { 'item_name': 'test_str1', 'item_type': 'string', 'item_default': 'ABCD' },
- { 'item_name': 'test_list1', 'item_type': 'list', 'item_default': [1,2,3],
- 'list_item_spec' : { 'item_name': 'number', 'item_type': 'integer' } },
- { 'item_name': 'test_map1', 'item_type': 'map', 'item_default': {'a':1,'b':2,'c':3},
- 'map_item_spec' : [ { 'item_name': 'a', 'item_type': 'integer'},
- { 'item_name': 'b', 'item_type': 'integer'},
- { 'item_name': 'c', 'item_type': 'integer'} ] },
- { 'item_name': 'test_int2', 'item_type': 'integer' },
- { 'item_name': 'test_real2', 'item_type': 'real' },
- { 'item_name': 'test_bool2', 'item_type': 'boolean' },
- { 'item_name': 'test_str2', 'item_type': 'string' },
- { 'item_name': 'test_list2', 'item_type': 'list',
- 'list_item_spec' : { 'item_name': 'number', 'item_type': 'integer' } },
- { 'item_name': 'test_map2', 'item_type': 'map',
- 'map_item_spec' : [ { 'item_name': 'A', 'item_type': 'integer'},
- { 'item_name': 'B', 'item_type': 'integer'},
- { 'item_name': 'C', 'item_type': 'integer'} ] },
- { 'item_name': 'test_none', 'item_type': 'none' },
- { 'item_name': 'test_list3', 'item_type': 'list', 'item_default': ["one","two","three"],
- 'list_item_spec' : { 'item_name': 'number', 'item_type': 'string' } },
- { 'item_name': 'test_map3', 'item_type': 'map', 'item_default': {'a':'one','b':'two','c':'three'},
- 'map_item_spec' : [ { 'item_name': 'a', 'item_type': 'string'},
- { 'item_name': 'b', 'item_type': 'string'},
- { 'item_name': 'c', 'item_type': 'string'} ] },
- {
- 'item_name': 'test_named_set',
- 'item_type': 'named_set',
- 'item_default': { },
- 'named_set_item_spec': {
- 'item_name': 'name',
- 'item_type': 'map',
- 'item_default': { },
- 'map_item_spec': [
- {
- 'item_name': 'number1',
- 'item_type': 'integer'
- },
- {
- 'item_name': 'number2',
- 'item_type': 'integer'
- }
- ]
- }
- }
- ]
-
- def setUp(self):
- self.const_timestamp = 1308730448.965706
- self.const_timetuple = (2011, 6, 22, 8, 14, 8, 2, 173, 0)
- self.const_datetime = '2011-06-22T08:14:08Z'
- stats.time = lambda : self.const_timestamp
- stats.gmtime = lambda : self.const_timetuple
-
- def test_get_spec_defaults(self):
- self.assertEqual(
- stats.get_spec_defaults(self.items), {
- 'test_int1' : 12345 ,
- 'test_real1' : 12345.6789 ,
- 'test_bool1' : True ,
- 'test_str1' : 'ABCD' ,
- 'test_list1' : [1,2,3] ,
- 'test_map1' : {'a':1,'b':2,'c':3},
- 'test_int2' : 0 ,
- 'test_real2' : 0.0,
- 'test_bool2' : False,
- 'test_str2' : "",
- 'test_list2' : [0],
- 'test_map2' : { 'A' : 0, 'B' : 0, 'C' : 0 },
- 'test_none' : None,
- 'test_list3' : [ "one", "two", "three" ],
- 'test_map3' : { 'a' : 'one', 'b' : 'two', 'c' : 'three' },
- 'test_named_set' : {} })
- self.assertEqual(stats.get_spec_defaults(None), {})
- self.assertRaises(KeyError, stats.get_spec_defaults, [{'item_name':'Foo'}])
-
- def test_get_timestamp(self):
- self.assertEqual(stats.get_timestamp(), self.const_timestamp)
-
- def test_get_datetime(self):
- self.assertEqual(stats.get_datetime(), self.const_datetime)
- self.assertNotEqual(stats.get_datetime(
- (2011, 6, 22, 8, 23, 40, 2, 173, 0)), self.const_datetime)
-
- def test__accum(self):
- self.assertEqual(stats._accum(None, None), None)
- self.assertEqual(stats._accum(None, "b"), "b")
- self.assertEqual(stats._accum("a", None), "a")
- self.assertEqual(stats._accum(1, 2), 3)
- self.assertEqual(stats._accum(0.5, 0.3), 0.8)
- self.assertEqual(stats._accum('aa','bb'), 'bb')
- self.assertEqual(stats._accum('1970-01-01T09:00:00Z','2012-08-09T09:33:31Z'),
- '2012-08-09T09:33:31Z')
- self.assertEqual(stats._accum(
- [1, 2, 3], [4, 5]), [5, 7, 3])
- self.assertEqual(stats._accum(
- [4, 5], [1, 2, 3]), [5, 7, 3])
- self.assertEqual(stats._accum(
- [1, 2, 3], [None, 5, 6]), [1, 7, 9])
- self.assertEqual(stats._accum(
- [None, 5, 6], [1, 2, 3]), [1, 7, 9])
- self.assertEqual(stats._accum(
- [1, 2, 3], [None, None, None, None]), [1,2,3,None])
- self.assertEqual(stats._accum(
- [[1,2],3],[[],5,6]), [[1,2],8,6])
- self.assertEqual(stats._accum(
- {'one': 1, 'two': 2, 'three': 3},
- {'one': 4, 'two': 5}),
- {'one': 5, 'two': 7, 'three': 3})
- self.assertEqual(stats._accum(
- {'one': 1, 'two': 2, 'three': 3},
- {'four': 4, 'five': 5}),
- {'one': 1, 'two': 2, 'three': 3, 'four': 4, 'five': 5})
- self.assertEqual(stats._accum(
- {'one': [1, 2], 'two': [3, None, 5], 'three': [None, 3, None]},
- {'one': [2], 'two': [4, 5], 'three': [None, None, None], 'four': 'FOUR'}),
- {'one':[3,2], 'two':[7,5,5], 'three':[None,3,None], 'four': 'FOUR'})
- self.assertEqual(stats._accum(
- [ {'one': 1, 'two': 2, 'three': 3}, {'four': 4, 'five': 5, 'six': 6} ],
- [ {}, {'four': 1, 'five': 2, 'six': 3} ]),
- [ {'one': 1, 'two': 2, 'three': 3}, {'four': 5, 'five': 7, 'six': 9} ])
-
- def test_merge_oldnre(self):
- self.assertEqual(stats.merge_oldnew(1, 2), 2)
- self.assertEqual(stats.merge_oldnew(0.5, 0.3), 0.3)
- self.assertEqual(stats.merge_oldnew('aa','bb'), 'bb')
- self.assertEqual(stats.merge_oldnew(
- [1, 2, 3], [4, 5]), [4, 5, 3])
- self.assertEqual(stats.merge_oldnew(
- [4, 5], [1, 2, 3]), [1, 2, 3])
- self.assertEqual(stats.merge_oldnew(
- [1, 2, 3], [None, 5, 6]), [None, 5, 6])
- self.assertEqual(stats.merge_oldnew(
- [None, 5, 6], [1, 2, 3]), [1, 2, 3])
- self.assertEqual(stats.merge_oldnew(
- [1, 2, 3], [None, None, None, None]), [None, None, None, None])
- self.assertEqual(stats.merge_oldnew(
- [[1,2],3],[[],5,6]), [[1,2],5,6])
- self.assertEqual(stats.merge_oldnew(
- {'one': 1, 'two': 2, 'three': 3},
- {'one': 4, 'two': 5}),
- {'one': 4, 'two': 5, 'three': 3})
- self.assertEqual(stats.merge_oldnew(
- {'one': 1, 'two': 2, 'three': 3},
- {'four': 4, 'five': 5}),
- {'one': 1, 'two': 2, 'three': 3, 'four': 4, 'five': 5})
- self.assertEqual(stats.merge_oldnew(
- {'one': [1, 2], 'two': [3, None, 5], 'three': [None, 3, None]},
- {'one': [2], 'two': [4, 5], 'three': [None, None, None], 'four': 'FOUR'}),
- {'one':[2,2], 'two':[4,5,5], 'three':[None,None,None], 'four': 'FOUR'})
- self.assertEqual(stats.merge_oldnew(
- [ {'one': 1, 'two': 2, 'three': 3}, {'four': 4, 'five': 5, 'six': 6} ],
- [ {}, {'four': 1, 'five': 2, 'six': 3} ]),
- [ {'one': 1, 'two': 2, 'three': 3}, {'four': 1, 'five': 2, 'six': 3} ])
-
-class TestCallback(unittest.TestCase):
- def setUp(self):
- self.dummy_func = lambda *x, **y : (x, y)
- self.dummy_args = (1,2,3)
- self.dummy_kwargs = {'a':1,'b':2,'c':3}
- self.cback1 = stats.Callback(
- command=self.dummy_func,
- args=self.dummy_args,
- kwargs=self.dummy_kwargs
- )
- self.cback2 = stats.Callback(
- args=self.dummy_args,
- kwargs=self.dummy_kwargs
- )
- self.cback3 = stats.Callback(
- command=self.dummy_func,
- kwargs=self.dummy_kwargs
- )
- self.cback4 = stats.Callback(
- command=self.dummy_func,
- args=self.dummy_args
- )
-
- def test_init(self):
- self.assertEqual((self.cback1.command, self.cback1.args, self.cback1.kwargs),
- (self.dummy_func, self.dummy_args, self.dummy_kwargs))
- self.assertEqual((self.cback2.command, self.cback2.args, self.cback2.kwargs),
- (None, self.dummy_args, self.dummy_kwargs))
- self.assertEqual((self.cback3.command, self.cback3.args, self.cback3.kwargs),
- (self.dummy_func, (), self.dummy_kwargs))
- self.assertEqual((self.cback4.command, self.cback4.args, self.cback4.kwargs),
- (self.dummy_func, self.dummy_args, {}))
-
- def test_call(self):
- self.assertEqual(self.cback1(), (self.dummy_args, self.dummy_kwargs))
- self.assertEqual(self.cback1(100, 200), ((100, 200), self.dummy_kwargs))
- self.assertEqual(self.cback1(a=100, b=200), (self.dummy_args, {'a':100, 'b':200}))
- self.assertEqual(self.cback2(), None)
- self.assertEqual(self.cback3(), ((), self.dummy_kwargs))
- self.assertEqual(self.cback3(100, 200), ((100, 200), self.dummy_kwargs))
- self.assertEqual(self.cback3(a=100, b=200), ((), {'a':100, 'b':200}))
- self.assertEqual(self.cback4(), (self.dummy_args, {}))
- self.assertEqual(self.cback4(100, 200), ((100, 200), {}))
- self.assertEqual(self.cback4(a=100, b=200), (self.dummy_args, {'a':100, 'b':200}))
-
-class TestStats(unittest.TestCase):
- def setUp(self):
- # set the signal handler for deadlock
- self.sig_handler = SignalHandler(self.fail)
- self.base = BaseModules()
- self.const_timestamp = 1308730448.965706
- self.const_datetime = '2011-06-22T08:14:08Z'
- self.const_default_datetime = '1970-01-01T00:00:00Z'
- # Record original module-defined functions in case we replace them
- self.__orig_timestamp = stats.get_timestamp
- self.__orig_get_datetime = stats.get_datetime
-
- def tearDown(self):
- self.base.shutdown()
- # reset the signal handler
- self.sig_handler.reset()
- # restore the stored original function in case we replaced them
- stats.get_timestamp = self.__orig_timestamp
- stats.get_datetime = self.__orig_get_datetime
-
- def test_init(self):
- self.stats = stats.Stats()
- self.assertEqual(self.stats.module_name, 'Stats')
- self.assertFalse(self.stats.running)
- self.assertTrue('command_show' in self.stats.callbacks)
- self.assertTrue('command_status' in self.stats.callbacks)
- self.assertTrue('command_shutdown' in self.stats.callbacks)
- self.assertTrue('command_show' in self.stats.callbacks)
- self.assertTrue('command_showschema' in self.stats.callbacks)
- self.assertEqual(self.stats.config['poll-interval'], 60)
-
- def test_init_undefcmd(self):
- spec_str = """\
-{
- "module_spec": {
- "module_name": "Stats",
- "module_description": "Stats daemon",
- "config_data": [],
- "commands": [
- {
- "command_name": "_undef_command_",
- "command_description": "a undefined command in stats",
- "command_args": []
- }
- ],
- "statistics": []
- }
-}
-"""
- orig_spec_location = stats.SPECFILE_LOCATION
- stats.SPECFILE_LOCATION = io.StringIO(spec_str)
- self.assertRaises(stats.StatsError, stats.Stats)
- stats.SPECFILE_LOCATION = orig_spec_location
-
- def __send_command(self, stats, command_name, params=None):
- '''Emulate a command arriving to stats by directly calling callback'''
- return isc.config.ccsession.parse_answer(
- stats.command_handler(command_name, params))
-
- def test_start(self):
- # Define a separate exception class so we can be sure that's actually
- # the one raised in __check_start() below
- class CheckException(Exception):
- pass
-
- def __check_start(tested_stats):
- self.assertTrue(tested_stats.running)
- raise CheckException # terminate the loop
-
- # start without err
- stats = SimpleStats()
- self.assertFalse(stats.running)
- stats._check_command = lambda: __check_start(stats)
- # We are going to confirm start() will set running to True, avoiding
- # to fall into a loop with the exception trick.
- self.assertRaises(CheckException, stats.start)
- self.assertEqual(self.__send_command(stats, "status"),
- (0, "Stats is up. (PID " + str(os.getpid()) + ")"))
-
- def test_shutdown(self):
- def __check_shutdown(tested_stats):
- self.assertTrue(tested_stats.running)
- self.assertEqual(self.__send_command(tested_stats, "shutdown"),
- (0, None))
- self.assertFalse(tested_stats.running)
- # override get_interval() so it won't go poll statistics
- tested_stats.get_interval = lambda : 0
-
- stats = SimpleStats()
- stats._check_command = lambda: __check_shutdown(stats)
- stats.start()
- self.assertTrue(stats.mccs.stopped)
-
- def test_handlers(self):
- """Test command_handler"""
-
- __stats = SimpleStats()
-
- # 'show' command. We're going to check the expected methods are
- # called in the expected order, and check the resulting response.
- # Details of each method are tested separately.
- call_log = []
- def __steal_method(fn_name, *arg):
- call_log.append((fn_name, arg))
- if fn_name == 'update_stat':
- return False # "no error"
- if fn_name == 'showschema':
- return isc.config.create_answer(0, 'no error')
-
- # Fake some methods and attributes for inspection
- __stats.do_polling = lambda: __steal_method('polling')
- __stats.update_statistics_data = \
- lambda x, y, z: __steal_method('update_stat', x, y, z)
- __stats.update_modules = lambda: __steal_method('update_module')
- __stats.mccs.lname = 'test lname'
- __stats.statistics_data = {'Init': {'boot_time': self.const_datetime}}
-
- # skip initial polling
- stats.get_timestamp = lambda: 0
- __stats._lasttime_poll = 0
-
- stats.get_datetime = lambda: 42 # make the result predictable
-
- # now send the command
- self.assertEqual(
- self.__send_command(
- __stats, 'show',
- params={ 'owner' : 'Init', 'name' : 'boot_time' }),
- (0, {'Init': {'boot_time': self.const_datetime}}))
- # Check if expected methods are called
- self.assertEqual([('update_stat',
- ('Stats', 'test lname',
- {'timestamp': 0,
- 'report_time': 42})),
- ('update_module', ())], call_log)
-
- # Then update faked timestamp so the intial polling will happen, and
- # confirm that.
- call_log = []
- stats.get_timestamp = lambda: 10
- self.assertEqual(
- self.__send_command(
- __stats, 'show',
- params={ 'owner' : 'Init', 'name' : 'boot_time' }),
- (0, {'Init': {'boot_time': self.const_datetime}}))
- self.assertEqual([('polling', ()),
- ('update_stat',
- ('Stats', 'test lname',
- {'timestamp': 10,
- 'report_time': 42})),
- ('update_module', ())], call_log)
-
- # 'status' command. We can confirm the behavior without any fake
- self.assertEqual(
- self.__send_command(__stats, 'status'),
- (0, "Stats is up. (PID " + str(os.getpid()) + ")"))
-
- # 'showschema' command. update_modules() will be called, which
- # (implicitly) confirms the correct method is called; further details
- # are tested separately.
- call_log = []
- (rcode, value) = self.__send_command(__stats, 'showschema')
- self.assertEqual([('update_module', ())], call_log)
-
- # Unknown command. Error should be returned
- self.assertEqual(
- self.__send_command(__stats, '__UNKNOWN__'),
- (1, "Unknown command: '__UNKNOWN__'"))
-
- def test_update_modules(self):
- """Confirm the behavior of Stats.update_modules().
-
- It checks whether the expected command is sent to ConfigManager,
- and whether the answer from ConfigManager is handled as expected.
-
- """
-
- def __check_rpc_call(command, group):
- self.assertEqual('ConfigManager', group)
- self.assertEqual(command,
- isc.config.ccsession.COMMAND_GET_STATISTICS_SPEC)
- answer_value = {'Init': [{
- "item_name": "boot_time",
- "item_type": "string",
- "item_optional": False,
- # Use a different default so we can check it below
- "item_default": "2013-01-01T00:00:01Z",
- "item_title": "Boot time",
- "item_description": "dummy desc",
- "item_format": "date-time"
- }]}
- return answer_value
-
- self.stats = SimpleStats()
- self.stats.cc_session.rpc_call = __check_rpc_call
-
- self.stats.update_modules()
-
- # Stats is always incorporated. For others, only the ones returned
- # by group_recvmsg() above is available.
- self.assertTrue('Stats' in self.stats.modules)
- self.assertTrue('Init' in self.stats.modules)
- self.assertFalse('Dummy' in self.stats.modules)
-
- my_statistics_data = stats.get_spec_defaults(
- self.stats.modules['Stats'].get_statistics_spec())
- self.assertTrue('report_time' in my_statistics_data)
- self.assertTrue('boot_time' in my_statistics_data)
- self.assertTrue('last_update_time' in my_statistics_data)
- self.assertTrue('timestamp' in my_statistics_data)
- self.assertTrue('lname' in my_statistics_data)
- self.assertEqual(my_statistics_data['report_time'],
- self.const_default_datetime)
- self.assertEqual(my_statistics_data['boot_time'],
- self.const_default_datetime)
- self.assertEqual(my_statistics_data['last_update_time'],
- self.const_default_datetime)
- self.assertEqual(my_statistics_data['timestamp'], 0.0)
- self.assertEqual(my_statistics_data['lname'], "")
- my_statistics_data = stats.get_spec_defaults(
- self.stats.modules['Init'].get_statistics_spec())
- self.assertTrue('boot_time' in my_statistics_data)
- self.assertEqual(my_statistics_data['boot_time'],
- "2013-01-01T00:00:01Z")
-
- # Error case
- def __raise_on_rpc_call(x, y):
- raise isc.config.RPCError(99, 'error')
- orig_parse_answer = stats.isc.config.ccsession.parse_answer
- self.stats.cc_session.rpc_call = __raise_on_rpc_call
- self.assertRaises(stats.StatsError, self.stats.update_modules)
-
- def test_get_statistics_data(self):
- """Confirm the behavior of Stats.get_statistics_data().
-
- It should first call update_modules(), and then retrieve the requested
- data from statistics_data. We confirm this by fake update_modules()
- where we set the expected data in statistics_data.
-
- """
- self.stats = SimpleStats()
- def __faked_update_modules():
- self.stats.statistics_data = { \
- 'Stats': {
- 'report_time': self.const_default_datetime,
- 'boot_time': None,
- 'last_update_time': None,
- 'timestamp': 0.0,
- 'lname': 'dummy name'
- },
- 'Init': { 'boot_time': None }
- }
-
- self.stats.update_modules = __faked_update_modules
-
- my_statistics_data = self.stats.get_statistics_data()
- self.assertTrue('Stats' in my_statistics_data)
- self.assertTrue('Init' in my_statistics_data)
- self.assertTrue('boot_time' in my_statistics_data['Init'])
-
- my_statistics_data = self.stats.get_statistics_data(owner='Stats')
- self.assertTrue('Stats' in my_statistics_data)
- self.assertTrue('report_time' in my_statistics_data['Stats'])
- self.assertTrue('boot_time' in my_statistics_data['Stats'])
- self.assertTrue('last_update_time' in my_statistics_data['Stats'])
- self.assertTrue('timestamp' in my_statistics_data['Stats'])
- self.assertTrue('lname' in my_statistics_data['Stats'])
- self.assertRaises(stats.StatsError, self.stats.get_statistics_data,
- owner='Foo')
-
- my_statistics_data = self.stats.get_statistics_data(
- owner='Stats', name='report_time')
- self.assertEqual(my_statistics_data['Stats']['report_time'],
- self.const_default_datetime)
-
- my_statistics_data = self.stats.get_statistics_data(
- owner='Stats', name='boot_time')
- self.assertTrue('boot_time' in my_statistics_data['Stats'])
-
- my_statistics_data = self.stats.get_statistics_data(
- owner='Stats', name='last_update_time')
- self.assertTrue('last_update_time' in my_statistics_data['Stats'])
-
- my_statistics_data = self.stats.get_statistics_data(
- owner='Stats', name='timestamp')
- self.assertEqual(my_statistics_data['Stats']['timestamp'], 0.0)
-
- my_statistics_data = self.stats.get_statistics_data(
- owner='Stats', name='lname')
- self.assertTrue(len(my_statistics_data['Stats']['lname']) >0)
- self.assertRaises(stats.StatsError, self.stats.get_statistics_data,
- owner='Stats', name='Bar')
- self.assertRaises(stats.StatsError, self.stats.get_statistics_data,
- owner='Foo', name='Bar')
- self.assertRaises(stats.StatsError, self.stats.get_statistics_data,
- name='Bar')
-
- def test_update_statistics_data(self):
- """test for list-type statistics"""
- self.stats = SimpleStats()
- _test_exp1 = {
- 'zonename': 'test1.example',
- 'queries.tcp': 5,
- 'queries.udp': 4
- }
- _test_exp2 = {
- 'zonename': 'test2.example',
- 'queries.tcp': 3,
- 'queries.udp': 2
- }
- _test_exp3 = {}
- _test_exp4 = {
- 'queries.udp': 4
- }
- _test_exp5_1 = {
- 'queries.perzone': [
- { },
- {
- 'queries.udp': 9876
- }
- ]
- }
- _test_exp5_2 = {
- 'queries.perzone[1]/queries.udp':
- isc.cc.data.find(_test_exp5_1,
- 'queries.perzone[1]/queries.udp')
- }
- # Success cases
- self.assertEqual(self.stats.statistics_data['Stats']['lname'],
- self.stats.cc_session.lname)
- self.stats.update_statistics_data(
- 'Stats', self.stats.cc_session.lname,
- {'lname': 'foo at bar'})
- self.assertEqual(self.stats.statistics_data['Stats']['lname'],
- 'foo at bar')
- self.assertIsNone(self.stats.update_statistics_data(
- 'Auth', 'foo1', {'queries.perzone': [_test_exp1]}))
- self.assertEqual(self.stats.statistics_data_bymid['Auth']\
- ['foo1']['queries.perzone'],\
- [_test_exp1])
- self.assertIsNone(self.stats.update_statistics_data(
- 'Auth', 'foo1', {'queries.perzone': [_test_exp2]}))
- self.assertEqual(self.stats.statistics_data_bymid['Auth']\
- ['foo1']['queries.perzone'],\
- [_test_exp2])
- self.assertIsNone(self.stats.update_statistics_data(
- 'Auth', 'foo1', {'queries.perzone': [_test_exp1,_test_exp2]}))
- self.assertEqual(self.stats.statistics_data_bymid['Auth']\
- ['foo1']['queries.perzone'],
- [_test_exp1,_test_exp2])
- # differential update
- self.assertIsNone(self.stats.update_statistics_data(
- 'Auth', 'foo1', {'queries.perzone': [_test_exp3,_test_exp4]}))
- _new_data = stats.merge_oldnew(_test_exp2,_test_exp4)
- self.assertEqual(self.stats.statistics_data_bymid['Auth']\
- ['foo1']['queries.perzone'], \
- [_test_exp1,_new_data])
- self.assertIsNone(self.stats.update_statistics_data(
- 'Auth', 'foo1', _test_exp5_2))
- _new_data = stats.merge_oldnew(_new_data,
- _test_exp5_1['queries.perzone'][1])
- self.assertEqual(self.stats.statistics_data_bymid['Auth']\
- ['foo1']['queries.perzone'], \
- [_test_exp1,_new_data])
- # Error cases
- self.assertEqual(self.stats.update_statistics_data('Stats', None,
- {'lname': 0.0}),
- ['0.0 should be a string'])
- self.assertEqual(self.stats.update_statistics_data('Dummy', None,
- {'foo': 'bar'}),
- ['unknown module name: Dummy'])
- self.assertEqual(self.stats.update_statistics_data(
- 'Auth', 'foo1', {'queries.perzone': [None]}), ['None should be a map'])
-
- def test_update_statistics_data_pt2(self):
- """test for named_set-type statistics"""
- self.stats = SimpleStats()
- _test_exp1 = \
- { 'test10.example': { 'queries.tcp': 5, 'queries.udp': 4 } }
- _test_exp2 = \
- { 'test20.example': { 'queries.tcp': 3, 'queries.udp': 2 } }
- _test_exp3 = {}
- _test_exp4 = { 'test20.example': { 'queries.udp': 4 } }
- _test_exp5_1 = { 'test10.example': { 'queries.udp': 5432 } }
- _test_exp5_2 ={
- 'nds_queries.perzone/test10.example/queries.udp':
- isc.cc.data.find(_test_exp5_1, 'test10.example/queries.udp')
- }
- _test_exp6 = { 'foo/bar': 'brabra' }
- _test_exp7 = { 'foo[100]': 'bar' }
- # Success cases
- self.assertIsNone(self.stats.update_statistics_data(
- 'Auth', 'foo1', {'nds_queries.perzone': _test_exp1}))
- self.assertEqual(self.stats.statistics_data_bymid['Auth']\
- ['foo1']['nds_queries.perzone'],\
- _test_exp1)
- self.assertIsNone(self.stats.update_statistics_data(
- 'Auth', 'foo1', {'nds_queries.perzone': _test_exp2}))
- self.assertEqual(self.stats.statistics_data_bymid['Auth']\
- ['foo1']['nds_queries.perzone'],\
- dict(_test_exp1,**_test_exp2))
- self.assertIsNone(self.stats.update_statistics_data(
- 'Auth', 'foo1', {'nds_queries.perzone':
- dict(_test_exp1, **_test_exp2)}))
- self.assertEqual(self.stats.statistics_data_bymid['Auth']\
- ['foo1']['nds_queries.perzone'],
- dict(_test_exp1, **_test_exp2))
- # differential update
- self.assertIsNone(self.stats.update_statistics_data(
- 'Auth', 'foo1', {'nds_queries.perzone':
- dict(_test_exp3, **_test_exp4)}))
- _new_val = dict(_test_exp1,
- **stats.merge_oldnew(_test_exp2,_test_exp4))
- self.assertEqual(self.stats.statistics_data_bymid['Auth']\
- ['foo1']['nds_queries.perzone'],\
- _new_val)
- self.assertIsNone(self.stats.update_statistics_data(
- 'Auth', 'foo1', _test_exp5_2))
- _new_val = stats.merge_oldnew(_new_val, _test_exp5_1)
- self.assertEqual(self.stats.statistics_data_bymid['Auth']\
- ['foo1']['nds_queries.perzone'],\
- _new_val)
- self.assertIsNone(self.stats.update_statistics_data(
- 'Auth', 'foo2', _test_exp5_2))
- _new_val = stats.merge_oldnew(_new_val, _test_exp5_1)
- self.assertEqual(self.stats.statistics_data_bymid['Auth']\
- ['foo2']['nds_queries.perzone'],\
- _test_exp5_1)
- # Error cases
- self.assertEqual(self.stats.update_statistics_data(
- 'Auth', 'foo1', {'nds_queries.perzone': None}),
- ['None should be a map'])
- self.assertEqual(self.stats.statistics_data_bymid['Auth']\
- ['foo1']['nds_queries.perzone'],\
- _new_val)
- self.assertEqual(self.stats.update_statistics_data(
- 'Auth', 'foo1', _test_exp6), ['unknown item foo'])
- self.assertEqual(self.stats.statistics_data_bymid['Auth']\
- ['foo1']['nds_queries.perzone'],\
- _new_val)
- self.assertEqual(self.stats.update_statistics_data(
- 'Init', 'bar1', _test_exp7), ["KeyError: 'foo'"])
- self.assertEqual(self.stats.update_statistics_data(
- 'Foo', 'foo1', _test_exp6), ['unknown module name: Foo'])
-
- def test_update_statistics_data_withmid(self):
- self.stats = SimpleStats()
-
- # This test relies on existing statistics data at the Stats object.
- # This version of test prepares the data using the do_polling() method;
- # that's a bad practice because a unittest for a method
- # (update_statistics_data) would heavily depend on details of another
- # method (do_polling). However, there's currently no direct test
- # for do_polling (which is also bad), so we still keep that approach,
- # partly for testing do_polling indirectly. #2781 should provide
- # direct test for do_polling, with which this test scenario should
- # also be changed to be more stand-alone.
-
- # We use the knowledge of what kind of messages are sent via
- # do_polling, and return the following faked answer directly.
- create_answer = isc.config.ccsession.create_answer # shortcut
- self.stats._answers = [\
- # Answer for "show_processes"
- (create_answer(0, [[1034, 'b10-auth-1', 'Auth'],
- [1035, 'b10-auth-2', 'Auth']]), None),
- # Answers for "getstats". 2 for Auth instances and 1 for Init.
- # we return some bogus values for Init, but the rest of the test
- # doesn't need it, so it's okay.
- (create_answer(0, self.stats._auth_sdata), {'from': 'auth1'}),
- (create_answer(0, self.stats._auth_sdata), {'from': 'auth2'}),
- (create_answer(0, self.stats._auth_sdata), {'from': 'auth3'})
- ]
- # do_polling calls update_modules internally; in our scenario there's
- # no change in modules, so we make it no-op.
- self.stats.update_modules = lambda: None
- # Now call do_polling.
- self.stats.do_polling()
-
- # samples of query number
- bar1_tcp = 1001
- bar2_tcp = 2001
- bar3_tcp = 1002
- bar3_udp = 1003
- # two auth instances invoked, so we double the pre-set stat values
- sum_qtcp = self.stats._queries_tcp * 2
- sum_qudp = self.stats._queries_udp * 2
- self.stats.update_statistics_data('Auth', "bar1 at foo",
- {'queries.tcp': bar1_tcp})
- self.assertTrue('Auth' in self.stats.statistics_data)
- self.assertTrue('queries.tcp' in self.stats.statistics_data['Auth'])
- self.assertEqual(self.stats.statistics_data['Auth']['queries.tcp'],
- bar1_tcp + sum_qtcp)
- self.assertTrue('Auth' in self.stats.statistics_data_bymid)
- self.assertTrue('bar1 at foo' in self.stats.statistics_data_bymid['Auth'])
- self.assertTrue('queries.tcp' in self.stats.statistics_data_bymid
- ['Auth']['bar1 at foo'])
- self.assertEqual(self.stats.statistics_data_bymid['Auth']['bar1 at foo'],
- {'queries.tcp': bar1_tcp})
- # check consolidation of statistics data even if there is
- # non-existent mid of Auth
- self.stats.update_statistics_data('Auth', "bar2 at foo",
- {'queries.tcp': bar2_tcp})
- self.assertTrue('Auth' in self.stats.statistics_data)
- self.assertTrue('queries.tcp' in self.stats.statistics_data['Auth'])
- self.assertEqual(self.stats.statistics_data['Auth']['queries.tcp'],
- bar1_tcp + bar2_tcp + sum_qtcp)
- self.assertTrue('Auth' in self.stats.statistics_data_bymid)
- self.assertTrue('bar1 at foo' in self.stats.statistics_data_bymid['Auth'])
- self.assertTrue('queries.tcp' in self.stats.statistics_data_bymid['Auth']['bar1 at foo'])
- self.assertEqual(self.stats.statistics_data_bymid['Auth']['bar1 at foo'],
- {'queries.tcp': bar1_tcp})
- self.assertEqual(self.stats.statistics_data_bymid['Auth']['bar2 at foo'],
- {'queries.tcp': bar2_tcp})
- # kill running Auth but the statistics data doesn't change
- self.base.auth2.server.shutdown()
- self.stats.update_statistics_data()
- self.assertTrue('Auth' in self.stats.statistics_data)
- self.assertTrue('queries.tcp' in self.stats.statistics_data['Auth'])
- self.assertTrue('queries.udp' in self.stats.statistics_data['Auth'])
- self.assertEqual(self.stats.statistics_data['Auth']['queries.tcp'],
- bar1_tcp + bar2_tcp + sum_qtcp)
- self.assertEqual(self.stats.statistics_data['Auth']['queries.udp'],
- sum_qudp)
- self.assertTrue('Auth' in self.stats.statistics_data_bymid)
- # restore statistics data of killed auth
- # self.base.b10_init.server.pid_list = [ killed ] + self.base.b10_init.server.pid_list[:]
- self.stats.update_statistics_data('Auth',
- "bar1 at foo",
- {'queries.tcp': bar1_tcp})
- # set another mid of Auth
- self.stats.update_statistics_data('Auth',
- "bar3 at foo",
- {'queries.tcp':bar3_tcp,
- 'queries.udp':bar3_udp})
- self.assertTrue('Auth' in self.stats.statistics_data)
- self.assertTrue('queries.tcp' in self.stats.statistics_data['Auth'])
- self.assertTrue('queries.udp' in self.stats.statistics_data['Auth'])
- self.assertEqual(self.stats.statistics_data['Auth']['queries.tcp'],
- bar1_tcp + bar2_tcp + bar3_tcp + sum_qtcp)
- self.assertEqual(self.stats.statistics_data['Auth']['queries.udp'],
- bar3_udp + sum_qudp)
- self.assertTrue('Auth' in self.stats.statistics_data_bymid)
- self.assertTrue('bar1 at foo' in self.stats.statistics_data_bymid['Auth'])
- self.assertTrue('bar3 at foo' in self.stats.statistics_data_bymid['Auth'])
- self.assertTrue('queries.tcp' in self.stats.statistics_data_bymid['Auth']['bar1 at foo'])
- self.assertTrue('queries.udp' in self.stats.statistics_data_bymid['Auth']['bar3 at foo'])
- self.assertTrue('queries.udp' in self.stats.statistics_data_bymid['Auth']['bar3 at foo'])
- self.assertEqual(self.stats.statistics_data_bymid['Auth']['bar1 at foo']['queries.tcp'], bar1_tcp)
- self.assertEqual(self.stats.statistics_data_bymid['Auth']['bar3 at foo']['queries.tcp'], bar3_tcp)
- self.assertEqual(self.stats.statistics_data_bymid['Auth']['bar3 at foo']['queries.udp'], bar3_udp)
-
- def test_config(self):
- orig_get_timestamp = stats.get_timestamp
- stats.get_timestamp = lambda : self.const_timestamp
- stat = SimpleStats()
-
- # test updating poll-interval
- self.assertEqual(stat.config['poll-interval'], 60)
- self.assertEqual(stat.get_interval(), 60)
- self.assertEqual(stat.next_polltime, self.const_timestamp + 60)
- self.assertEqual(stat.config_handler({'poll-interval': 120}),
- isc.config.create_answer(0))
- self.assertEqual(stat.config['poll-interval'], 120)
- self.assertEqual(stat.get_interval(), 120)
- self.assertEqual(stat.next_polltime, self.const_timestamp + 120)
- stats.get_timestamp = orig_get_timestamp
- self.assertEqual(stat.config_handler({'poll-interval': "foo"}),
- isc.config.create_answer(1, 'foo should be an integer'))
- self.assertEqual(stat.config_handler({'poll-interval': -1}),
- isc.config.create_answer(1, 'Negative integer ignored'))
- # unknown item
- self.assertEqual(
- stat.config_handler({'_UNKNOWN_KEY_': None}),
- isc.config.ccsession.create_answer(
- 1, "unknown item _UNKNOWN_KEY_"))
- # test no change if zero interval time
- self.assertEqual(stat.config_handler({'poll-interval': 0}),
- isc.config.create_answer(0))
- self.assertEqual(stat.config['poll-interval'], 0)
-
- # see the comment for test_update_statistics_data_withmid. We abuse
- # do_polling here, too. With #2781 we should make it more direct.
- create_answer = isc.config.ccsession.create_answer # shortcut
- stat._answers = [\
- # Answer for "show_processes"
- (create_answer(0, []), None),
- # Answers for "getstats" for Init (the other one for Auth, but
- # that doesn't matter for this test)
- (create_answer(0, stat._init_sdata), {'from': 'init'}),
- (create_answer(0, stat._init_sdata), {'from': 'init'})
- ]
- stat.update_modules = lambda: None
-
- self.assertEqual(
- self.__send_command(
- stat, 'show',
- params={ 'owner' : 'Init', 'name' : 'boot_time' }),
- (0, {'Init': {'boot_time': self.const_datetime}}))
-
- def test_commands(self):
- self.stats = stats.Stats()
-
- # status
- self.assertEqual(self.stats.command_status(),
- isc.config.create_answer(
- 0, "Stats is up. (PID " + str(os.getpid()) + ")"))
-
- # shutdown
- self.stats.running = True
- self.assertEqual(self.stats.command_shutdown(),
- isc.config.create_answer(0))
- self.assertFalse(self.stats.running)
-
- @unittest.skipIf(sys.version_info >= (3, 3), "Unsupported in Python 3.3 or higher")
- def test_command_show(self):
- # two auth instances invoked
- list_auth = [ self.base.auth.server,
- self.base.auth2.server ]
- sum_qtcp = 0
- sum_qudp = 0
- sum_qtcp_perzone1 = 0
- sum_qudp_perzone1 = 0
- sum_qtcp_perzone2 = 4 * len(list_auth)
- sum_qudp_perzone2 = 3 * len(list_auth)
- sum_qtcp_nds_perzone10 = 0
- sum_qudp_nds_perzone10 = 0
- sum_qtcp_nds_perzone20 = 4 * len(list_auth)
- sum_qudp_nds_perzone20 = 3 * len(list_auth)
- self.stats = stats.Stats()
- self.assertEqual(self.stats.command_show(owner='Foo', name=None),
- isc.config.create_answer(
- 1, "specified arguments are incorrect: owner: Foo, name: None"))
- self.assertEqual(self.stats.command_show(owner='Foo', name='_bar_'),
- isc.config.create_answer(
- 1, "specified arguments are incorrect: owner: Foo, name: _bar_"))
- self.assertEqual(self.stats.command_show(owner='Foo', name='bar'),
- isc.config.create_answer(
- 1, "specified arguments are incorrect: owner: Foo, name: bar"))
-
- for a in list_auth:
- sum_qtcp += a.queries_tcp
- sum_qudp += a.queries_udp
- sum_qtcp_perzone1 += a.queries_per_zone[0]['queries.tcp']
- sum_qudp_perzone1 += a.queries_per_zone[0]['queries.udp']
- sum_qtcp_nds_perzone10 += a.nds_queries_per_zone['test10.example']['queries.tcp']
- sum_qudp_nds_perzone10 += a.nds_queries_per_zone['test10.example']['queries.udp']
-
- self.assertEqual(self.stats.command_show(owner='Auth'),
- isc.config.create_answer(
- 0, {'Auth':{ 'queries.udp': sum_qudp,
- 'queries.tcp': sum_qtcp,
- 'queries.perzone': [{ 'zonename': 'test1.example',
- 'queries.udp': sum_qudp_perzone1,
- 'queries.tcp': sum_qtcp_perzone1 },
- { 'zonename': 'test2.example',
- 'queries.udp': sum_qudp_perzone2,
- 'queries.tcp': sum_qtcp_perzone2 }
- ],
- 'nds_queries.perzone': { 'test10.example' : {
- 'queries.udp': sum_qudp_nds_perzone10,
- 'queries.tcp': sum_qtcp_nds_perzone10 },
- 'test20.example' : {
- 'queries.udp': sum_qudp_nds_perzone20,
- 'queries.tcp': sum_qtcp_nds_perzone20 }
- }}}))
- self.assertEqual(self.stats.command_show(owner='Auth', name='queries.udp'),
- isc.config.create_answer(
- 0, {'Auth': {'queries.udp': sum_qudp}}))
- self.assertEqual(self.stats.command_show(owner='Auth', name='queries.perzone'),
- isc.config.create_answer(
- 0, {'Auth': {'queries.perzone': [
- { 'zonename': 'test1.example',
- 'queries.udp': sum_qudp_perzone1,
- 'queries.tcp': sum_qtcp_perzone1 },
- { 'zonename': 'test2.example',
- 'queries.udp': sum_qudp_perzone2,
- 'queries.tcp': sum_qtcp_perzone2 }]}}))
- self.assertEqual(self.stats.command_show(owner='Auth', name='nds_queries.perzone'),
- isc.config.create_answer(
- 0, {'Auth': {'nds_queries.perzone': {
- 'test10.example': {
- 'queries.udp': sum_qudp_nds_perzone10,
- 'queries.tcp': sum_qtcp_nds_perzone10 },
- 'test20.example': {
- 'queries.udp': sum_qudp_nds_perzone20,
- 'queries.tcp': sum_qtcp_nds_perzone20 }}}}))
- orig_get_datetime = stats.get_datetime
- orig_get_timestamp = stats.get_timestamp
- stats.get_datetime = lambda x=None: self.const_datetime
- stats.get_timestamp = lambda : self.const_timestamp
- self.assertEqual(self.stats.command_show(owner='Stats', name='report_time'),
- isc.config.create_answer(
- 0, {'Stats': {'report_time':self.const_datetime}}))
- self.assertEqual(self.stats.command_show(owner='Stats', name='timestamp'),
- isc.config.create_answer(
- 0, {'Stats': {'timestamp':self.const_timestamp}}))
- stats.get_datetime = orig_get_datetime
- stats.get_timestamp = orig_get_timestamp
- self.stats.modules[self.stats.module_name] = isc.config.module_spec.ModuleSpec(
- { "module_name": self.stats.module_name,
- "statistics": [] } )
- self.assertRaises(
- stats.StatsError, self.stats.command_show, owner=self.stats.module_name, name='bar')
-
- def test_command_showchema(self):
- self.stats = stats.Stats()
- (rcode, value) = isc.config.ccsession.parse_answer(
- self.stats.command_showschema())
- self.assertEqual(rcode, 0)
- self.assertEqual(len(value), 3)
- self.assertTrue('Stats' in value)
- self.assertTrue('Init' in value)
- self.assertTrue('Auth' in value)
- self.assertFalse('__Dummy__' in value)
- schema = value['Stats']
- self.assertEqual(len(schema), 5)
- for item in schema:
- self.assertTrue(len(item) == 6 or len(item) == 7)
- self.assertTrue('item_name' in item)
- self.assertTrue('item_type' in item)
- self.assertTrue('item_optional' in item)
- self.assertTrue('item_default' in item)
- self.assertTrue('item_title' in item)
- self.assertTrue('item_description' in item)
- if len(item) == 7:
- self.assertTrue('item_format' in item)
-
- schema = value['Init']
- self.assertEqual(len(schema), 1)
- for item in schema:
- self.assertTrue(len(item) == 7)
- self.assertTrue('item_name' in item)
- self.assertTrue('item_type' in item)
- self.assertTrue('item_optional' in item)
- self.assertTrue('item_default' in item)
- self.assertTrue('item_title' in item)
- self.assertTrue('item_description' in item)
- self.assertTrue('item_format' in item)
-
- schema = value['Auth']
- self.assertEqual(len(schema), 4)
- for item in schema:
- if item['item_type'] == 'list' or item['item_type'] == 'named_set':
- self.assertEqual(len(item), 7)
- else:
- self.assertEqual(len(item), 6)
- self.assertTrue('item_name' in item)
- self.assertTrue('item_type' in item)
- self.assertTrue('item_optional' in item)
- self.assertTrue('item_default' in item)
- self.assertTrue('item_title' in item)
- self.assertTrue('item_description' in item)
-
- (rcode, value) = isc.config.ccsession.parse_answer(
- self.stats.command_showschema(owner='Stats'))
- self.assertEqual(rcode, 0)
- self.assertTrue('Stats' in value)
- self.assertFalse('Init' in value)
- self.assertFalse('Auth' in value)
- for item in value['Stats']:
- self.assertTrue(len(item) == 6 or len(item) == 7)
- self.assertTrue('item_name' in item)
- self.assertTrue('item_type' in item)
- self.assertTrue('item_optional' in item)
- self.assertTrue('item_default' in item)
- self.assertTrue('item_title' in item)
- self.assertTrue('item_description' in item)
- if len(item) == 7:
- self.assertTrue('item_format' in item)
-
- (rcode, value) = isc.config.ccsession.parse_answer(
- self.stats.command_showschema(owner='Stats', name='report_time'))
- self.assertEqual(rcode, 0)
- self.assertTrue('Stats' in value)
- self.assertFalse('Init' in value)
- self.assertFalse('Auth' in value)
- self.assertEqual(len(value['Stats'][0]), 7)
- self.assertTrue('item_name' in value['Stats'][0])
- self.assertTrue('item_type' in value['Stats'][0])
- self.assertTrue('item_optional' in value['Stats'][0])
- self.assertTrue('item_default' in value['Stats'][0])
- self.assertTrue('item_title' in value['Stats'][0])
- self.assertTrue('item_description' in value['Stats'][0])
- self.assertTrue('item_format' in value['Stats'][0])
- self.assertEqual(value['Stats'][0]['item_name'], 'report_time')
- self.assertEqual(value['Stats'][0]['item_format'], 'date-time')
-
- self.assertEqual(self.stats.command_showschema(owner='Foo'),
- isc.config.create_answer(
- 1, "specified arguments are incorrect: owner: Foo, name: None"))
- self.assertEqual(self.stats.command_showschema(owner='Foo', name='bar'),
- isc.config.create_answer(
- 1, "specified arguments are incorrect: owner: Foo, name: bar"))
- self.assertEqual(self.stats.command_showschema(owner='Auth'),
- isc.config.create_answer(
- 0, {'Auth': [{
- "item_default": 0,
- "item_description": "A number of total query counts which all auth servers receive over TCP since they started initially",
- "item_name": "queries.tcp",
- "item_optional": False,
- "item_title": "Queries TCP",
- "item_type": "integer"
- },
- {
- "item_default": 0,
- "item_description": "A number of total query counts which all auth servers receive over UDP since they started initially",
- "item_name": "queries.udp",
- "item_optional": False,
- "item_title": "Queries UDP",
- "item_type": "integer"
- },
- {
- "item_name": "queries.perzone",
- "item_type": "list",
- "item_optional": False,
- "item_default": [
- {
- "zonename" : "test1.example",
- "queries.udp" : 1,
- "queries.tcp" : 2
- },
- {
- "zonename" : "test2.example",
- "queries.udp" : 3,
- "queries.tcp" : 4
- }
- ],
- "item_title": "Queries per zone",
- "item_description": "Queries per zone",
- "list_item_spec": {
- "item_name": "zones",
- "item_type": "map",
- "item_optional": False,
- "item_default": {},
- "map_item_spec": [
- {
- "item_name": "zonename",
- "item_type": "string",
- "item_optional": False,
- "item_default": "",
- "item_title": "Zonename",
- "item_description": "Zonename"
- },
- {
- "item_name": "queries.udp",
- "item_type": "integer",
- "item_optional": False,
- "item_default": 0,
- "item_title": "Queries UDP per zone",
- "item_description": "A number of UDP query counts per zone"
- },
- {
- "item_name": "queries.tcp",
- "item_type": "integer",
- "item_optional": False,
- "item_default": 0,
- "item_title": "Queries TCP per zone",
- "item_description": "A number of TCP query counts per zone"
- }
- ]
- }
- },
- {
- "item_name": "nds_queries.perzone",
- "item_type": "named_set",
- "item_optional": False,
- "item_default": {
- "test10.example" : {
- "queries.udp" : 1,
- "queries.tcp" : 2
- },
- "test20.example" : {
- "queries.udp" : 3,
- "queries.tcp" : 4
- }
- },
- "item_title": "Queries per zone",
- "item_description": "Queries per zone",
- "named_set_item_spec": {
- "item_name": "zonename",
- "item_type": "map",
- "item_optional": False,
- "item_default": {},
- "item_title": "Zonename",
- "item_description": "Zonename",
- "map_item_spec": [
- {
- "item_name": "queries.udp",
- "item_type": "integer",
- "item_optional": False,
- "item_default": 0,
- "item_title": "Queries UDP per zone",
- "item_description": "A number of UDP query counts per zone"
- },
- {
- "item_name": "queries.tcp",
- "item_type": "integer",
- "item_optional": False,
- "item_default": 0,
- "item_title": "Queries TCP per zone",
- "item_description": "A number of TCP query counts per zone"
- }
- ]
- }
- }]}))
- self.assertEqual(self.stats.command_showschema(owner='Auth', name='queries.tcp'),
- isc.config.create_answer(
- 0, {'Auth': [{
- "item_default": 0,
- "item_description": "A number of total query counts which all auth servers receive over TCP since they started initially",
- "item_name": "queries.tcp",
- "item_optional": False,
- "item_title": "Queries TCP",
- "item_type": "integer"
- }]}))
- self.assertEqual(self.stats.command_showschema(owner='Auth', name='queries.perzone'),
- isc.config.create_answer(
- 0, {'Auth':[{
- "item_name": "queries.perzone",
- "item_type": "list",
- "item_optional": False,
- "item_default": [
- {
- "zonename" : "test1.example",
- "queries.udp" : 1,
- "queries.tcp" : 2
- },
- {
- "zonename" : "test2.example",
- "queries.udp" : 3,
- "queries.tcp" : 4
- }
- ],
- "item_title": "Queries per zone",
- "item_description": "Queries per zone",
- "list_item_spec": {
- "item_name": "zones",
- "item_type": "map",
- "item_optional": False,
- "item_default": {},
- "map_item_spec": [
- {
- "item_name": "zonename",
- "item_type": "string",
- "item_optional": False,
- "item_default": "",
- "item_title": "Zonename",
- "item_description": "Zonename"
- },
- {
- "item_name": "queries.udp",
- "item_type": "integer",
- "item_optional": False,
- "item_default": 0,
- "item_title": "Queries UDP per zone",
- "item_description": "A number of UDP query counts per zone"
- },
- {
- "item_name": "queries.tcp",
- "item_type": "integer",
- "item_optional": False,
- "item_default": 0,
- "item_title": "Queries TCP per zone",
- "item_description": "A number of TCP query counts per zone"
- }
- ]
- }
- }]}))
- self.assertEqual(self.stats.command_showschema(owner='Auth', name='nds_queries.perzone'),
- isc.config.create_answer(
- 0, {'Auth':[{
- "item_name": "nds_queries.perzone",
- "item_type": "named_set",
- "item_optional": False,
- "item_default": {
- "test10.example" : {
- "queries.udp" : 1,
- "queries.tcp" : 2
- },
- "test20.example" : {
- "queries.udp" : 3,
- "queries.tcp" : 4
- }
- },
- "item_title": "Queries per zone",
- "item_description": "Queries per zone",
- "named_set_item_spec": {
- "item_name": "zonename",
- "item_type": "map",
- "item_optional": False,
- "item_default": {},
- "item_title": "Zonename",
- "item_description": "Zonename",
- "map_item_spec": [
- {
- "item_name": "queries.udp",
- "item_type": "integer",
- "item_optional": False,
- "item_default": 0,
- "item_title": "Queries UDP per zone",
- "item_description": "A number of UDP query counts per zone"
- },
- {
- "item_name": "queries.tcp",
- "item_type": "integer",
- "item_optional": False,
- "item_default": 0,
- "item_title": "Queries TCP per zone",
- "item_description": "A number of TCP query counts per zone"
- }
- ]
- }
- }]}))
-
- self.assertEqual(self.stats.command_showschema(owner='Stats', name='bar'),
- isc.config.create_answer(
- 1, "specified arguments are incorrect: owner: Stats, name: bar"))
- self.assertEqual(self.stats.command_showschema(name='bar'),
- isc.config.create_answer(
- 1, "module name is not specified"))
-
- @unittest.skipIf(sys.version_info >= (3, 3), "Unsupported in Python 3.3 or higher")
- def test_polling(self):
- stats_server = ThreadingServerManager(MyStats)
- stat = stats_server.server
- stats_server.run()
- self.assertEqual(
- send_command('show', 'Stats'),
- (0, stat.statistics_data))
- # check statistics data of 'Init'
- b10_init = self.base.b10_init.server
- self.assertEqual(
- stat.statistics_data_bymid['Init'][b10_init.cc_session.lname],
- {'boot_time': self.const_datetime})
- self.assertEqual(
- len(stat.statistics_data_bymid['Init']), 1)
- self.assertEqual(
- stat.statistics_data['Init'],
- {'boot_time': self.const_datetime})
- # check statistics data of each 'Auth' instances
- list_auth = ['', '2']
- for i in list_auth:
- auth = getattr(self.base,"auth"+i).server
- for s in stat.statistics_data_bymid['Auth'].values():
- self.assertEqual(
- s, {'queries.perzone': auth.queries_per_zone,
- 'nds_queries.perzone': auth.nds_queries_per_zone,
- 'queries.tcp': auth.queries_tcp,
- 'queries.udp': auth.queries_udp})
- n = len(stat.statistics_data_bymid['Auth'])
- self.assertEqual(n, len(list_auth))
- # check consolidation of statistics data of the auth
- # instances
- self.assertEqual(
- stat.statistics_data['Auth'],
- {'queries.perzone': [
- {'zonename':
- auth.queries_per_zone[0]['zonename'],
- 'queries.tcp':
- auth.queries_per_zone[0]['queries.tcp']*n,
- 'queries.udp':
- auth.queries_per_zone[0]['queries.udp']*n},
- {'zonename': "test2.example",
- 'queries.tcp': 4*n,
- 'queries.udp': 3*n },
- ],
- 'nds_queries.perzone': {
- 'test10.example': {
- 'queries.tcp':
- auth.nds_queries_per_zone['test10.example']['queries.tcp']*n,
- 'queries.udp':
- auth.nds_queries_per_zone['test10.example']['queries.udp']*n},
- 'test20.example': {
- 'queries.tcp':
- 4*n,
- 'queries.udp':
- 3*n},
- },
- 'queries.tcp': auth.queries_tcp*n,
- 'queries.udp': auth.queries_udp*n})
- # check statistics data of 'Stats'
- self.assertEqual(
- len(stat.statistics_data['Stats']), 5)
- self.assertTrue('boot_time' in
- stat.statistics_data['Stats'])
- self.assertTrue('last_update_time' in
- stat.statistics_data['Stats'])
- self.assertTrue('report_time' in
- stat.statistics_data['Stats'])
- self.assertTrue('timestamp' in
- stat.statistics_data['Stats'])
- self.assertEqual(
- stat.statistics_data['Stats']['lname'],
- stat.mccs._session.lname)
- stats_server.shutdown()
-
- def test_polling2(self):
- # set invalid statistics
- b10_init = self.base.b10_init.server
- b10_init.statistics_data = {'boot_time':1}
- stats_server = ThreadingServerManager(MyStats)
- stat = stats_server.server
- stats_server.run()
- self.assertEqual(
- send_command('status', 'Stats'),
- (0, "Stats is up. (PID " + str(os.getpid()) + ")"))
- # check default statistics data of 'Init'
- self.assertEqual(
- stat.statistics_data['Init'],
- {'boot_time': self.const_default_datetime})
- stats_server.shutdown()
-
-class TestOSEnv(unittest.TestCase):
- def test_osenv(self):
- """
- test for the environ variable "B10_FROM_SOURCE"
- "B10_FROM_SOURCE" is set in Makefile
- """
- # test case having B10_FROM_SOURCE
- self.assertTrue("B10_FROM_SOURCE" in os.environ)
- self.assertEqual(stats.SPECFILE_LOCATION, \
- os.environ["B10_FROM_SOURCE"] + os.sep + \
- "src" + os.sep + "bin" + os.sep + "stats" + \
- os.sep + "stats.spec")
- # test case not having B10_FROM_SOURCE
- path = os.environ["B10_FROM_SOURCE"]
- os.environ.pop("B10_FROM_SOURCE")
- self.assertFalse("B10_FROM_SOURCE" in os.environ)
- # import stats again
- imp.reload(stats)
- # revert the changes
- os.environ["B10_FROM_SOURCE"] = path
- imp.reload(stats)
-
-if __name__ == "__main__":
- isc.log.resetUnitTestRootLogger()
- unittest.main()
diff --git a/src/bin/stats/tests/stats-httpd_test.py b/src/bin/stats/tests/stats-httpd_test.py
new file mode 100644
index 0000000..a61ee47
--- /dev/null
+++ b/src/bin/stats/tests/stats-httpd_test.py
@@ -0,0 +1,1127 @@
+# Copyright (C) 2011-2012 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.
+
+"""
+In each of these tests we start several virtual components. They are
+not the real components, no external processes are started. They are
+just simple mock objects running each in its own thread and pretending
+to be bind10 modules. This helps testing the stats http server in a
+close to real environment.
+"""
+
+import unittest
+import os
+import imp
+import socket
+import errno
+import select
+import string
+import time
+import threading
+import http.client
+import xml.etree.ElementTree
+import random
+import urllib.parse
+import sys
+# load this module for xml validation with xsd. For this test, an
+# installation of lxml is required in advance. See http://lxml.de/.
+try:
+ from lxml import etree as lxml_etree
+except ImportError:
+ lxml_etree = None
+
+import isc
+import isc.log
+import stats_httpd
+import stats
+from test_utils import ThreadingServerManager, SignalHandler, \
+ MyStatsHttpd, CONST_BASETIME
+from isc.testutils.ccsession_mock import MockModuleCCSession
+from isc.config import RPCRecipientMissing, RPCError
+
+# This test suite uses xml.etree.ElementTree.XMLParser via
+# xml.etree.ElementTree.parse. On the platform where expat isn't
+# installed, ImportError is raised and it's failed. Check expat is
+# available before the test invocation. Skip this test if it's
+# unavailable.
+try:
+ # ImportError raised if xpat is unavailable
+ xml_parser = xml.etree.ElementTree.XMLParser()
+except ImportError:
+ xml_parser = None
+
+# set XML Namespaces for testing
+XMLNS_XSL = "http://www.w3.org/1999/XSL/Transform"
+XMLNS_XHTML = "http://www.w3.org/1999/xhtml"
+XMLNS_XSD = "http://www.w3.org/2001/XMLSchema"
+XMLNS_XSI = stats_httpd.XMLNS_XSI
+
+DUMMY_DATA = {
+ 'Init' : {
+ "boot_time": time.strftime('%Y-%m-%dT%H:%M:%SZ', CONST_BASETIME)
+ },
+ 'Auth' : {
+ "queries.tcp": 6,
+ "queries.udp": 4,
+ "queries.perzone": [{
+ "zonename": "test1.example",
+ "queries.tcp": 10,
+ "queries.udp": 8
+ }, {
+ "zonename": "test2.example",
+ "queries.tcp": 8,
+ "queries.udp": 6
+ }],
+ "nds_queries.perzone": {
+ "test10.example": {
+ "queries.tcp": 10,
+ "queries.udp": 8
+ },
+ "test20.example": {
+ "queries.tcp": 8,
+ "queries.udp": 6
+ }
+ }
+ },
+ 'Stats' : {
+ "report_time": time.strftime('%Y-%m-%dT%H:%M:%SZ', CONST_BASETIME),
+ "boot_time": time.strftime('%Y-%m-%dT%H:%M:%SZ', CONST_BASETIME),
+ "last_update_time": time.strftime('%Y-%m-%dT%H:%M:%SZ', CONST_BASETIME),
+ "lname": "4d70d40a_c at host",
+ "timestamp": time.mktime(CONST_BASETIME)
+ }
+ }
+
+# Bad practice: this should be localized
+stats._BASETIME = CONST_BASETIME
+stats.get_timestamp = lambda: time.mktime(CONST_BASETIME)
+stats.get_datetime = lambda x=None: time.strftime("%Y-%m-%dT%H:%M:%SZ", CONST_BASETIME)
+
+def get_availaddr(address='127.0.0.1', port=8001):
+ """returns a tuple of address and port which is available to
+ listen on the platform. The first argument is a address for
+ search. The second argument is a port for search. If a set of
+ address and port is failed on the search for the availability, the
+ port number is increased and it goes on the next trial until the
+ available set of address and port is looked up. If the port number
+ reaches over 65535, it may stop the search and raise a
+ OverflowError exception."""
+ while True:
+ for addr in socket.getaddrinfo(
+ address, port, 0,
+ socket.SOCK_STREAM, socket.IPPROTO_TCP):
+ sock = socket.socket(addr[0], socket.SOCK_STREAM)
+ try:
+ sock.bind((address, port))
+ return (address, port)
+ except socket.error:
+ continue
+ finally:
+ if sock: sock.close()
+ # This address and port number are already in use.
+ # next port number is added
+ port = port + 1
+
+def is_ipv6_enabled(address='::1', port=8001):
+ """checks IPv6 enabled on the platform. address for check is '::1'
+ and port for check is random number between 8001 and
+ 65535. Retrying is 3 times even if it fails. The built-in socket
+ module provides a 'has_ipv6' parameter, but it's not used here
+ because there may be a situation where the value is True on an
+ environment where the IPv6 config is disabled."""
+ for p in random.sample(range(port, 65535), 3):
+ try:
+ sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
+ sock.bind((address, p))
+ return True
+ except socket.error:
+ continue
+ finally:
+ if sock: sock.close()
+ return False
+
+class TestItemNameList(unittest.TestCase):
+
+ def test_item_name_list(self):
+ # for a one-element list
+ self.assertEqual(['a'],
+ stats_httpd.item_name_list({'a':1}, 'a'))
+ # for a dict under a dict
+ self.assertEqual(['a','a/b'],
+ stats_httpd.item_name_list({'a':{'b':1}}, 'a'))
+ self.assertEqual(['a/b'],
+ stats_httpd.item_name_list({'a':{'b':1}}, 'a/b'))
+ self.assertEqual(['a','a/b','a/b/c'],
+ stats_httpd.item_name_list({'a':{'b':{'c':1}}}, 'a'))
+ self.assertEqual(['a/b','a/b/c'],
+ stats_httpd.item_name_list({'a':{'b':{'c':1}}},
+ 'a/b'))
+ self.assertEqual(['a/b/c'],
+ stats_httpd.item_name_list({'a':{'b':{'c':1}}},
+ 'a/b/c'))
+ # for a list under a dict
+ self.assertEqual(['a[2]'],
+ stats_httpd.item_name_list({'a':[1,2,3]}, 'a[2]'))
+ self.assertEqual(['a', 'a[0]', 'a[1]', 'a[2]'],
+ stats_httpd.item_name_list({'a':[1,2,3]}, 'a'))
+ self.assertEqual(['a', 'a[0]', 'a[1]', 'a[2]'],
+ stats_httpd.item_name_list({'a':[1,2,3]}, ''))
+ # for a list under a dict under a dict
+ self.assertEqual(['a', 'a/b', 'a/b[0]', 'a/b[1]', 'a/b[2]'],
+ stats_httpd.item_name_list({'a':{'b':[1,2,3]}}, 'a'))
+ self.assertEqual(['a', 'a/b', 'a/b[0]', 'a/b[1]', 'a/b[2]'],
+ stats_httpd.item_name_list({'a':{'b':[1,2,3]}}, ''))
+ self.assertEqual(['a/b', 'a/b[0]', 'a/b[1]', 'a/b[2]'],
+ stats_httpd.item_name_list({'a':{'b':[1,2,3]}}, 'a/b'))
+ # for a mixed case of the above
+ self.assertEqual(['a', 'a/b', 'a/b[0]', 'a/b[1]', 'a/b[2]', 'a/c'],
+ stats_httpd.item_name_list(
+ {'a':{'b':[1,2,3], 'c':1}}, 'a'))
+ self.assertEqual(['a/b', 'a/b[0]', 'a/b[1]', 'a/b[2]'],
+ stats_httpd.item_name_list(
+ {'a':{'b':[1,2,3], 'c':1}}, 'a/b'))
+ self.assertEqual(['a/c'],
+ stats_httpd.item_name_list(
+ {'a':{'b':[1,2,3], 'c':1}}, 'a/c'))
+ # for specifying a wrong identifier which is not found in
+ # element
+ self.assertRaises(isc.cc.data.DataNotFoundError,
+ stats_httpd.item_name_list, {'x':1}, 'a')
+ # for specifying a string in element and an empty string in
+ # identifier
+ self.assertEqual([],
+ stats_httpd.item_name_list('a', ''))
+ # for specifying empty strings in element and identifier
+ self.assertEqual([],
+ stats_httpd.item_name_list('', ''))
+ # for specifying wrong element, which is an non-empty string,
+ # and an non-empty string in identifier
+ self.assertRaises(isc.cc.data.DataTypeError,
+ stats_httpd.item_name_list, 'a', 'a')
+ # for specifying None in element and identifier
+ self.assertRaises(isc.cc.data.DataTypeError,
+ stats_httpd.item_name_list, None, None)
+ # for specifying non-dict in element
+ self.assertRaises(isc.cc.data.DataTypeError,
+ stats_httpd.item_name_list, [1,2,3], 'a')
+ self.assertRaises(isc.cc.data.DataTypeError,
+ stats_httpd.item_name_list, [1,2,3], '')
+ # for checking key names sorted which consist of element
+ num = 11
+ keys = [ 'a', 'aa', 'b' ]
+ keys.sort(reverse=True)
+ dictlist = dict([ (k, list(range(num))) for k in keys ])
+ keys.sort()
+ ans = []
+ for k in keys:
+ ans += [k] + [ '%s[%d]' % (k, i) for i in range(num) ]
+ self.assertEqual(ans,
+ stats_httpd.item_name_list(dictlist, ''))
+
+class TestHttpHandler(unittest.TestCase):
+ """Tests for HttpHandler class"""
+ def setUp(self):
+ # set the signal handler for deadlock
+ self.sig_handler = SignalHandler(self.fail)
+ DUMMY_DATA['Stats']['lname'] = 'test-lname'
+ (self.address, self.port) = get_availaddr()
+ self.stats_httpd_server = ThreadingServerManager(MyStatsHttpd,
+ (self.address,
+ self.port))
+ self.stats_httpd = self.stats_httpd_server.server
+ self.stats_httpd_server.run()
+ self.client = http.client.HTTPConnection(self.address, self.port)
+ self.client._http_vsn_str = 'HTTP/1.0\n'
+ self.client.connect()
+
+ def tearDown(self):
+ self.client.close()
+ # reset the signal handler
+ self.sig_handler.reset()
+
+ @unittest.skipUnless(xml_parser, "skipping the test using XMLParser")
+ def test_do_GET(self):
+ self.assertTrue(type(self.stats_httpd.httpd) is list)
+ self.assertEqual(len(self.stats_httpd.httpd), 1)
+ self.assertEqual((self.address, self.port), self.stats_httpd.http_addrs[0])
+
+ def check_XML_URL_PATH(path=''):
+ url_path = '%s/%s' % (stats_httpd.XML_URL_PATH, path)
+ url_path = urllib.parse.quote(url_path)
+ self.client.putrequest('GET', url_path)
+ self.client.endheaders()
+ response = self.client.getresponse()
+ self.assertEqual(response.getheader("Content-type"), "text/xml")
+ self.assertGreater(int(response.getheader("Content-Length")), 0)
+ self.assertEqual(response.status, 200)
+ xml_doctype = response.readline().decode()
+ xsl_doctype = response.readline().decode()
+ self.assertGreater(len(xml_doctype), 0)
+ self.assertGreater(len(xsl_doctype), 0)
+ root = xml.etree.ElementTree.parse(response).getroot()
+ self.assertGreater(root.tag.find('statistics'), 0)
+ schema_loc = '{%s}schemaLocation' % XMLNS_XSI
+ # check the path of XSD
+ self.assertEqual(root.attrib[schema_loc],
+ stats_httpd.XSD_NAMESPACE + ' '
+ + stats_httpd.XSD_URL_PATH)
+ # check the path of XSL
+ self.assertTrue(xsl_doctype.startswith(
+ '<?xml-stylesheet type="text/xsl" href="' +
+ stats_httpd.XSL_URL_PATH
+ + '"?>'))
+ # check whether the list of 'identifier' attributes in
+ # root is same as the list of item names in DUMMY_DATA
+ id_list = [ elm.attrib['identifier'] for elm in root ]
+ item_list = [ it for it in \
+ stats_httpd.item_name_list(DUMMY_DATA, path) \
+ if len(it.split('/')) > 1 ]
+ self.assertEqual(id_list, item_list)
+ for elem in root:
+ attr = elem.attrib
+ value = isc.cc.data.find(DUMMY_DATA, attr['identifier'])
+ # No 'value' attribute should be found in the 'item'
+ # element when datatype of the value is list or dict.
+ if type(value) is list or type(value) is dict:
+ self.assertFalse('value' in attr)
+ # The value of the 'value' attribute should be checked
+ # after casting it to string type if datatype of the
+ # value is int or float. Because attr['value'] returns
+ # string type even if its value is int or float.
+ elif type(value) is int or type(value) is float:
+ self.assertEqual(attr['value'], str(value))
+ else:
+ self.assertEqual(attr['value'], value)
+
+ # URL is '/bind10/statistics/xml'
+ check_XML_URL_PATH()
+ for path in stats_httpd.item_name_list(DUMMY_DATA, ''):
+ check_XML_URL_PATH(path)
+
+ def check_XSD_URL_PATH():
+ url_path = stats_httpd.XSD_URL_PATH
+ url_path = urllib.parse.quote(url_path)
+ self.client.putrequest('GET', url_path)
+ self.client.endheaders()
+ response = self.client.getresponse()
+ self.assertEqual(response.getheader("Content-type"), "text/xml")
+ self.assertGreater(int(response.getheader("Content-Length")), 0)
+ self.assertEqual(response.status, 200)
+ root = xml.etree.ElementTree.parse(response).getroot()
+ url_xmlschema = '{%s}' % XMLNS_XSD
+ self.assertGreater(root.tag.find('schema'), 0)
+ self.assertTrue(hasattr(root, 'attrib'))
+ self.assertTrue('targetNamespace' in root.attrib)
+ self.assertEqual(root.attrib['targetNamespace'],
+ stats_httpd.XSD_NAMESPACE)
+
+ # URL is '/bind10/statistics/xsd'
+ check_XSD_URL_PATH()
+
+ def check_XSL_URL_PATH():
+ url_path = stats_httpd.XSL_URL_PATH
+ url_path = urllib.parse.quote(url_path)
+ self.client.putrequest('GET', url_path)
+ self.client.endheaders()
+ response = self.client.getresponse()
+ self.assertEqual(response.getheader("Content-type"), "text/xml")
+ self.assertGreater(int(response.getheader("Content-Length")), 0)
+ self.assertEqual(response.status, 200)
+ root = xml.etree.ElementTree.parse(response).getroot()
+ url_trans = '{%s}' % XMLNS_XSL
+ url_xhtml = '{%s}' % XMLNS_XHTML
+ self.assertEqual(root.tag, url_trans + 'stylesheet')
+
+ # URL is '/bind10/statistics/xsl'
+ check_XSL_URL_PATH()
+
+ # 302 redirect
+ self.client._http_vsn_str = 'HTTP/1.1'
+ self.client.putrequest('GET', '/')
+ self.client.putheader('Host', self.address)
+ self.client.endheaders()
+ response = self.client.getresponse()
+ self.assertEqual(response.status, 302)
+ self.assertEqual(response.getheader('Location'),
+ "http://%s:%d%s/" % (self.address, self.port, stats_httpd.XML_URL_PATH))
+
+ # 404 NotFound (random path)
+ self.client._http_vsn_str = 'HTTP/1.0'
+ self.client.putrequest('GET', '/path/to/foo/bar')
+ self.client.endheaders()
+ response = self.client.getresponse()
+ self.assertEqual(response.status, 404)
+ self.client._http_vsn_str = 'HTTP/1.0'
+ self.client.putrequest('GET', '/bind10/foo')
+ self.client.endheaders()
+ response = self.client.getresponse()
+ self.assertEqual(response.status, 404)
+ self.client._http_vsn_str = 'HTTP/1.0'
+ self.client.putrequest('GET', '/bind10/statistics/foo')
+ self.client.endheaders()
+ response = self.client.getresponse()
+ self.assertEqual(response.status, 404)
+ self.client._http_vsn_str = 'HTTP/1.0'
+ self.client.putrequest('GET', stats_httpd.XML_URL_PATH + 'Auth') # with no slash
+ self.client.endheaders()
+ response = self.client.getresponse()
+ self.assertEqual(response.status, 404)
+
+ # 200 ok
+ self.client._http_vsn_str = 'HTTP/1.0'
+ self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/')
+ self.client.endheaders()
+ response = self.client.getresponse()
+ self.assertEqual(response.status, 200)
+ self.client._http_vsn_str = 'HTTP/1.0'
+ self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/#foo')
+ self.client.endheaders()
+ response = self.client.getresponse()
+ self.assertEqual(response.status, 200)
+ self.client._http_vsn_str = 'HTTP/1.0'
+ self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/?foo=bar')
+ self.client.endheaders()
+ response = self.client.getresponse()
+ self.assertEqual(response.status, 200)
+
+ # 404 NotFound (too long path)
+ self.client._http_vsn_str = 'HTTP/1.0'
+ self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/Init/boot_time/a')
+ self.client.endheaders()
+ response = self.client.getresponse()
+ self.assertEqual(response.status, 404)
+
+ # 404 NotFound (nonexistent module name)
+ self.client._http_vsn_str = 'HTTP/1.0'
+ self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/Foo')
+ self.client.endheaders()
+ response = self.client.getresponse()
+ self.assertEqual(response.status, 404)
+ self.client._http_vsn_str = 'HTTP/1.0'
+ self.client.putrequest('GET', stats_httpd.XSD_URL_PATH + '/Foo')
+ self.client.endheaders()
+ response = self.client.getresponse()
+ self.assertEqual(response.status, 404)
+ self.client._http_vsn_str = 'HTTP/1.0'
+ self.client.putrequest('GET', stats_httpd.XSL_URL_PATH + '/Foo')
+ self.client.endheaders()
+ response = self.client.getresponse()
+ self.assertEqual(response.status, 404)
+
+ # 404 NotFound (nonexistent item name)
+ self.client._http_vsn_str = 'HTTP/1.0'
+ self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/Foo/bar')
+ self.client.endheaders()
+ response = self.client.getresponse()
+ self.assertEqual(response.status, 404)
+ self.client._http_vsn_str = 'HTTP/1.0'
+ self.client.putrequest('GET', stats_httpd.XSD_URL_PATH + '/Foo/bar')
+ self.client.endheaders()
+ response = self.client.getresponse()
+ self.assertEqual(response.status, 404)
+ self.client._http_vsn_str = 'HTTP/1.0'
+ self.client.putrequest('GET', stats_httpd.XSL_URL_PATH + '/Foo/bar')
+ self.client.endheaders()
+ response = self.client.getresponse()
+ self.assertEqual(response.status, 404)
+
+ # 404 NotFound (existent module but nonexistent item name)
+ self.client._http_vsn_str = 'HTTP/1.0'
+ self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/Auth/bar')
+ self.client.endheaders()
+ response = self.client.getresponse()
+ self.assertEqual(response.status, 404)
+ self.client._http_vsn_str = 'HTTP/1.0'
+ self.client.putrequest('GET', stats_httpd.XSD_URL_PATH + '/Auth/bar')
+ self.client.endheaders()
+ response = self.client.getresponse()
+ self.assertEqual(response.status, 404)
+ self.client._http_vsn_str = 'HTTP/1.0'
+ self.client.putrequest('GET', stats_httpd.XSL_URL_PATH + '/Auth/bar')
+ self.client.endheaders()
+ response = self.client.getresponse()
+ self.assertEqual(response.status, 404)
+
+ def test_do_GET_failed1(self):
+ # failure case (Stats is down, so rpc_call() results in an exception)
+ # Note: this should eventually be RPCRecipientMissing.
+ self.stats_httpd._rpc_answers.append(
+ isc.cc.session.SessionTimeout('timeout'))
+
+ # request XML
+ self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/')
+ self.client.endheaders()
+ response = self.client.getresponse()
+ self.assertEqual(response.status, 500)
+
+ # request XSD
+ self.client.putrequest('GET', stats_httpd.XSD_URL_PATH)
+ self.client.endheaders()
+ response = self.client.getresponse()
+ self.assertEqual(response.status, 200)
+
+ # request XSL
+ self.client.putrequest('GET', stats_httpd.XSL_URL_PATH)
+ self.client.endheaders()
+ response = self.client.getresponse()
+ self.assertEqual(response.status, 200)
+
+ def test_do_GET_failed2(self):
+ # failure case(Stats replies an error)
+ self.stats_httpd._rpc_answers.append(
+ RPCError(1, "specified arguments are incorrect: I have an error."))
+
+ # request XML
+ self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/')
+ self.client.endheaders()
+ response = self.client.getresponse()
+ self.assertEqual(response.status, 404)
+
+ # request XSD
+ self.stats_httpd._rpc_answers.append(
+ RPCError(1, "specified arguments are incorrect: I have an error."))
+ self.client.putrequest('GET', stats_httpd.XSD_URL_PATH)
+ self.client.endheaders()
+ response = self.client.getresponse()
+ self.assertEqual(response.status, 200)
+
+ # request XSL
+ self.stats_httpd._rpc_answers.append(
+ RPCError(1, "specified arguments are incorrect: I have an error."))
+ self.client.putrequest('GET', stats_httpd.XSL_URL_PATH)
+ self.client.endheaders()
+ response = self.client.getresponse()
+ self.assertEqual(response.status, 200)
+
+ def test_do_HEAD(self):
+ self.client.putrequest('HEAD', stats_httpd.XML_URL_PATH + '/')
+ self.client.endheaders()
+ response = self.client.getresponse()
+ self.assertEqual(response.status, 200)
+
+ self.client.putrequest('HEAD', '/path/to/foo/bar')
+ self.client.endheaders()
+ response = self.client.getresponse()
+ self.assertEqual(response.status, 404)
+
+ @unittest.skipUnless(lxml_etree, "skipping XML validation with XSD")
+ def test_xml_validation_with_xsd(self):
+ """Tests for XML validation with XSD. If lxml is not
+ installed, this tests would be skipped."""
+ def request_xsd():
+ url_path = stats_httpd.XSD_URL_PATH
+ url_path = urllib.parse.quote(url_path)
+ self.client.putrequest('GET', url_path)
+ self.client.endheaders()
+ xsd_doc = self.client.getresponse()
+ xsd_doc = lxml_etree.parse(xsd_doc)
+ return lxml_etree.XMLSchema(xsd_doc)
+
+ def request_xmldoc(path=''):
+ url_path = '%s/%s' % (stats_httpd.XML_URL_PATH, path)
+ url_path = urllib.parse.quote(url_path)
+ self.client.putrequest('GET', url_path)
+ self.client.endheaders()
+ xml_doc = self.client.getresponse()
+ return lxml_etree.parse(xml_doc)
+
+ # request XSD and XML
+ xsd = request_xsd()
+ xml_doc = request_xmldoc()
+ # do validation
+ self.assertTrue(xsd.validate(xml_doc))
+
+ # validate each paths in DUMMY_DATA
+ for path in stats_httpd.item_name_list(DUMMY_DATA, ''):
+ # request XML
+ xml_doc = request_xmldoc(path)
+ # do validation
+ self.assertTrue(xsd.validate(xml_doc))
+
+class TestHttpServerError(unittest.TestCase):
+ """Tests for HttpServerError exception"""
+ def test_raises(self):
+ try:
+ raise stats_httpd.HttpServerError('Nothing')
+ except stats_httpd.HttpServerError as err:
+ self.assertEqual(str(err), 'Nothing')
+
+class TestHttpServer(unittest.TestCase):
+ """Tests for HttpServer class"""
+ def setUp(self):
+ # set the signal handler for deadlock
+ self.sig_handler = SignalHandler(self.fail)
+
+ def tearDown(self):
+ if hasattr(self, "stats_httpd"):
+ self.stats_httpd.stop()
+ # reset the signal handler
+ self.sig_handler.reset()
+
+ def test_httpserver(self):
+ self.stats_httpd = MyStatsHttpd(get_availaddr())
+ self.assertEqual(type(self.stats_httpd.httpd), list)
+ self.assertEqual(len(self.stats_httpd.httpd), 1)
+ for httpd in self.stats_httpd.httpd:
+ self.assertTrue(isinstance(httpd, stats_httpd.HttpServer))
+
+class TestStatsHttpdError(unittest.TestCase):
+ """Tests for StatsHttpdError exception"""
+
+ def test_raises1(self):
+ try:
+ raise stats_httpd.StatsHttpdError('Nothing')
+ except stats_httpd.StatsHttpdError as err:
+ self.assertEqual(str(err), 'Nothing')
+
+ def test_raises2(self):
+ try:
+ raise stats_httpd.StatsHttpdDataError('Nothing')
+ except stats_httpd.StatsHttpdDataError as err:
+ self.assertEqual(str(err), 'Nothing')
+
+class TestStatsHttpd(unittest.TestCase):
+ """Tests for StatsHttpd class"""
+
+ def setUp(self):
+ # set the signal handler for deadlock
+ self.sig_handler = SignalHandler(self.fail)
+ # checking IPv6 enabled on this platform
+ self.ipv6_enabled = is_ipv6_enabled()
+ # instantiation of StatsHttpd indirectly calls gethostbyaddr(), which
+ # can block for an uncontrollable period, leading many undesirable
+ # results. We should rather eliminate the reliance, but until we
+ # can make such fundamental cleanup we replace it with a faked method;
+ # in our test scenario the return value doesn't matter.
+ self.__gethostbyaddr_orig = socket.gethostbyaddr
+ socket.gethostbyaddr = lambda x: ('test.example.', [], None)
+
+ # Some tests replace this library function. Keep the original for
+ # restor
+ self.__orig_select_select = select.select
+
+ def tearDown(self):
+ socket.gethostbyaddr = self.__gethostbyaddr_orig
+ if hasattr(self, "stats_httpd"):
+ self.stats_httpd.stop()
+ # reset the signal handler
+ self.sig_handler.reset()
+
+ # restore original of replaced library
+ select.select = self.__orig_select_select
+
+ def test_init(self):
+ server_address = get_availaddr()
+ self.stats_httpd = MyStatsHttpd(server_address)
+ self.assertEqual(self.stats_httpd.running, False)
+ self.assertEqual(self.stats_httpd.poll_intval, 0.5)
+ self.assertNotEqual(len(self.stats_httpd.httpd), 0)
+ self.assertIsNotNone(self.stats_httpd.mccs)
+ self.assertIsNotNone(self.stats_httpd.cc_session)
+ # The real CfgMgr would return 'version', but our test mock omits it,
+ # so the len(config) should be 1
+ self.assertEqual(len(self.stats_httpd.config), 1)
+ self.assertTrue('listen_on' in self.stats_httpd.config)
+ self.assertEqual(len(self.stats_httpd.config['listen_on']), 1)
+ self.assertTrue('address' in self.stats_httpd.config['listen_on'][0])
+ self.assertTrue('port' in self.stats_httpd.config['listen_on'][0])
+ self.assertTrue(server_address in set(self.stats_httpd.http_addrs))
+ self.assertEqual('StatsHttpd', self.stats_httpd.mccs.\
+ get_module_spec().get_module_name())
+
+ def test_init_hterr(self):
+ """Test the behavior of StatsHttpd constructor when open_httpd fails.
+
+ We specifically check the following two:
+ - close_mccs() is called (so stats-httpd tells ConfigMgr it's shutting
+ down)
+ - the constructor results in HttpServerError exception.
+
+ """
+ self.__mccs_closed = False
+ def call_checker():
+ self.__mccs_closed = True
+ class FailingStatsHttpd(MyStatsHttpd):
+ def open_httpd(self):
+ raise stats_httpd.HttpServerError
+ def close_mccs(self):
+ call_checker()
+ self.assertRaises(stats_httpd.HttpServerError, FailingStatsHttpd)
+ self.assertTrue(self.__mccs_closed)
+
+ def test_openclose_mccs(self):
+ self.stats_httpd = MyStatsHttpd(get_availaddr())
+ mccs = self.stats_httpd.mccs
+ self.assertFalse(self.stats_httpd.mccs.stopped)
+ self.assertFalse(self.stats_httpd.mccs.closed)
+ self.stats_httpd.close_mccs()
+ self.assertTrue(mccs.stopped)
+ self.assertTrue(mccs.closed)
+ self.assertIsNone(self.stats_httpd.mccs)
+ self.stats_httpd.open_mccs()
+ self.assertIsNotNone(self.stats_httpd.mccs)
+ self.stats_httpd.mccs = None
+ self.assertIsNone(self.stats_httpd.mccs)
+ self.assertIsNone(self.stats_httpd.close_mccs())
+
+ def test_mccs(self):
+ self.stats_httpd = MyStatsHttpd(get_availaddr())
+ self.assertIsNotNone(self.stats_httpd.mccs.get_socket())
+ self.assertTrue(
+ isinstance(self.stats_httpd.mccs.get_socket(), socket.socket))
+ self.assertIsNotNone(self.stats_httpd.cc_session)
+ statistics_spec = self.stats_httpd.get_stats_spec()
+ for mod in DUMMY_DATA:
+ self.assertTrue(mod in statistics_spec)
+ for cfg in statistics_spec[mod]:
+ self.assertTrue('item_name' in cfg)
+ self.assertTrue(cfg['item_name'] in DUMMY_DATA[mod])
+ self.assertTrue(len(statistics_spec[mod]), len(DUMMY_DATA[mod]))
+ self.stats_httpd.close_mccs()
+ self.assertIsNone(self.stats_httpd.mccs)
+
+ def test_httpd(self):
+ # dual stack (addresses is ipv4 and ipv6)
+ if self.ipv6_enabled:
+ server_addresses = (get_availaddr('::1'), get_availaddr())
+ self.stats_httpd = MyStatsHttpd(*server_addresses)
+ for ht in self.stats_httpd.httpd:
+ self.assertTrue(isinstance(ht, stats_httpd.HttpServer))
+ self.assertTrue(ht.address_family in set([socket.AF_INET,
+ socket.AF_INET6]))
+ self.assertTrue(isinstance(ht.socket, socket.socket))
+ ht.socket.close() # to silence warning about resource leak
+ self.stats_httpd.close_mccs() # ditto
+
+ # dual stack (address is ipv6)
+ if self.ipv6_enabled:
+ server_addresses = get_availaddr('::1')
+ self.stats_httpd = MyStatsHttpd(server_addresses)
+ for ht in self.stats_httpd.httpd:
+ self.assertTrue(isinstance(ht, stats_httpd.HttpServer))
+ self.assertEqual(ht.address_family, socket.AF_INET6)
+ self.assertTrue(isinstance(ht.socket, socket.socket))
+ ht.socket.close()
+ self.stats_httpd.close_mccs() # ditto
+
+ # dual/single stack (address is ipv4)
+ server_addresses = get_availaddr()
+ self.stats_httpd = MyStatsHttpd(server_addresses)
+ for ht in self.stats_httpd.httpd:
+ self.assertTrue(isinstance(ht, stats_httpd.HttpServer))
+ self.assertEqual(ht.address_family, socket.AF_INET)
+ self.assertTrue(isinstance(ht.socket, socket.socket))
+ ht.socket.close()
+ self.stats_httpd.close_mccs()
+
+ def test_httpd_anyIPv4(self):
+ # any address (IPv4)
+ server_addresses = get_availaddr(address='0.0.0.0')
+ self.stats_httpd = MyStatsHttpd(server_addresses)
+ for ht in self.stats_httpd.httpd:
+ self.assertTrue(isinstance(ht, stats_httpd.HttpServer))
+ self.assertEqual(ht.address_family,socket.AF_INET)
+ self.assertTrue(isinstance(ht.socket, socket.socket))
+
+ def test_httpd_anyIPv6(self):
+ # any address (IPv6)
+ if self.ipv6_enabled:
+ server_addresses = get_availaddr(address='::')
+ self.stats_httpd = MyStatsHttpd(server_addresses)
+ for ht in self.stats_httpd.httpd:
+ self.assertTrue(isinstance(ht, stats_httpd.HttpServer))
+ self.assertEqual(ht.address_family,socket.AF_INET6)
+ self.assertTrue(isinstance(ht.socket, socket.socket))
+
+ def test_httpd_failed(self):
+ # existent hostname
+ self.assertRaises(stats_httpd.HttpServerError, MyStatsHttpd,
+ get_availaddr(address='localhost'))
+
+ # nonexistent hostname
+ self.assertRaises(stats_httpd.HttpServerError, MyStatsHttpd,
+ ('my.host.domain', 8000))
+
+ # over flow of port number
+ self.assertRaises(stats_httpd.HttpServerError, MyStatsHttpd,
+ ('127.0.0.1', 80000))
+
+ # negative
+ self.assertRaises(stats_httpd.HttpServerError, MyStatsHttpd,
+ ('127.0.0.1', -8000))
+
+ # alphabet
+ self.assertRaises(stats_httpd.HttpServerError, MyStatsHttpd,
+ ('127.0.0.1', 'ABCDE'))
+
+ # Address already in use
+ server_addresses = get_availaddr()
+ server = MyStatsHttpd(server_addresses)
+ self.assertRaises(stats_httpd.HttpServerError, MyStatsHttpd,
+ server_addresses)
+
+ def __faked_select(self, ex=None):
+ """A helper subroutine for tests using faked select.select.
+
+ See test_running() for basic features. If ex is not None,
+ it's assumed to be an exception object and will be raised on the
+ first call.
+
+ """
+ self.assertTrue(self.stats_httpd.running)
+ self.__call_count += 1
+ if ex is not None and self.__call_count == 1:
+ raise ex
+ if self.__call_count == 2:
+ self.stats_httpd.running = False
+ assert self.__call_count <= 2 # safety net to avoid infinite loop
+ return ([], [], [])
+
+ def test_running(self):
+ # Previous version of this test checks the result of "status" and
+ # "shutdown" commands; however, they are more explicitly tested
+ # in specific tests. In this test we only have to check:
+ # - start() will set 'running' to True
+ # - as long as 'running' is True, it keeps calling select.select
+ # - when running becomes False, it exists from the loop and calls
+ # stop()
+ self.stats_httpd = MyStatsHttpd(get_availaddr())
+ self.assertFalse(self.stats_httpd.running)
+
+ # In this test we'll call select.select() 2 times: on the first call
+ # stats_httpd.running should be True; on the second call the faked
+ # select() will set it to False.
+ self.__call_count = 0
+ select.select = lambda r, w, x, t: self.__faked_select()
+ self.stats_httpd.start()
+ self.assertFalse(self.stats_httpd.running)
+ self.assertIsNone(self.stats_httpd.mccs) # stop() clears .mccs
+
+ def test_running_fail(self):
+ # A failure case of start(): we close the (real but dummy) socket for
+ # the CC session. This breaks the select-loop due to exception
+ self.stats_httpd = MyStatsHttpd(get_availaddr())
+ self.stats_httpd.mccs.get_socket().close()
+ self.assertRaises(ValueError, self.stats_httpd.start)
+
+ def test_failure_with_a_select_error (self):
+ """checks select.error is raised if the exception except
+ errno.EINTR is raised while it's selecting"""
+ def raise_select_except(*args):
+ raise select.error('dummy error')
+ select.select = raise_select_except
+ self.stats_httpd = MyStatsHttpd(get_availaddr())
+ self.assertRaises(select.error, self.stats_httpd.start)
+
+ def test_nofailure_with_errno_EINTR(self):
+ """checks no exception is raised if errno.EINTR is raised
+ while it's selecting"""
+ self.__call_count = 0
+ select.select = lambda r, w, x, t: self.__faked_select(
+ select.error(errno.EINTR))
+ self.stats_httpd = MyStatsHttpd(get_availaddr())
+ self.stats_httpd.start() # shouldn't leak the exception
+ self.assertFalse(self.stats_httpd.running)
+ self.assertIsNone(self.stats_httpd.mccs)
+
+ def test_open_template(self):
+ self.stats_httpd = MyStatsHttpd(get_availaddr())
+ # successful conditions
+ tmpl = self.stats_httpd.open_template(
+ stats_httpd.XML_TEMPLATE_LOCATION)
+ self.assertTrue(isinstance(tmpl, string.Template))
+ opts = dict(
+ xml_string="<dummy></dummy>",
+ xsl_url_path="/path/to/")
+ lines = tmpl.substitute(opts)
+ for n in opts:
+ self.assertGreater(lines.find(opts[n]), 0)
+ tmpl = self.stats_httpd.open_template(
+ stats_httpd.XSD_TEMPLATE_LOCATION)
+ self.assertTrue(isinstance(tmpl, string.Template))
+ opts = dict(xsd_namespace="http://host/path/to/")
+ lines = tmpl.substitute(opts)
+ for n in opts:
+ self.assertGreater(lines.find(opts[n]), 0)
+ tmpl = self.stats_httpd.open_template(
+ stats_httpd.XSL_TEMPLATE_LOCATION)
+ self.assertTrue(isinstance(tmpl, string.Template))
+ opts = dict(xsd_namespace="http://host/path/to/")
+ lines = tmpl.substitute(opts)
+ for n in opts:
+ self.assertGreater(lines.find(opts[n]), 0)
+ # unsuccessful condition
+ self.assertRaises(
+ stats_httpd.StatsHttpdDataError,
+ self.stats_httpd.open_template, '/path/to/foo/bar')
+
+ def test_commands(self):
+ self.stats_httpd = MyStatsHttpd(get_availaddr())
+ self.assertEqual(self.stats_httpd.command_handler("status", None),
+ isc.config.ccsession.create_answer(
+ 0, "Stats Httpd is up. (PID " + str(os.getpid()) + ")"))
+ self.stats_httpd.running = True
+ self.assertEqual(self.stats_httpd.command_handler("shutdown", None),
+ isc.config.ccsession.create_answer(0))
+ self.assertFalse(self.stats_httpd.running)
+ self.assertEqual(
+ self.stats_httpd.command_handler("__UNKNOWN_COMMAND__", None),
+ isc.config.ccsession.create_answer(
+ 1, "Unknown command: __UNKNOWN_COMMAND__"))
+
+ def test_config(self):
+ self.stats_httpd = MyStatsHttpd(get_availaddr())
+ self.assertEqual(
+ self.stats_httpd.config_handler(dict(_UNKNOWN_KEY_=None)),
+ isc.config.ccsession.create_answer(
+ 1, "unknown item _UNKNOWN_KEY_"))
+
+ addresses = get_availaddr()
+ self.assertEqual(
+ self.stats_httpd.config_handler(
+ dict(listen_on=[dict(address=addresses[0],port=addresses[1])])),
+ isc.config.ccsession.create_answer(0))
+ self.assertTrue("listen_on" in self.stats_httpd.config)
+ for addr in self.stats_httpd.config["listen_on"]:
+ self.assertTrue("address" in addr)
+ self.assertTrue("port" in addr)
+ self.assertTrue(addr["address"] == addresses[0])
+ self.assertTrue(addr["port"] == addresses[1])
+
+ if self.ipv6_enabled:
+ addresses = get_availaddr("::1")
+ self.assertEqual(
+ self.stats_httpd.config_handler(
+ dict(listen_on=[dict(address=addresses[0],port=addresses[1])])),
+ isc.config.ccsession.create_answer(0))
+ self.assertTrue("listen_on" in self.stats_httpd.config)
+ for addr in self.stats_httpd.config["listen_on"]:
+ self.assertTrue("address" in addr)
+ self.assertTrue("port" in addr)
+ self.assertTrue(addr["address"] == addresses[0])
+ self.assertTrue(addr["port"] == addresses[1])
+
+ addresses = get_availaddr()
+ self.assertEqual(
+ self.stats_httpd.config_handler(
+ dict(listen_on=[dict(address=addresses[0],port=addresses[1])])),
+ isc.config.ccsession.create_answer(0))
+ self.assertTrue("listen_on" in self.stats_httpd.config)
+ for addr in self.stats_httpd.config["listen_on"]:
+ self.assertTrue("address" in addr)
+ self.assertTrue("port" in addr)
+ self.assertTrue(addr["address"] == addresses[0])
+ self.assertTrue(addr["port"] == addresses[1])
+ (ret, arg) = isc.config.ccsession.parse_answer(
+ self.stats_httpd.config_handler(
+ dict(listen_on=[dict(address="1.2.3.4",port=543210)]))
+ )
+ self.assertEqual(ret, 1)
+
+ @unittest.skipUnless(xml_parser, "skipping the test using XMLParser")
+ def test_xml_handler(self):
+ self.stats_httpd = MyStatsHttpd(get_availaddr())
+ module_name = 'Dummy'
+ stats_spec = \
+ { module_name :
+ [{
+ "item_name": "foo",
+ "item_type": "string",
+ "item_optional": False,
+ "item_default": "bar",
+ "item_description": "foo is bar",
+ "item_title": "Foo"
+ },
+ {
+ "item_name": "foo2",
+ "item_type": "list",
+ "item_optional": False,
+ "item_default": [
+ {
+ "zonename" : "test1",
+ "queries.udp" : 1,
+ "queries.tcp" : 2
+ },
+ {
+ "zonename" : "test2",
+ "queries.udp" : 3,
+ "queries.tcp" : 4
+ }
+ ],
+ "item_title": "Foo bar",
+ "item_description": "Foo bar",
+ "list_item_spec": {
+ "item_name": "foo2-1",
+ "item_type": "map",
+ "item_optional": False,
+ "item_default": {},
+ "map_item_spec": [
+ {
+ "item_name": "foo2-1-1",
+ "item_type": "string",
+ "item_optional": False,
+ "item_default": "",
+ "item_title": "Foo2 1 1",
+ "item_description": "Foo bar"
+ },
+ {
+ "item_name": "foo2-1-2",
+ "item_type": "integer",
+ "item_optional": False,
+ "item_default": 0,
+ "item_title": "Foo2 1 2",
+ "item_description": "Foo bar"
+ },
+ {
+ "item_name": "foo2-1-3",
+ "item_type": "integer",
+ "item_optional": False,
+ "item_default": 0,
+ "item_title": "Foo2 1 3",
+ "item_description": "Foo bar"
+ }
+ ]
+ }
+ }]
+ }
+ stats_data = \
+ { module_name : { 'foo':'bar',
+ 'foo2': [
+ {
+ "foo2-1-1" : "bar1",
+ "foo2-1-2" : 10,
+ "foo2-1-3" : 9
+ },
+ {
+ "foo2-1-1" : "bar2",
+ "foo2-1-2" : 8,
+ "foo2-1-3" : 7
+ }
+ ] } }
+ self.stats_httpd.get_stats_spec = lambda x,y: stats_spec
+ self.stats_httpd.get_stats_data = lambda x,y: stats_data
+ xml_string = self.stats_httpd.xml_handler()
+ stats_xml = xml.etree.ElementTree.fromstring(xml_string)
+ schema_loc = '{%s}schemaLocation' % XMLNS_XSI
+ self.assertEqual(stats_xml.attrib[schema_loc],
+ stats_httpd.XML_ROOT_ATTRIB['xsi:schemaLocation'])
+ stats_data = stats_data[module_name]
+ stats_spec = stats_spec[module_name]
+ names = stats_httpd.item_name_list(stats_data, '')
+ for i in range(0, len(names)):
+ self.assertEqual('%s/%s' % (module_name, names[i]), stats_xml[i].attrib['identifier'])
+ value = isc.cc.data.find(stats_data, names[i])
+ if type(value) is int:
+ value = str(value)
+ if type(value) is dict or type(value) is list:
+ self.assertFalse('value' in stats_xml[i].attrib)
+ else:
+ self.assertEqual(value, stats_xml[i].attrib['value'])
+ self.assertEqual(module_name, stats_xml[i].attrib['owner'])
+ self.assertEqual(urllib.parse.quote('%s/%s/%s' % (stats_httpd.XML_URL_PATH,
+ module_name, names[i])),
+ stats_xml[i].attrib['uri'])
+ spec = isc.config.find_spec_part(stats_spec, names[i])
+ self.assertEqual(spec['item_name'], stats_xml[i].attrib['name'])
+ self.assertEqual(spec['item_type'], stats_xml[i].attrib['type'])
+ self.assertEqual(spec['item_description'], stats_xml[i].attrib['description'])
+ self.assertEqual(spec['item_title'], stats_xml[i].attrib['title'])
+ self.assertEqual(str(spec['item_optional']).lower(), stats_xml[i].attrib['optional'])
+ default = spec['item_default']
+ if type(default) is int:
+ default = str(default)
+ if type(default) is dict or type(default) is list:
+ self.assertFalse('default' in stats_xml[i].attrib)
+ else:
+ self.assertEqual(default, stats_xml[i].attrib['default'])
+ self.assertFalse('item_format' in spec)
+ self.assertFalse('format' in stats_xml[i].attrib)
+
+ @unittest.skipUnless(xml_parser, "skipping the test using XMLParser")
+ def test_xsd_handler(self):
+ self.stats_httpd = MyStatsHttpd(get_availaddr())
+ xsd_string = self.stats_httpd.xsd_handler()
+ stats_xsd = xml.etree.ElementTree.fromstring(xsd_string)
+ ns = '{%s}' % XMLNS_XSD
+ stats_xsd = stats_xsd[1].find('%scomplexType/%ssequence/%selement' % ((ns,)*3))
+ self.assertEqual('item', stats_xsd.attrib['name'])
+ stats_xsd = stats_xsd.find('%scomplexType' % ns)
+ type_types = ('boolean', 'integer', 'real', 'string', 'map', \
+ 'list', 'named_set', 'any')
+ attribs = [('identifier', 'string', 'required'),
+ ('value', 'string', 'optional'),
+ ('owner', 'string', 'required'),
+ ('uri', 'anyURI', 'required'),
+ ('name', 'string', 'required'),
+ ('type', type_types, 'required'),
+ ('description', 'string', 'optional'),
+ ('title', 'string', 'optional'),
+ ('optional', 'boolean', 'optional'),
+ ('default', 'string', 'optional'),
+ ('format', 'string', 'optional')]
+ for i in range(0, len(attribs)):
+ self.assertEqual(attribs[i][0], stats_xsd[i].attrib['name'])
+ if attribs[i][0] == 'type':
+ stats_xsd_types = \
+ stats_xsd[i].find('%ssimpleType/%srestriction' % \
+ ((ns,)*2))
+ for j in range(0, len(attribs[i][1])):
+ self.assertEqual(attribs[i][1][j], \
+ stats_xsd_types[j].attrib['value'])
+ else:
+ self.assertEqual(attribs[i][1], stats_xsd[i].attrib['type'])
+ self.assertEqual(attribs[i][2], stats_xsd[i].attrib['use'])
+
+ @unittest.skipUnless(xml_parser, "skipping the test using XMLParser")
+ def test_xsl_handler(self):
+ self.stats_httpd = MyStatsHttpd(get_availaddr())
+ xsl_string = self.stats_httpd.xsl_handler()
+ stats_xsl = xml.etree.ElementTree.fromstring(xsl_string)
+ nst = '{%s}' % XMLNS_XSL
+ nsx = '{%s}' % XMLNS_XHTML
+ self.assertEqual("bind10:statistics", stats_xsl[2].attrib['match'])
+ stats_xsl = stats_xsl[2].find('%stable' % nsx)
+ self.assertEqual('item', stats_xsl[1].attrib['select'])
+ stats_xsl = stats_xsl[1].find('%str' % nsx)
+ self.assertEqual('@uri', stats_xsl[0].find(
+ '%selement/%sattribute/%svalue-of' % ((nst,)*3)).attrib['select'])
+ self.assertEqual('@identifier', stats_xsl[0].find(
+ '%selement/%svalue-of' % ((nst,)*2)).attrib['select'])
+ self.assertEqual('@value', stats_xsl[1].find('%sif' % nst).attrib['test'])
+ self.assertEqual('@value', stats_xsl[1].find('%sif/%svalue-of' % ((nst,)*2)).attrib['select'])
+ self.assertEqual('@description', stats_xsl[2].find('%sif' % nst).attrib['test'])
+ self.assertEqual('@description', stats_xsl[2].find('%sif/%svalue-of' % ((nst,)*2)).attrib['select'])
+
+class Z_TestOSEnv(unittest.TestCase):
+ def test_for_without_B10_FROM_SOURCE(self):
+ # Note: this test is sensitive due to its substantial side effect of
+ # reloading. For exmaple, it affects tests that tweak module
+ # attributes (such as test_init_hterr). It also breaks logging
+ # setting for unit tests. To minimize these effects, we use
+ # workaround: make it very likely to run at the end of the tests
+ # by naming the test class "Z_".
+
+ # just lets it go through the code without B10_FROM_SOURCE env
+ # variable
+ if "B10_FROM_SOURCE" in os.environ:
+ tmppath = os.environ["B10_FROM_SOURCE"]
+ os.environ.pop("B10_FROM_SOURCE")
+ imp.reload(stats_httpd)
+ os.environ["B10_FROM_SOURCE"] = tmppath
+ imp.reload(stats_httpd)
+
+if __name__ == "__main__":
+ isc.log.resetUnitTestRootLogger()
+ unittest.main()
diff --git a/src/bin/stats/tests/stats_test.py b/src/bin/stats/tests/stats_test.py
new file mode 100644
index 0000000..e010c97
--- /dev/null
+++ b/src/bin/stats/tests/stats_test.py
@@ -0,0 +1,1431 @@
+# Copyright (C) 2010, 2011, 2012 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.
+
+"""
+In each of these tests we start several virtual components. They are
+not the real components, no external processes are started. They are
+just simple mock objects running each in its own thread and pretending
+to be bind10 modules. This helps testing the stats module in a close
+to real environment.
+"""
+
+import unittest
+import os
+import io
+import time
+import imp
+import sys
+
+import stats
+import isc.log
+from test_utils import MyStats
+
+class TestUtilties(unittest.TestCase):
+ items = [
+ { 'item_name': 'test_int1', 'item_type': 'integer', 'item_default': 12345 },
+ { 'item_name': 'test_real1', 'item_type': 'real', 'item_default': 12345.6789 },
+ { 'item_name': 'test_bool1', 'item_type': 'boolean', 'item_default': True },
+ { 'item_name': 'test_str1', 'item_type': 'string', 'item_default': 'ABCD' },
+ { 'item_name': 'test_list1', 'item_type': 'list', 'item_default': [1,2,3],
+ 'list_item_spec' : { 'item_name': 'number', 'item_type': 'integer' } },
+ { 'item_name': 'test_map1', 'item_type': 'map', 'item_default': {'a':1,'b':2,'c':3},
+ 'map_item_spec' : [ { 'item_name': 'a', 'item_type': 'integer'},
+ { 'item_name': 'b', 'item_type': 'integer'},
+ { 'item_name': 'c', 'item_type': 'integer'} ] },
+ { 'item_name': 'test_int2', 'item_type': 'integer' },
+ { 'item_name': 'test_real2', 'item_type': 'real' },
+ { 'item_name': 'test_bool2', 'item_type': 'boolean' },
+ { 'item_name': 'test_str2', 'item_type': 'string' },
+ { 'item_name': 'test_list2', 'item_type': 'list',
+ 'list_item_spec' : { 'item_name': 'number', 'item_type': 'integer' } },
+ { 'item_name': 'test_map2', 'item_type': 'map',
+ 'map_item_spec' : [ { 'item_name': 'A', 'item_type': 'integer'},
+ { 'item_name': 'B', 'item_type': 'integer'},
+ { 'item_name': 'C', 'item_type': 'integer'} ] },
+ { 'item_name': 'test_none', 'item_type': 'none' },
+ { 'item_name': 'test_list3', 'item_type': 'list', 'item_default': ["one","two","three"],
+ 'list_item_spec' : { 'item_name': 'number', 'item_type': 'string' } },
+ { 'item_name': 'test_map3', 'item_type': 'map', 'item_default': {'a':'one','b':'two','c':'three'},
+ 'map_item_spec' : [ { 'item_name': 'a', 'item_type': 'string'},
+ { 'item_name': 'b', 'item_type': 'string'},
+ { 'item_name': 'c', 'item_type': 'string'} ] },
+ {
+ 'item_name': 'test_named_set',
+ 'item_type': 'named_set',
+ 'item_default': { },
+ 'named_set_item_spec': {
+ 'item_name': 'name',
+ 'item_type': 'map',
+ 'item_default': { },
+ 'map_item_spec': [
+ {
+ 'item_name': 'number1',
+ 'item_type': 'integer'
+ },
+ {
+ 'item_name': 'number2',
+ 'item_type': 'integer'
+ }
+ ]
+ }
+ }
+ ]
+
+ def setUp(self):
+ self.const_timestamp = 1308730448.965706
+ self.const_timetuple = (2011, 6, 22, 8, 14, 8, 2, 173, 0)
+ self.const_datetime = '2011-06-22T08:14:08Z'
+ self.__orig_time = stats.time
+ self.__orig_gmtime = stats.gmtime
+ stats.time = lambda : self.const_timestamp
+ stats.gmtime = lambda : self.const_timetuple
+
+ def tearDown(self):
+ stats.time = self.__orig_time
+ stats.gmtime = self.__orig_gmtime
+
+ def test_get_spec_defaults(self):
+ self.assertEqual(
+ stats.get_spec_defaults(self.items), {
+ 'test_int1' : 12345 ,
+ 'test_real1' : 12345.6789 ,
+ 'test_bool1' : True ,
+ 'test_str1' : 'ABCD' ,
+ 'test_list1' : [1,2,3] ,
+ 'test_map1' : {'a':1,'b':2,'c':3},
+ 'test_int2' : 0 ,
+ 'test_real2' : 0.0,
+ 'test_bool2' : False,
+ 'test_str2' : "",
+ 'test_list2' : [0],
+ 'test_map2' : { 'A' : 0, 'B' : 0, 'C' : 0 },
+ 'test_none' : None,
+ 'test_list3' : [ "one", "two", "three" ],
+ 'test_map3' : { 'a' : 'one', 'b' : 'two', 'c' : 'three' },
+ 'test_named_set' : {} })
+ self.assertEqual(stats.get_spec_defaults(None), {})
+ self.assertRaises(KeyError, stats.get_spec_defaults, [{'item_name':'Foo'}])
+
+ def test_get_timestamp(self):
+ self.assertEqual(stats.get_timestamp(), self.const_timestamp)
+
+ def test_get_datetime(self):
+ self.assertEqual(stats.get_datetime(), self.const_datetime)
+ self.assertNotEqual(stats.get_datetime(
+ (2011, 6, 22, 8, 23, 40, 2, 173, 0)), self.const_datetime)
+
+ def test__accum(self):
+ self.assertEqual(stats._accum(None, None), None)
+ self.assertEqual(stats._accum(None, "b"), "b")
+ self.assertEqual(stats._accum("a", None), "a")
+ self.assertEqual(stats._accum(1, 2), 3)
+ self.assertEqual(stats._accum(0.5, 0.3), 0.8)
+ self.assertEqual(stats._accum('aa','bb'), 'bb')
+ self.assertEqual(stats._accum('1970-01-01T09:00:00Z','2012-08-09T09:33:31Z'),
+ '2012-08-09T09:33:31Z')
+ self.assertEqual(stats._accum(
+ [1, 2, 3], [4, 5]), [5, 7, 3])
+ self.assertEqual(stats._accum(
+ [4, 5], [1, 2, 3]), [5, 7, 3])
+ self.assertEqual(stats._accum(
+ [1, 2, 3], [None, 5, 6]), [1, 7, 9])
+ self.assertEqual(stats._accum(
+ [None, 5, 6], [1, 2, 3]), [1, 7, 9])
+ self.assertEqual(stats._accum(
+ [1, 2, 3], [None, None, None, None]), [1,2,3,None])
+ self.assertEqual(stats._accum(
+ [[1,2],3],[[],5,6]), [[1,2],8,6])
+ self.assertEqual(stats._accum(
+ {'one': 1, 'two': 2, 'three': 3},
+ {'one': 4, 'two': 5}),
+ {'one': 5, 'two': 7, 'three': 3})
+ self.assertEqual(stats._accum(
+ {'one': 1, 'two': 2, 'three': 3},
+ {'four': 4, 'five': 5}),
+ {'one': 1, 'two': 2, 'three': 3, 'four': 4, 'five': 5})
+ self.assertEqual(stats._accum(
+ {'one': [1, 2], 'two': [3, None, 5], 'three': [None, 3, None]},
+ {'one': [2], 'two': [4, 5], 'three': [None, None, None], 'four': 'FOUR'}),
+ {'one':[3,2], 'two':[7,5,5], 'three':[None,3,None], 'four': 'FOUR'})
+ self.assertEqual(stats._accum(
+ [ {'one': 1, 'two': 2, 'three': 3}, {'four': 4, 'five': 5, 'six': 6} ],
+ [ {}, {'four': 1, 'five': 2, 'six': 3} ]),
+ [ {'one': 1, 'two': 2, 'three': 3}, {'four': 5, 'five': 7, 'six': 9} ])
+
+ def test_merge_oldnre(self):
+ self.assertEqual(stats.merge_oldnew(1, 2), 2)
+ self.assertEqual(stats.merge_oldnew(0.5, 0.3), 0.3)
+ self.assertEqual(stats.merge_oldnew('aa','bb'), 'bb')
+ self.assertEqual(stats.merge_oldnew(
+ [1, 2, 3], [4, 5]), [4, 5, 3])
+ self.assertEqual(stats.merge_oldnew(
+ [4, 5], [1, 2, 3]), [1, 2, 3])
+ self.assertEqual(stats.merge_oldnew(
+ [1, 2, 3], [None, 5, 6]), [None, 5, 6])
+ self.assertEqual(stats.merge_oldnew(
+ [None, 5, 6], [1, 2, 3]), [1, 2, 3])
+ self.assertEqual(stats.merge_oldnew(
+ [1, 2, 3], [None, None, None, None]), [None, None, None, None])
+ self.assertEqual(stats.merge_oldnew(
+ [[1,2],3],[[],5,6]), [[1,2],5,6])
+ self.assertEqual(stats.merge_oldnew(
+ {'one': 1, 'two': 2, 'three': 3},
+ {'one': 4, 'two': 5}),
+ {'one': 4, 'two': 5, 'three': 3})
+ self.assertEqual(stats.merge_oldnew(
+ {'one': 1, 'two': 2, 'three': 3},
+ {'four': 4, 'five': 5}),
+ {'one': 1, 'two': 2, 'three': 3, 'four': 4, 'five': 5})
+ self.assertEqual(stats.merge_oldnew(
+ {'one': [1, 2], 'two': [3, None, 5], 'three': [None, 3, None]},
+ {'one': [2], 'two': [4, 5], 'three': [None, None, None], 'four': 'FOUR'}),
+ {'one':[2,2], 'two':[4,5,5], 'three':[None,None,None], 'four': 'FOUR'})
+ self.assertEqual(stats.merge_oldnew(
+ [ {'one': 1, 'two': 2, 'three': 3}, {'four': 4, 'five': 5, 'six': 6} ],
+ [ {}, {'four': 1, 'five': 2, 'six': 3} ]),
+ [ {'one': 1, 'two': 2, 'three': 3}, {'four': 1, 'five': 2, 'six': 3} ])
+
+class TestCallback(unittest.TestCase):
+ def setUp(self):
+ self.dummy_func = lambda *x, **y : (x, y)
+ self.dummy_args = (1,2,3)
+ self.dummy_kwargs = {'a':1,'b':2,'c':3}
+ self.cback1 = stats.Callback(
+ command=self.dummy_func,
+ args=self.dummy_args,
+ kwargs=self.dummy_kwargs
+ )
+ self.cback2 = stats.Callback(
+ args=self.dummy_args,
+ kwargs=self.dummy_kwargs
+ )
+ self.cback3 = stats.Callback(
+ command=self.dummy_func,
+ kwargs=self.dummy_kwargs
+ )
+ self.cback4 = stats.Callback(
+ command=self.dummy_func,
+ args=self.dummy_args
+ )
+
+ def test_init(self):
+ self.assertEqual((self.cback1.command, self.cback1.args, self.cback1.kwargs),
+ (self.dummy_func, self.dummy_args, self.dummy_kwargs))
+ self.assertEqual((self.cback2.command, self.cback2.args, self.cback2.kwargs),
+ (None, self.dummy_args, self.dummy_kwargs))
+ self.assertEqual((self.cback3.command, self.cback3.args, self.cback3.kwargs),
+ (self.dummy_func, (), self.dummy_kwargs))
+ self.assertEqual((self.cback4.command, self.cback4.args, self.cback4.kwargs),
+ (self.dummy_func, self.dummy_args, {}))
+
+ def test_call(self):
+ self.assertEqual(self.cback1(), (self.dummy_args, self.dummy_kwargs))
+ self.assertEqual(self.cback1(100, 200), ((100, 200), self.dummy_kwargs))
+ self.assertEqual(self.cback1(a=100, b=200), (self.dummy_args, {'a':100, 'b':200}))
+ self.assertEqual(self.cback2(), None)
+ self.assertEqual(self.cback3(), ((), self.dummy_kwargs))
+ self.assertEqual(self.cback3(100, 200), ((100, 200), self.dummy_kwargs))
+ self.assertEqual(self.cback3(a=100, b=200), ((), {'a':100, 'b':200}))
+ self.assertEqual(self.cback4(), (self.dummy_args, {}))
+ self.assertEqual(self.cback4(100, 200), ((100, 200), {}))
+ self.assertEqual(self.cback4(a=100, b=200), (self.dummy_args, {'a':100, 'b':200}))
+
+class TestStats(unittest.TestCase):
+ def setUp(self):
+ # set the signal handler for deadlock
+ self.const_timestamp = 1308730448.965706
+ self.const_datetime = '2011-06-22T08:14:08Z'
+ self.const_default_datetime = '1970-01-01T00:00:00Z'
+ # Record original module-defined functions in case we replace them
+ self.__orig_timestamp = stats.get_timestamp
+ self.__orig_get_datetime = stats.get_datetime
+
+ def tearDown(self):
+ # restore the stored original function in case we replaced them
+ stats.get_timestamp = self.__orig_timestamp
+ stats.get_datetime = self.__orig_get_datetime
+
+ def test_init(self):
+ self.stats = MyStats()
+ self.assertEqual(self.stats.module_name, 'Stats')
+ self.assertFalse(self.stats.running)
+ self.assertTrue('command_show' in self.stats.callbacks)
+ self.assertTrue('command_status' in self.stats.callbacks)
+ self.assertTrue('command_shutdown' in self.stats.callbacks)
+ self.assertTrue('command_show' in self.stats.callbacks)
+ self.assertTrue('command_showschema' in self.stats.callbacks)
+ self.assertEqual(self.stats.config['poll-interval'], 60)
+
+ def test_init_undefcmd(self):
+ spec_str = """\
+{
+ "module_spec": {
+ "module_name": "Stats",
+ "module_description": "Stats daemon",
+ "config_data": [],
+ "commands": [
+ {
+ "command_name": "_undef_command_",
+ "command_description": "a undefined command in stats",
+ "command_args": []
+ }
+ ],
+ "statistics": []
+ }
+}
+"""
+ orig_spec_location = stats.SPECFILE_LOCATION
+ stats.SPECFILE_LOCATION = io.StringIO(spec_str)
+ self.assertRaises(stats.StatsError, MyStats)
+ stats.SPECFILE_LOCATION = orig_spec_location
+
+ def __send_command(self, stats, command_name, params=None):
+ '''Emulate a command arriving to stats by directly calling callback'''
+ return isc.config.ccsession.parse_answer(
+ stats.command_handler(command_name, params))
+
+ def test_start(self):
+ # Define a separate exception class so we can be sure that's actually
+ # the one raised in __check_start() below
+ class CheckException(Exception):
+ pass
+
+ def __check_start(tested_stats):
+ self.assertTrue(tested_stats.running)
+ raise CheckException # terminate the loop
+
+ # start without err
+ self.stats = MyStats()
+ self.assertFalse(self.stats.running)
+ self.stats._check_command = lambda: __check_start(self.stats)
+ # We are going to confirm start() will set running to True, avoiding
+ # to fall into a loop with the exception trick.
+ self.assertRaises(CheckException, self.stats.start)
+ self.assertEqual(self.__send_command(self.stats, "status"),
+ (0, "Stats is up. (PID " + str(os.getpid()) + ")"))
+
+ def test_shutdown(self):
+ def __check_shutdown(tested_stats):
+ self.assertTrue(tested_stats.running)
+ self.assertEqual(self.__send_command(tested_stats, "shutdown"),
+ (0, None))
+ self.assertFalse(tested_stats.running)
+ # override get_interval() so it won't go poll statistics
+ tested_stats.get_interval = lambda : 0
+
+ self.stats = MyStats()
+ self.stats._check_command = lambda: __check_shutdown(self.stats)
+ self.stats.start()
+ self.assertTrue(self.stats.mccs.stopped)
+
+ def test_handlers(self):
+ """Test command_handler"""
+
+ __stats = MyStats()
+
+ # 'show' command. We're going to check the expected methods are
+ # called in the expected order, and check the resulting response.
+ # Details of each method are tested separately.
+ call_log = []
+ def __steal_method(fn_name, *arg):
+ call_log.append((fn_name, arg))
+ if fn_name == 'update_stat':
+ return False # "no error"
+ if fn_name == 'showschema':
+ return isc.config.create_answer(0, 'no error')
+
+ # Fake some methods and attributes for inspection
+ __stats.do_polling = lambda: __steal_method('polling')
+ __stats.update_statistics_data = \
+ lambda x, y, z: __steal_method('update_stat', x, y, z)
+ __stats.update_modules = lambda: __steal_method('update_module')
+ __stats.mccs.lname = 'test lname'
+ __stats.statistics_data = {'Init': {'boot_time': self.const_datetime}}
+
+ # skip initial polling
+ stats.get_timestamp = lambda: 0
+ __stats._lasttime_poll = 0
+
+ stats.get_datetime = lambda: 42 # make the result predictable
+
+ # now send the command
+ self.assertEqual(
+ self.__send_command(
+ __stats, 'show',
+ params={ 'owner' : 'Init', 'name' : 'boot_time' }),
+ (0, {'Init': {'boot_time': self.const_datetime}}))
+ # Check if expected methods are called
+ self.assertEqual([('update_stat',
+ ('Stats', 'test lname',
+ {'timestamp': 0,
+ 'report_time': 42})),
+ ('update_module', ())], call_log)
+
+ # Then update faked timestamp so the initial polling will happen, and
+ # confirm that.
+ call_log = []
+ stats.get_timestamp = lambda: 10
+ self.assertEqual(
+ self.__send_command(
+ __stats, 'show',
+ params={ 'owner' : 'Init', 'name' : 'boot_time' }),
+ (0, {'Init': {'boot_time': self.const_datetime}}))
+ self.assertEqual([('polling', ()),
+ ('update_stat',
+ ('Stats', 'test lname',
+ {'timestamp': 10,
+ 'report_time': 42})),
+ ('update_module', ())], call_log)
+
+ # 'status' command. We can confirm the behavior without any fake
+ self.assertEqual(
+ self.__send_command(__stats, 'status'),
+ (0, "Stats is up. (PID " + str(os.getpid()) + ")"))
+
+ # 'showschema' command. update_modules() will be called, which
+ # (implicitly) confirms the correct method is called; further details
+ # are tested separately.
+ call_log = []
+ (rcode, value) = self.__send_command(__stats, 'showschema')
+ self.assertEqual([('update_module', ())], call_log)
+
+ # Unknown command. Error should be returned
+ self.assertEqual(
+ self.__send_command(__stats, '__UNKNOWN__'),
+ (1, "Unknown command: '__UNKNOWN__'"))
+
+ def test_update_modules(self):
+ """Confirm the behavior of Stats.update_modules().
+
+ It checks whether the expected command is sent to ConfigManager,
+ and whether the answer from ConfigManager is handled as expected.
+
+ """
+
+ def __check_rpc_call(command, group):
+ self.assertEqual('ConfigManager', group)
+ self.assertEqual(command,
+ isc.config.ccsession.COMMAND_GET_STATISTICS_SPEC)
+ answer_value = {'Init': [{
+ "item_name": "boot_time",
+ "item_type": "string",
+ "item_optional": False,
+ # Use a different default so we can check it below
+ "item_default": "2013-01-01T00:00:01Z",
+ "item_title": "Boot time",
+ "item_description": "dummy desc",
+ "item_format": "date-time"
+ }]}
+ return answer_value
+
+ self.stats = MyStats()
+ self.stats.cc_session.rpc_call = __check_rpc_call
+
+ self.stats.update_modules()
+
+ # Stats is always incorporated. For others, only the ones returned
+ # by group_recvmsg() above is available.
+ self.assertTrue('Stats' in self.stats.modules)
+ self.assertTrue('Init' in self.stats.modules)
+ self.assertFalse('Dummy' in self.stats.modules)
+
+ my_statistics_data = stats.get_spec_defaults(
+ self.stats.modules['Stats'].get_statistics_spec())
+ self.assertTrue('report_time' in my_statistics_data)
+ self.assertTrue('boot_time' in my_statistics_data)
+ self.assertTrue('last_update_time' in my_statistics_data)
+ self.assertTrue('timestamp' in my_statistics_data)
+ self.assertTrue('lname' in my_statistics_data)
+ self.assertEqual(my_statistics_data['report_time'],
+ self.const_default_datetime)
+ self.assertEqual(my_statistics_data['boot_time'],
+ self.const_default_datetime)
+ self.assertEqual(my_statistics_data['last_update_time'],
+ self.const_default_datetime)
+ self.assertEqual(my_statistics_data['timestamp'], 0.0)
+ self.assertEqual(my_statistics_data['lname'], "")
+ my_statistics_data = stats.get_spec_defaults(
+ self.stats.modules['Init'].get_statistics_spec())
+ self.assertTrue('boot_time' in my_statistics_data)
+ self.assertEqual(my_statistics_data['boot_time'],
+ "2013-01-01T00:00:01Z")
+
+ # Error case
+ def __raise_on_rpc_call(x, y):
+ raise isc.config.RPCError(99, 'error')
+ orig_parse_answer = stats.isc.config.ccsession.parse_answer
+ self.stats.cc_session.rpc_call = __raise_on_rpc_call
+ self.assertRaises(stats.StatsError, self.stats.update_modules)
+
+ def test_get_statistics_data(self):
+ """Confirm the behavior of Stats.get_statistics_data().
+
+ It should first call update_modules(), and then retrieve the requested
+ data from statistics_data. We confirm this by fake update_modules()
+ where we set the expected data in statistics_data.
+
+ """
+ self.stats = MyStats()
+ def __faked_update_modules():
+ self.stats.statistics_data = { \
+ 'Stats': {
+ 'report_time': self.const_default_datetime,
+ 'boot_time': None,
+ 'last_update_time': None,
+ 'timestamp': 0.0,
+ 'lname': 'dummy name'
+ },
+ 'Init': { 'boot_time': None }
+ }
+
+ self.stats.update_modules = __faked_update_modules
+
+ my_statistics_data = self.stats.get_statistics_data()
+ self.assertTrue('Stats' in my_statistics_data)
+ self.assertTrue('Init' in my_statistics_data)
+ self.assertTrue('boot_time' in my_statistics_data['Init'])
+
+ my_statistics_data = self.stats.get_statistics_data(owner='Stats')
+ self.assertTrue('Stats' in my_statistics_data)
+ self.assertTrue('report_time' in my_statistics_data['Stats'])
+ self.assertTrue('boot_time' in my_statistics_data['Stats'])
+ self.assertTrue('last_update_time' in my_statistics_data['Stats'])
+ self.assertTrue('timestamp' in my_statistics_data['Stats'])
+ self.assertTrue('lname' in my_statistics_data['Stats'])
+ self.assertRaises(stats.StatsError, self.stats.get_statistics_data,
+ owner='Foo')
+
+ my_statistics_data = self.stats.get_statistics_data(
+ owner='Stats', name='report_time')
+ self.assertEqual(my_statistics_data['Stats']['report_time'],
+ self.const_default_datetime)
+
+ my_statistics_data = self.stats.get_statistics_data(
+ owner='Stats', name='boot_time')
+ self.assertTrue('boot_time' in my_statistics_data['Stats'])
+
+ my_statistics_data = self.stats.get_statistics_data(
+ owner='Stats', name='last_update_time')
+ self.assertTrue('last_update_time' in my_statistics_data['Stats'])
+
+ my_statistics_data = self.stats.get_statistics_data(
+ owner='Stats', name='timestamp')
+ self.assertEqual(my_statistics_data['Stats']['timestamp'], 0.0)
+
+ my_statistics_data = self.stats.get_statistics_data(
+ owner='Stats', name='lname')
+ self.assertTrue(len(my_statistics_data['Stats']['lname']) >0)
+ self.assertRaises(stats.StatsError, self.stats.get_statistics_data,
+ owner='Stats', name='Bar')
+ self.assertRaises(stats.StatsError, self.stats.get_statistics_data,
+ owner='Foo', name='Bar')
+ self.assertRaises(stats.StatsError, self.stats.get_statistics_data,
+ name='Bar')
+
+ def test_update_statistics_data(self):
+ """test for list-type statistics"""
+ self.stats = MyStats()
+ _test_exp1 = {
+ 'zonename': 'test1.example',
+ 'queries.tcp': 5,
+ 'queries.udp': 4
+ }
+ _test_exp2 = {
+ 'zonename': 'test2.example',
+ 'queries.tcp': 3,
+ 'queries.udp': 2
+ }
+ _test_exp3 = {}
+ _test_exp4 = {
+ 'queries.udp': 4
+ }
+ _test_exp5_1 = {
+ 'queries.perzone': [
+ { },
+ {
+ 'queries.udp': 9876
+ }
+ ]
+ }
+ _test_exp5_2 = {
+ 'queries.perzone[1]/queries.udp':
+ isc.cc.data.find(_test_exp5_1,
+ 'queries.perzone[1]/queries.udp')
+ }
+ # Success cases
+ self.assertEqual(self.stats.statistics_data['Stats']['lname'],
+ self.stats.cc_session.lname)
+ self.stats.update_statistics_data(
+ 'Stats', self.stats.cc_session.lname,
+ {'lname': 'foo at bar'})
+ self.assertEqual(self.stats.statistics_data['Stats']['lname'],
+ 'foo at bar')
+ self.assertIsNone(self.stats.update_statistics_data(
+ 'Auth', 'foo1', {'queries.perzone': [_test_exp1]}))
+ self.assertEqual(self.stats.statistics_data_bymid['Auth']\
+ ['foo1']['queries.perzone'],\
+ [_test_exp1])
+ self.assertIsNone(self.stats.update_statistics_data(
+ 'Auth', 'foo1', {'queries.perzone': [_test_exp2]}))
+ self.assertEqual(self.stats.statistics_data_bymid['Auth']\
+ ['foo1']['queries.perzone'],\
+ [_test_exp2])
+ self.assertIsNone(self.stats.update_statistics_data(
+ 'Auth', 'foo1', {'queries.perzone': [_test_exp1,_test_exp2]}))
+ self.assertEqual(self.stats.statistics_data_bymid['Auth']\
+ ['foo1']['queries.perzone'],
+ [_test_exp1,_test_exp2])
+ # differential update
+ self.assertIsNone(self.stats.update_statistics_data(
+ 'Auth', 'foo1', {'queries.perzone': [_test_exp3,_test_exp4]}))
+ _new_data = stats.merge_oldnew(_test_exp2,_test_exp4)
+ self.assertEqual(self.stats.statistics_data_bymid['Auth']\
+ ['foo1']['queries.perzone'], \
+ [_test_exp1,_new_data])
+ self.assertIsNone(self.stats.update_statistics_data(
+ 'Auth', 'foo1', _test_exp5_2))
+ _new_data = stats.merge_oldnew(_new_data,
+ _test_exp5_1['queries.perzone'][1])
+ self.assertEqual(self.stats.statistics_data_bymid['Auth']\
+ ['foo1']['queries.perzone'], \
+ [_test_exp1,_new_data])
+ # Error cases
+ self.assertEqual(self.stats.update_statistics_data('Stats', None,
+ {'lname': 0.0}),
+ ['0.0 should be a string'])
+ self.assertEqual(self.stats.update_statistics_data('Dummy', None,
+ {'foo': 'bar'}),
+ ['unknown module name: Dummy'])
+ self.assertEqual(self.stats.update_statistics_data(
+ 'Auth', 'foo1', {'queries.perzone': [None]}), ['None should be a map'])
+
+ def test_update_statistics_data_pt2(self):
+ """test for named_set-type statistics"""
+ self.stats = MyStats()
+ _test_exp1 = \
+ { 'test10.example': { 'queries.tcp': 5, 'queries.udp': 4 } }
+ _test_exp2 = \
+ { 'test20.example': { 'queries.tcp': 3, 'queries.udp': 2 } }
+ _test_exp3 = {}
+ _test_exp4 = { 'test20.example': { 'queries.udp': 4 } }
+ _test_exp5_1 = { 'test10.example': { 'queries.udp': 5432 } }
+ _test_exp5_2 ={
+ 'nds_queries.perzone/test10.example/queries.udp':
+ isc.cc.data.find(_test_exp5_1, 'test10.example/queries.udp')
+ }
+ _test_exp6 = { 'foo/bar': 'brabra' }
+ _test_exp7 = { 'foo[100]': 'bar' }
+ # Success cases
+ self.assertIsNone(self.stats.update_statistics_data(
+ 'Auth', 'foo1', {'nds_queries.perzone': _test_exp1}))
+ self.assertEqual(self.stats.statistics_data_bymid['Auth']\
+ ['foo1']['nds_queries.perzone'],\
+ _test_exp1)
+ self.assertIsNone(self.stats.update_statistics_data(
+ 'Auth', 'foo1', {'nds_queries.perzone': _test_exp2}))
+ self.assertEqual(self.stats.statistics_data_bymid['Auth']\
+ ['foo1']['nds_queries.perzone'],\
+ dict(_test_exp1,**_test_exp2))
+ self.assertIsNone(self.stats.update_statistics_data(
+ 'Auth', 'foo1', {'nds_queries.perzone':
+ dict(_test_exp1, **_test_exp2)}))
+ self.assertEqual(self.stats.statistics_data_bymid['Auth']\
+ ['foo1']['nds_queries.perzone'],
+ dict(_test_exp1, **_test_exp2))
+ # differential update
+ self.assertIsNone(self.stats.update_statistics_data(
+ 'Auth', 'foo1', {'nds_queries.perzone':
+ dict(_test_exp3, **_test_exp4)}))
+ _new_val = dict(_test_exp1,
+ **stats.merge_oldnew(_test_exp2,_test_exp4))
+ self.assertEqual(self.stats.statistics_data_bymid['Auth']\
+ ['foo1']['nds_queries.perzone'],\
+ _new_val)
+ self.assertIsNone(self.stats.update_statistics_data(
+ 'Auth', 'foo1', _test_exp5_2))
+ _new_val = stats.merge_oldnew(_new_val, _test_exp5_1)
+ self.assertEqual(self.stats.statistics_data_bymid['Auth']\
+ ['foo1']['nds_queries.perzone'],\
+ _new_val)
+ self.assertIsNone(self.stats.update_statistics_data(
+ 'Auth', 'foo2', _test_exp5_2))
+ _new_val = stats.merge_oldnew(_new_val, _test_exp5_1)
+ self.assertEqual(self.stats.statistics_data_bymid['Auth']\
+ ['foo2']['nds_queries.perzone'],\
+ _test_exp5_1)
+ # Error cases
+ self.assertEqual(self.stats.update_statistics_data(
+ 'Auth', 'foo1', {'nds_queries.perzone': None}),
+ ['None should be a map'])
+ self.assertEqual(self.stats.statistics_data_bymid['Auth']\
+ ['foo1']['nds_queries.perzone'],\
+ _new_val)
+ self.assertEqual(self.stats.update_statistics_data(
+ 'Auth', 'foo1', _test_exp6), ['unknown item foo'])
+ self.assertEqual(self.stats.statistics_data_bymid['Auth']\
+ ['foo1']['nds_queries.perzone'],\
+ _new_val)
+ self.assertEqual(self.stats.update_statistics_data(
+ 'Init', 'bar1', _test_exp7), ["KeyError: 'foo'"])
+ self.assertEqual(self.stats.update_statistics_data(
+ 'Foo', 'foo1', _test_exp6), ['unknown module name: Foo'])
+
+ def test_update_statistics_data_withmid(self):
+ self.stats = MyStats()
+
+ # This test relies on existing statistics data at the Stats object.
+ # This version of test prepares the data using the do_polling() method;
+ # that's a bad practice because a unittest for a method
+ # (update_statistics_data) would heavily depend on details of another
+ # method (do_polling). However, there's currently no direct test
+ # for do_polling (which is also bad), so we still keep that approach,
+ # partly for testing do_polling indirectly. #2781 should provide
+ # direct test for do_polling, with which this test scenario should
+ # also be changed to be more stand-alone.
+
+ # We use the knowledge of what kind of messages are sent via
+ # do_polling, and return the following faked answer directly.
+ create_answer = isc.config.ccsession.create_answer # shortcut
+ self.stats._answers = [
+ # Answer for "show_processes"
+ (create_answer(0, [[1034, 'b10-auth-1', 'Auth'],
+ [1035, 'b10-auth-2', 'Auth']]), None),
+ # Answers for "getstats". 2 for Auth instances and 1 for Init.
+ # we return some bogus values for Init, but the rest of the test
+ # doesn't need it, so it's okay.
+ (create_answer(0, self.stats._auth_sdata), {'from': 'auth1'}),
+ (create_answer(0, self.stats._auth_sdata), {'from': 'auth2'}),
+ (create_answer(0, self.stats._auth_sdata), {'from': 'auth3'})
+ ]
+ # do_polling calls update_modules internally; in our scenario there's
+ # no change in modules, so we make it no-op.
+ self.stats.update_modules = lambda: None
+ # Now call do_polling.
+ self.stats.do_polling()
+
+ # samples of query number
+ bar1_tcp = 1001
+ bar2_tcp = 2001
+ bar3_tcp = 1002
+ bar3_udp = 1003
+ # two auth instances invoked, so we double the pre-set stat values
+ sum_qtcp = self.stats._queries_tcp * 2
+ sum_qudp = self.stats._queries_udp * 2
+ self.stats.update_statistics_data('Auth', "bar1 at foo",
+ {'queries.tcp': bar1_tcp})
+ self.assertTrue('Auth' in self.stats.statistics_data)
+ self.assertTrue('queries.tcp' in self.stats.statistics_data['Auth'])
+ self.assertEqual(self.stats.statistics_data['Auth']['queries.tcp'],
+ bar1_tcp + sum_qtcp)
+ self.assertTrue('Auth' in self.stats.statistics_data_bymid)
+ self.assertTrue('bar1 at foo' in self.stats.statistics_data_bymid['Auth'])
+ self.assertTrue('queries.tcp' in self.stats.statistics_data_bymid
+ ['Auth']['bar1 at foo'])
+ self.assertEqual(self.stats.statistics_data_bymid['Auth']['bar1 at foo'],
+ {'queries.tcp': bar1_tcp})
+ # check consolidation of statistics data even if there is
+ # non-existent mid of Auth
+ self.stats.update_statistics_data('Auth', "bar2 at foo",
+ {'queries.tcp': bar2_tcp})
+ self.assertTrue('Auth' in self.stats.statistics_data)
+ self.assertTrue('queries.tcp' in self.stats.statistics_data['Auth'])
+ self.assertEqual(self.stats.statistics_data['Auth']['queries.tcp'],
+ bar1_tcp + bar2_tcp + sum_qtcp)
+ self.assertTrue('Auth' in self.stats.statistics_data_bymid)
+ self.assertTrue('bar1 at foo' in self.stats.statistics_data_bymid['Auth'])
+ self.assertTrue('queries.tcp' in self.stats.statistics_data_bymid['Auth']['bar1 at foo'])
+ self.assertEqual(self.stats.statistics_data_bymid['Auth']['bar1 at foo'],
+ {'queries.tcp': bar1_tcp})
+ self.assertEqual(self.stats.statistics_data_bymid['Auth']['bar2 at foo'],
+ {'queries.tcp': bar2_tcp})
+ # kill running Auth but the statistics data doesn't change
+ self.stats.update_statistics_data()
+ self.assertTrue('Auth' in self.stats.statistics_data)
+ self.assertTrue('queries.tcp' in self.stats.statistics_data['Auth'])
+ self.assertTrue('queries.udp' in self.stats.statistics_data['Auth'])
+ self.assertEqual(self.stats.statistics_data['Auth']['queries.tcp'],
+ bar1_tcp + bar2_tcp + sum_qtcp)
+ self.assertEqual(self.stats.statistics_data['Auth']['queries.udp'],
+ sum_qudp)
+ self.assertTrue('Auth' in self.stats.statistics_data_bymid)
+ # restore statistics data of killed auth
+ self.stats.update_statistics_data('Auth',
+ "bar1 at foo",
+ {'queries.tcp': bar1_tcp})
+ # set another mid of Auth
+ self.stats.update_statistics_data('Auth',
+ "bar3 at foo",
+ {'queries.tcp':bar3_tcp,
+ 'queries.udp':bar3_udp})
+ self.assertTrue('Auth' in self.stats.statistics_data)
+ self.assertTrue('queries.tcp' in self.stats.statistics_data['Auth'])
+ self.assertTrue('queries.udp' in self.stats.statistics_data['Auth'])
+ self.assertEqual(self.stats.statistics_data['Auth']['queries.tcp'],
+ bar1_tcp + bar2_tcp + bar3_tcp + sum_qtcp)
+ self.assertEqual(self.stats.statistics_data['Auth']['queries.udp'],
+ bar3_udp + sum_qudp)
+ self.assertTrue('Auth' in self.stats.statistics_data_bymid)
+ self.assertTrue('bar1 at foo' in self.stats.statistics_data_bymid['Auth'])
+ self.assertTrue('bar3 at foo' in self.stats.statistics_data_bymid['Auth'])
+ self.assertTrue('queries.tcp' in self.stats.statistics_data_bymid['Auth']['bar1 at foo'])
+ self.assertTrue('queries.udp' in self.stats.statistics_data_bymid['Auth']['bar3 at foo'])
+ self.assertTrue('queries.udp' in self.stats.statistics_data_bymid['Auth']['bar3 at foo'])
+ self.assertEqual(self.stats.statistics_data_bymid['Auth']['bar1 at foo']['queries.tcp'], bar1_tcp)
+ self.assertEqual(self.stats.statistics_data_bymid['Auth']['bar3 at foo']['queries.tcp'], bar3_tcp)
+ self.assertEqual(self.stats.statistics_data_bymid['Auth']['bar3 at foo']['queries.udp'], bar3_udp)
+
+ def test_config(self):
+ orig_get_timestamp = stats.get_timestamp
+ stats.get_timestamp = lambda : self.const_timestamp
+ stat = MyStats()
+
+ # test updating poll-interval
+ self.assertEqual(stat.config['poll-interval'], 60)
+ self.assertEqual(stat.get_interval(), 60)
+ self.assertEqual(stat.next_polltime, self.const_timestamp + 60)
+ self.assertEqual(stat.config_handler({'poll-interval': 120}),
+ isc.config.create_answer(0))
+ self.assertEqual(stat.config['poll-interval'], 120)
+ self.assertEqual(stat.get_interval(), 120)
+ self.assertEqual(stat.next_polltime, self.const_timestamp + 120)
+ stats.get_timestamp = orig_get_timestamp
+ self.assertEqual(stat.config_handler({'poll-interval': "foo"}),
+ isc.config.create_answer(1, 'foo should be an integer'))
+ self.assertEqual(stat.config_handler({'poll-interval': -1}),
+ isc.config.create_answer(1, 'Negative integer ignored'))
+ # unknown item
+ self.assertEqual(
+ stat.config_handler({'_UNKNOWN_KEY_': None}),
+ isc.config.ccsession.create_answer(
+ 1, "unknown item _UNKNOWN_KEY_"))
+ # test no change if zero interval time
+ self.assertEqual(stat.config_handler({'poll-interval': 0}),
+ isc.config.create_answer(0))
+ self.assertEqual(stat.config['poll-interval'], 0)
+
+ # see the comment for test_update_statistics_data_withmid. We abuse
+ # do_polling here, too. With #2781 we should make it more direct.
+ create_answer = isc.config.ccsession.create_answer # shortcut
+ stat._answers = [\
+ # Answer for "show_processes"
+ (create_answer(0, []), None),
+ # Answers for "getstats" for Init (the other one for Auth, but
+ # that doesn't matter for this test)
+ (create_answer(0, stat._init_sdata), {'from': 'init'}),
+ (create_answer(0, stat._init_sdata), {'from': 'init'})
+ ]
+ stat.update_modules = lambda: None
+
+ self.assertEqual(
+ self.__send_command(
+ stat, 'show',
+ params={ 'owner' : 'Init', 'name' : 'boot_time' }),
+ (0, {'Init': {'boot_time': self.const_datetime}}))
+
+ def test_commands(self):
+ self.stats = MyStats()
+
+ # status
+ self.assertEqual(self.stats.command_status(),
+ isc.config.create_answer(
+ 0, "Stats is up. (PID " + str(os.getpid()) + ")"))
+
+ # shutdown
+ self.stats.running = True
+ self.assertEqual(self.stats.command_shutdown(),
+ isc.config.create_answer(0))
+ self.assertFalse(self.stats.running)
+
+ def test_command_show_error(self):
+ self.stats = MyStats()
+ self.assertEqual(self.stats.command_show(owner='Foo', name=None),
+ isc.config.create_answer(
+ 1,
+ "specified arguments are incorrect: owner: Foo, name: None"))
+ self.assertEqual(self.stats.command_show(owner='Foo', name='_bar_'),
+ isc.config.create_answer(
+ 1,
+ "specified arguments are incorrect: owner: Foo, name: _bar_"))
+ self.assertEqual(self.stats.command_show(owner='Foo', name='bar'),
+ isc.config.create_answer(
+ 1,
+ "specified arguments are incorrect: owner: Foo, name: bar"))
+
+ def test_command_show_auth(self):
+ self.stats = MyStats()
+ self.stats.update_modules = lambda: None
+
+ # Test data borrowed from test_update_statistics_data_withmid
+ create_answer = isc.config.ccsession.create_answer # shortcut
+ self.stats._answers = [
+ (create_answer(0, [[1034, 'b10-auth-1', 'Auth'],
+ [1035, 'b10-auth-2', 'Auth']]), None),
+ (create_answer(0, self.stats._auth_sdata), {'from': 'auth1'}),
+ (create_answer(0, self.stats._auth_sdata), {'from': 'auth2'}),
+ (create_answer(0, self.stats._auth_sdata), {'from': 'auth3'})
+ ]
+
+ num_instances = 2
+ sum_qtcp = 0
+ sum_qudp = 0
+ sum_qtcp_perzone1 = 0
+ sum_qudp_perzone1 = 0
+ sum_qtcp_perzone2 = 4 * num_instances
+ sum_qudp_perzone2 = 3 * num_instances
+ sum_qtcp_nds_perzone10 = 0
+ sum_qudp_nds_perzone10 = 0
+ sum_qtcp_nds_perzone20 = 4 * num_instances
+ sum_qudp_nds_perzone20 = 3 * num_instances
+
+ self.maxDiff = None
+ for a in (0, num_instances):
+ sum_qtcp += self.stats._queries_tcp
+ sum_qudp += self.stats._queries_udp
+ sum_qtcp_perzone1 += self.stats._queries_per_zone[0]['queries.tcp']
+ sum_qudp_perzone1 += self.stats._queries_per_zone[0]['queries.udp']
+ sum_qtcp_nds_perzone10 += \
+ self.stats._nds_queries_per_zone['test10.example']['queries.tcp']
+ sum_qudp_nds_perzone10 += \
+ self.stats._nds_queries_per_zone['test10.example']['queries.udp']
+
+ self.assertEqual(self.stats.command_show(owner='Auth'),
+ isc.config.create_answer(
+ 0, {'Auth':{ 'queries.udp': sum_qudp,
+ 'queries.tcp': sum_qtcp,
+ 'queries.perzone': [{ 'zonename': 'test1.example',
+ 'queries.udp': sum_qudp_perzone1,
+ 'queries.tcp': sum_qtcp_perzone1 },
+ { 'zonename': 'test2.example',
+ 'queries.udp': sum_qudp_perzone2,
+ 'queries.tcp': sum_qtcp_perzone2 }
+ ],
+ 'nds_queries.perzone': { 'test10.example' : {
+ 'queries.udp': sum_qudp_nds_perzone10,
+ 'queries.tcp': sum_qtcp_nds_perzone10 },
+ 'test20.example' : {
+ 'queries.udp': sum_qudp_nds_perzone20,
+ 'queries.tcp': sum_qtcp_nds_perzone20 }
+ }}}))
+ self.assertEqual(self.stats.command_show(owner='Auth', name='queries.udp'),
+ isc.config.create_answer(
+ 0, {'Auth': {'queries.udp': sum_qudp}}))
+ self.assertEqual(self.stats.command_show(owner='Auth', name='queries.perzone'),
+ isc.config.create_answer(
+ 0, {'Auth': {'queries.perzone': [
+ { 'zonename': 'test1.example',
+ 'queries.udp': sum_qudp_perzone1,
+ 'queries.tcp': sum_qtcp_perzone1 },
+ { 'zonename': 'test2.example',
+ 'queries.udp': sum_qudp_perzone2,
+ 'queries.tcp': sum_qtcp_perzone2 }]}}))
+ self.assertEqual(self.stats.command_show(owner='Auth', name='nds_queries.perzone'),
+ isc.config.create_answer(
+ 0, {'Auth': {'nds_queries.perzone': {
+ 'test10.example': {
+ 'queries.udp': sum_qudp_nds_perzone10,
+ 'queries.tcp': sum_qtcp_nds_perzone10 },
+ 'test20.example': {
+ 'queries.udp': sum_qudp_nds_perzone20,
+ 'queries.tcp': sum_qtcp_nds_perzone20 }}}}))
+
+ def test_command_show_stats(self):
+ self.stats = MyStats()
+ orig_get_datetime = stats.get_datetime
+ orig_get_timestamp = stats.get_timestamp
+ stats.get_datetime = lambda x=None: self.const_datetime
+ stats.get_timestamp = lambda : self.const_timestamp
+ self.assertEqual(self.stats.command_show(owner='Stats',
+ name='report_time'),
+ isc.config.create_answer(
+ 0, {'Stats': {'report_time':self.const_datetime}}))
+ self.assertEqual(self.stats.command_show(owner='Stats',
+ name='timestamp'),
+ isc.config.create_answer(
+ 0, {'Stats': {'timestamp':self.const_timestamp}}))
+ stats.get_datetime = orig_get_datetime
+ stats.get_timestamp = orig_get_timestamp
+ self.stats.do_polling = lambda : None
+ self.stats.modules[self.stats.module_name] = \
+ isc.config.module_spec.ModuleSpec(
+ { "module_name": self.stats.module_name, "statistics": [] } )
+ self.assertRaises(
+ stats.StatsError, self.stats.command_show,
+ owner=self.stats.module_name, name='bar')
+
+ def test_command_showchema(self):
+ self.stats = MyStats()
+ (rcode, value) = isc.config.ccsession.parse_answer(
+ self.stats.command_showschema())
+ self.assertEqual(rcode, 0)
+ self.assertEqual(len(value), 3)
+ self.assertTrue('Stats' in value)
+ self.assertTrue('Init' in value)
+ self.assertTrue('Auth' in value)
+ self.assertFalse('__Dummy__' in value)
+ schema = value['Stats']
+ self.assertEqual(len(schema), 5)
+ for item in schema:
+ self.assertTrue(len(item) == 6 or len(item) == 7)
+ self.assertTrue('item_name' in item)
+ self.assertTrue('item_type' in item)
+ self.assertTrue('item_optional' in item)
+ self.assertTrue('item_default' in item)
+ self.assertTrue('item_title' in item)
+ self.assertTrue('item_description' in item)
+ if len(item) == 7:
+ self.assertTrue('item_format' in item)
+
+ schema = value['Init']
+ self.assertEqual(len(schema), 1)
+ for item in schema:
+ self.assertTrue(len(item) == 7)
+ self.assertTrue('item_name' in item)
+ self.assertTrue('item_type' in item)
+ self.assertTrue('item_optional' in item)
+ self.assertTrue('item_default' in item)
+ self.assertTrue('item_title' in item)
+ self.assertTrue('item_description' in item)
+ self.assertTrue('item_format' in item)
+
+ schema = value['Auth']
+ self.assertEqual(len(schema), 4)
+ for item in schema:
+ if item['item_type'] == 'list' or item['item_type'] == 'named_set':
+ self.assertEqual(len(item), 7)
+ else:
+ self.assertEqual(len(item), 6)
+ self.assertTrue('item_name' in item)
+ self.assertTrue('item_type' in item)
+ self.assertTrue('item_optional' in item)
+ self.assertTrue('item_default' in item)
+ self.assertTrue('item_title' in item)
+ self.assertTrue('item_description' in item)
+
+ (rcode, value) = isc.config.ccsession.parse_answer(
+ self.stats.command_showschema(owner='Stats'))
+ self.assertEqual(rcode, 0)
+ self.assertTrue('Stats' in value)
+ self.assertFalse('Init' in value)
+ self.assertFalse('Auth' in value)
+ for item in value['Stats']:
+ self.assertTrue(len(item) == 6 or len(item) == 7)
+ self.assertTrue('item_name' in item)
+ self.assertTrue('item_type' in item)
+ self.assertTrue('item_optional' in item)
+ self.assertTrue('item_default' in item)
+ self.assertTrue('item_title' in item)
+ self.assertTrue('item_description' in item)
+ if len(item) == 7:
+ self.assertTrue('item_format' in item)
+
+ (rcode, value) = isc.config.ccsession.parse_answer(
+ self.stats.command_showschema(owner='Stats', name='report_time'))
+ self.assertEqual(rcode, 0)
+ self.assertTrue('Stats' in value)
+ self.assertFalse('Init' in value)
+ self.assertFalse('Auth' in value)
+ self.assertEqual(len(value['Stats'][0]), 7)
+ self.assertTrue('item_name' in value['Stats'][0])
+ self.assertTrue('item_type' in value['Stats'][0])
+ self.assertTrue('item_optional' in value['Stats'][0])
+ self.assertTrue('item_default' in value['Stats'][0])
+ self.assertTrue('item_title' in value['Stats'][0])
+ self.assertTrue('item_description' in value['Stats'][0])
+ self.assertTrue('item_format' in value['Stats'][0])
+ self.assertEqual(value['Stats'][0]['item_name'], 'report_time')
+ self.assertEqual(value['Stats'][0]['item_format'], 'date-time')
+
+ self.assertEqual(self.stats.command_showschema(owner='Foo'),
+ isc.config.create_answer(
+ 1, "specified arguments are incorrect: owner: Foo, name: None"))
+ self.assertEqual(self.stats.command_showschema(owner='Foo', name='bar'),
+ isc.config.create_answer(
+ 1, "specified arguments are incorrect: owner: Foo, name: bar"))
+ self.assertEqual(self.stats.command_showschema(owner='Auth'),
+ isc.config.create_answer(
+ 0, {'Auth': [{
+ "item_default": 0,
+ "item_description": "A number of total query counts which all auth servers receive over TCP since they started initially",
+ "item_name": "queries.tcp",
+ "item_optional": False,
+ "item_title": "Queries TCP",
+ "item_type": "integer"
+ },
+ {
+ "item_default": 0,
+ "item_description": "A number of total query counts which all auth servers receive over UDP since they started initially",
+ "item_name": "queries.udp",
+ "item_optional": False,
+ "item_title": "Queries UDP",
+ "item_type": "integer"
+ },
+ {
+ "item_name": "queries.perzone",
+ "item_type": "list",
+ "item_optional": False,
+ "item_default": [
+ {
+ "zonename" : "test1.example",
+ "queries.udp" : 1,
+ "queries.tcp" : 2
+ },
+ {
+ "zonename" : "test2.example",
+ "queries.udp" : 3,
+ "queries.tcp" : 4
+ }
+ ],
+ "item_title": "Queries per zone",
+ "item_description": "Queries per zone",
+ "list_item_spec": {
+ "item_name": "zones",
+ "item_type": "map",
+ "item_optional": False,
+ "item_default": {},
+ "map_item_spec": [
+ {
+ "item_name": "zonename",
+ "item_type": "string",
+ "item_optional": False,
+ "item_default": "",
+ "item_title": "Zonename",
+ "item_description": "Zonename"
+ },
+ {
+ "item_name": "queries.udp",
+ "item_type": "integer",
+ "item_optional": False,
+ "item_default": 0,
+ "item_title": "Queries UDP per zone",
+ "item_description": "A number of UDP query counts per zone"
+ },
+ {
+ "item_name": "queries.tcp",
+ "item_type": "integer",
+ "item_optional": False,
+ "item_default": 0,
+ "item_title": "Queries TCP per zone",
+ "item_description": "A number of TCP query counts per zone"
+ }
+ ]
+ }
+ },
+ {
+ "item_name": "nds_queries.perzone",
+ "item_type": "named_set",
+ "item_optional": False,
+ "item_default": {
+ "test10.example" : {
+ "queries.udp" : 1,
+ "queries.tcp" : 2
+ },
+ "test20.example" : {
+ "queries.udp" : 3,
+ "queries.tcp" : 4
+ }
+ },
+ "item_title": "Queries per zone",
+ "item_description": "Queries per zone",
+ "named_set_item_spec": {
+ "item_name": "zonename",
+ "item_type": "map",
+ "item_optional": False,
+ "item_default": {},
+ "item_title": "Zonename",
+ "item_description": "Zonename",
+ "map_item_spec": [
+ {
+ "item_name": "queries.udp",
+ "item_type": "integer",
+ "item_optional": False,
+ "item_default": 0,
+ "item_title": "Queries UDP per zone",
+ "item_description": "A number of UDP query counts per zone"
+ },
+ {
+ "item_name": "queries.tcp",
+ "item_type": "integer",
+ "item_optional": False,
+ "item_default": 0,
+ "item_title": "Queries TCP per zone",
+ "item_description": "A number of TCP query counts per zone"
+ }
+ ]
+ }
+ }]}))
+ self.assertEqual(self.stats.command_showschema(owner='Auth', name='queries.tcp'),
+ isc.config.create_answer(
+ 0, {'Auth': [{
+ "item_default": 0,
+ "item_description": "A number of total query counts which all auth servers receive over TCP since they started initially",
+ "item_name": "queries.tcp",
+ "item_optional": False,
+ "item_title": "Queries TCP",
+ "item_type": "integer"
+ }]}))
+ self.assertEqual(self.stats.command_showschema(owner='Auth', name='queries.perzone'),
+ isc.config.create_answer(
+ 0, {'Auth':[{
+ "item_name": "queries.perzone",
+ "item_type": "list",
+ "item_optional": False,
+ "item_default": [
+ {
+ "zonename" : "test1.example",
+ "queries.udp" : 1,
+ "queries.tcp" : 2
+ },
+ {
+ "zonename" : "test2.example",
+ "queries.udp" : 3,
+ "queries.tcp" : 4
+ }
+ ],
+ "item_title": "Queries per zone",
+ "item_description": "Queries per zone",
+ "list_item_spec": {
+ "item_name": "zones",
+ "item_type": "map",
+ "item_optional": False,
+ "item_default": {},
+ "map_item_spec": [
+ {
+ "item_name": "zonename",
+ "item_type": "string",
+ "item_optional": False,
+ "item_default": "",
+ "item_title": "Zonename",
+ "item_description": "Zonename"
+ },
+ {
+ "item_name": "queries.udp",
+ "item_type": "integer",
+ "item_optional": False,
+ "item_default": 0,
+ "item_title": "Queries UDP per zone",
+ "item_description": "A number of UDP query counts per zone"
+ },
+ {
+ "item_name": "queries.tcp",
+ "item_type": "integer",
+ "item_optional": False,
+ "item_default": 0,
+ "item_title": "Queries TCP per zone",
+ "item_description": "A number of TCP query counts per zone"
+ }
+ ]
+ }
+ }]}))
+ self.assertEqual(self.stats.command_showschema(owner='Auth', name='nds_queries.perzone'),
+ isc.config.create_answer(
+ 0, {'Auth':[{
+ "item_name": "nds_queries.perzone",
+ "item_type": "named_set",
+ "item_optional": False,
+ "item_default": {
+ "test10.example" : {
+ "queries.udp" : 1,
+ "queries.tcp" : 2
+ },
+ "test20.example" : {
+ "queries.udp" : 3,
+ "queries.tcp" : 4
+ }
+ },
+ "item_title": "Queries per zone",
+ "item_description": "Queries per zone",
+ "named_set_item_spec": {
+ "item_name": "zonename",
+ "item_type": "map",
+ "item_optional": False,
+ "item_default": {},
+ "item_title": "Zonename",
+ "item_description": "Zonename",
+ "map_item_spec": [
+ {
+ "item_name": "queries.udp",
+ "item_type": "integer",
+ "item_optional": False,
+ "item_default": 0,
+ "item_title": "Queries UDP per zone",
+ "item_description": "A number of UDP query counts per zone"
+ },
+ {
+ "item_name": "queries.tcp",
+ "item_type": "integer",
+ "item_optional": False,
+ "item_default": 0,
+ "item_title": "Queries TCP per zone",
+ "item_description": "A number of TCP query counts per zone"
+ }
+ ]
+ }
+ }]}))
+
+ self.assertEqual(self.stats.command_showschema(owner='Stats', name='bar'),
+ isc.config.create_answer(
+ 1, "specified arguments are incorrect: owner: Stats, name: bar"))
+ self.assertEqual(self.stats.command_showschema(name='bar'),
+ isc.config.create_answer(
+ 1, "module name is not specified"))
+
+ def test_polling_init(self):
+ """check statistics data of 'Init'."""
+
+ stat = MyStats()
+ stat.update_modules = lambda: None
+ create_answer = isc.config.ccsession.create_answer # shortcut
+
+ stat._answers = [
+ # Answer for "show_processes"
+ (create_answer(0, []), None),
+ # Answers for "getstats" for Init (type of boot_time is invalid)
+ (create_answer(0, {'boot_time': self.const_datetime}),
+ {'from': 'init'}),
+ ]
+
+ stat.do_polling()
+ self.assertEqual(
+ stat.statistics_data_bymid['Init']['init'],
+ {'boot_time': self.const_datetime})
+
+ def test_polling_consolidate(self):
+ """check statistics data of multiple instances of same module."""
+ stat = MyStats()
+ stat.update_modules = lambda: None
+ create_answer = isc.config.ccsession.create_answer # shortcut
+
+ # Test data borrowed from test_update_statistics_data_withmid
+ stat._answers = [
+ (create_answer(0, [[1034, 'b10-auth-1', 'Auth'],
+ [1035, 'b10-auth-2', 'Auth']]), None),
+ (create_answer(0, stat._auth_sdata), {'from': 'auth1'}),
+ (create_answer(0, stat._auth_sdata), {'from': 'auth2'}),
+ (create_answer(0, stat._auth_sdata), {'from': 'auth3'})
+ ]
+
+ stat.do_polling()
+
+ # check statistics data of each 'Auth' instances. expected data
+ # for 'nds_queries.perzone' is special as it needs data merge.
+ self.assertEqual(2, len(stat.statistics_data_bymid['Auth'].values()))
+ for s in stat.statistics_data_bymid['Auth'].values():
+ self.assertEqual(
+ s, {'queries.perzone': stat._auth_sdata['queries.perzone'],
+ 'nds_queries.perzone': stat._nds_queries_per_zone,
+ 'queries.tcp': stat._auth_sdata['queries.tcp'],
+ 'queries.udp': stat._auth_sdata['queries.udp']})
+
+ # check consolidation of statistics data of the auth instances.
+ # it's union of the reported data and the spec default.
+ n = len(stat.statistics_data_bymid['Auth'].values())
+ self.maxDiff = None
+ self.assertEqual(
+ stat.statistics_data['Auth'],
+ {'queries.perzone': [
+ {'zonename': 'test1.example',
+ 'queries.tcp': 5 * n,
+ 'queries.udp': 4 * n},
+ {'zonename': 'test2.example',
+ 'queries.tcp': 4 * n,
+ 'queries.udp': 3 * n},
+ ],
+ 'nds_queries.perzone': {
+ 'test10.example': {
+ 'queries.tcp': 5 * n,
+ 'queries.udp': 4 * n
+ },
+ 'test20.example': {
+ 'queries.tcp': 4 * n,
+ 'queries.udp': 3 * n
+ },
+ },
+ 'queries.tcp': 3 * n,
+ 'queries.udp': 2 * n})
+
+ def test_polling_stats(self):
+ """Check statistics data of 'Stats'
+
+ This is actually irrelevant to do_polling(), but provided to
+ compatibility of older tests.
+
+ """
+ stat = MyStats()
+ self.assertEqual(len(stat.statistics_data['Stats']), 5)
+ self.assertTrue('boot_time' in stat.statistics_data['Stats'])
+ self.assertTrue('last_update_time' in stat.statistics_data['Stats'])
+ self.assertTrue('report_time' in stat.statistics_data['Stats'])
+ self.assertTrue('timestamp' in stat.statistics_data['Stats'])
+ self.assertEqual(stat.statistics_data['Stats']['lname'],
+ stat.mccs._session.lname)
+
+ def test_polling2(self):
+ """Test do_polling() doesn't incorporate broken statistics data.
+
+ Actually, this is not a test for do_polling() itself. It's bad, but
+ fixing that is a subject of different ticket.
+
+ """
+ stat = MyStats()
+ # check default statistics data of 'Init'
+ self.assertEqual(
+ stat.statistics_data['Init'],
+ {'boot_time': self.const_default_datetime})
+
+ # set invalid statistics
+ create_answer = isc.config.ccsession.create_answer # shortcut
+ stat._answers = [
+ # Answer for "show_processes"
+ (create_answer(0, []), None),
+ # Answers for "getstats" for Init (type of boot_time is invalid)
+ (create_answer(0, {'boot_time': 1}), {'from': 'init'}),
+ ]
+ stat.update_modules = lambda: None
+
+ # do_polling() should ignore the invalid answer;
+ # default data shouldn't be replaced.
+ stat.do_polling()
+ self.assertEqual(
+ stat.statistics_data['Init'],
+ {'boot_time': self.const_default_datetime})
+
+class Z_TestOSEnv(unittest.TestCase):
+ # Running this test would break logging setting. To prevent it from
+ # affecting other tests we use the same workaround as
+ # Z_TestStatsHttpdError.
+ def test_osenv(self):
+ """
+ test for the environ variable "B10_FROM_SOURCE"
+ "B10_FROM_SOURCE" is set in Makefile
+ """
+ # test case having B10_FROM_SOURCE
+ self.assertTrue("B10_FROM_SOURCE" in os.environ)
+ self.assertEqual(stats.SPECFILE_LOCATION, \
+ os.environ["B10_FROM_SOURCE"] + os.sep + \
+ "src" + os.sep + "bin" + os.sep + "stats" + \
+ os.sep + "stats.spec")
+ # test case not having B10_FROM_SOURCE
+ path = os.environ["B10_FROM_SOURCE"]
+ os.environ.pop("B10_FROM_SOURCE")
+ self.assertFalse("B10_FROM_SOURCE" in os.environ)
+ # import stats again
+ imp.reload(stats)
+ # revert the changes
+ os.environ["B10_FROM_SOURCE"] = path
+ imp.reload(stats)
+
+if __name__ == "__main__":
+ isc.log.resetUnitTestRootLogger()
+ unittest.main()
diff --git a/src/bin/stats/tests/test_utils.py b/src/bin/stats/tests/test_utils.py
index bfabc13..8886ad2 100644
--- a/src/bin/stats/tests/test_utils.py
+++ b/src/bin/stats/tests/test_utils.py
@@ -20,13 +20,11 @@ Utilities and mock modules for unittests of statistics modules
import os
import io
import time
-import sys
import threading
-import tempfile
import json
import signal
+import socket
-import msgq
import isc.config.cfgmgr
import stats
import stats_httpd
@@ -48,22 +46,9 @@ class SignalHandler():
signal.signal(signal.SIGALRM, self.orig_handler)
def sig_handler(self, signal, frame):
- """envokes unittest.TestCase.fail as a signal handler"""
+ """invokes unittest.TestCase.fail as a signal handler"""
self.fail_handler("A deadlock might be detected")
-def send_command(command_name, module_name, params=None):
- cc_session = isc.cc.Session()
- command = isc.config.ccsession.create_command(command_name, params)
- seq = cc_session.group_sendmsg(command, module_name)
- try:
- (answer, env) = cc_session.group_recvmsg(False, seq)
- if answer:
- return isc.config.ccsession.parse_answer(answer)
- except isc.cc.SessionTimeout:
- pass
- finally:
- cc_session.close()
-
class ThreadingServerManager:
def __init__(self, server, *args, **kwargs):
self.server = server(*args, **kwargs)
@@ -91,45 +76,7 @@ class ThreadingServerManager:
else:
self.server._thread.join(0) # timeout is 0
-class MockMsgq:
- def __init__(self):
- self._started = threading.Event()
- self.msgq = msgq.MsgQ(verbose=False)
- result = self.msgq.setup()
- if result:
- sys.exit("Error on Msgq startup: %s" % result)
-
- def run(self):
- self._started.set()
- try:
- self.msgq.run()
- finally:
- # Make sure all the sockets, etc, are removed once it stops.
- self.msgq.shutdown()
-
- def shutdown(self):
- # Ask it to terminate nicely
- self.msgq.stop()
-
-class MockCfgmgr:
- def __init__(self):
- self._started = threading.Event()
- self.cfgmgr = isc.config.cfgmgr.ConfigManager(
- os.environ['CONFIG_TESTDATA_PATH'], "b10-config.db")
- self.cfgmgr.read_config()
-
- def run(self):
- self._started.set()
- try:
- self.cfgmgr.run()
- except Exception:
- pass
-
- def shutdown(self):
- self.cfgmgr.running = False
-
-class MockInit:
- spec_str = """\
+INIT_SPEC_STR = """\
{
"module_spec": {
"module_name": "Init",
@@ -221,56 +168,12 @@ class MockInit:
}
}
"""
- _BASETIME = CONST_BASETIME
- def __init__(self):
- self._started = threading.Event()
- self.running = False
- self.spec_file = io.StringIO(self.spec_str)
- # create ModuleCCSession object
- self.mccs = isc.config.ModuleCCSession(
- self.spec_file,
- self.config_handler,
- self.command_handler)
- self.spec_file.close()
- self.cc_session = self.mccs._session
- self.got_command_name = ''
- self.pid_list = [[ 9999, "b10-auth", "Auth" ],
- [ 9998, "b10-auth-2", "Auth" ]]
- self.statistics_data = {
- 'boot_time': time.strftime('%Y-%m-%dT%H:%M:%SZ', self._BASETIME)
- }
-
- def run(self):
- self.mccs.start()
- self.running = True
- self._started.set()
- try:
- while self.running:
- self.mccs.check_command(False)
- except Exception:
- pass
-
- def shutdown(self):
- self.running = False
-
- def config_handler(self, new_config):
- return isc.config.create_answer(0)
-
- def command_handler(self, command, *args, **kwargs):
- self._started.set()
- self.got_command_name = command
- sdata = self.statistics_data
- if command == 'getstats':
- return isc.config.create_answer(0, sdata)
- elif command == 'show_processes':
- # Return dummy pids
- return isc.config.create_answer(
- 0, self.pid_list)
- return isc.config.create_answer(1, "Unknown Command")
-
-class MockAuth:
- spec_str = """\
+# Note: this is derived of the spec for the DNS authoritative server, but
+# for the purpose of this test, it's completely irrelevant to DNS.
+# Some statisittics specs do not make sense for practical sense but used
+# just cover various types of statistics data (list, map/dict, etc).
+AUTH_SPEC_STR = """\
{
"module_spec": {
"module_name": "Auth",
@@ -392,68 +295,6 @@ class MockAuth:
}
}
"""
- def __init__(self):
- self._started = threading.Event()
- self.running = False
- self.spec_file = io.StringIO(self.spec_str)
- # create ModuleCCSession object
- self.mccs = isc.config.ModuleCCSession(
- self.spec_file,
- self.config_handler,
- self.command_handler)
- self.spec_file.close()
- self.cc_session = self.mccs._session
- self.got_command_name = ''
- self.queries_tcp = 3
- self.queries_udp = 2
- self.queries_per_zone = [{
- 'zonename': 'test1.example',
- 'queries.tcp': 5,
- 'queries.udp': 4
- }]
- self.nds_queries_per_zone = {
- 'test10.example': {
- 'queries.tcp': 5,
- 'queries.udp': 4
- }
- }
-
- def run(self):
- self.mccs.start()
- self.running = True
- self._started.set()
- try:
- while self.running:
- self.mccs.check_command(False)
- except Exception:
- pass
-
- def shutdown(self):
- self.running = False
-
- def config_handler(self, new_config):
- return isc.config.create_answer(0)
-
- def command_handler(self, command, *args, **kwargs):
- self.got_command_name = command
- sdata = { 'queries.tcp': self.queries_tcp,
- 'queries.udp': self.queries_udp,
- 'queries.perzone' : self.queries_per_zone,
- 'nds_queries.perzone' : {
- 'test10.example': {
- 'queries.tcp': \
- isc.cc.data.find(
- self.nds_queries_per_zone,
- 'test10.example/queries.tcp')
- }
- },
- 'nds_queries.perzone/test10.example/queries.udp' :
- isc.cc.data.find(self.nds_queries_per_zone,
- 'test10.example/queries.udp')
- }
- if command == 'getstats':
- return isc.config.create_answer(0, sdata)
- return isc.config.create_answer(1, "Unknown Command")
class MyModuleCCSession(isc.config.ConfigData):
"""Mocked ModuleCCSession class.
@@ -468,6 +309,7 @@ class MyModuleCCSession(isc.config.ConfigData):
isc.config.ConfigData.__init__(self, module_spec)
self._session = self
self.stopped = False
+ self.closed = False
self.lname = 'mock_mod_ccs'
def start(self):
@@ -476,10 +318,13 @@ class MyModuleCCSession(isc.config.ConfigData):
def send_stopping(self):
self.stopped = True # just record it's called to inspect it later
-class SimpleStats(stats.Stats):
+ def close(self):
+ self.closed = True
+
+class MyStats(stats.Stats):
"""A faked Stats class for unit tests.
- This class inherits most of the real Stats class, but replace the
+ This class inherits most of the real Stats class, but replaces the
ModuleCCSession with a fake one so we can avoid network I/O in tests,
and can also inspect or tweak messages via the session more easily.
This class also maintains some faked module information and statistics
@@ -500,9 +345,9 @@ class SimpleStats(stats.Stats):
# the default answer from faked recvmsg if _answers is empty
self.__default_answer = isc.config.ccsession.create_answer(
0, {'Init':
- json.loads(MockInit.spec_str)['module_spec']['statistics'],
+ json.loads(INIT_SPEC_STR)['module_spec']['statistics'],
'Auth':
- json.loads(MockAuth.spec_str)['module_spec']['statistics']
+ json.loads(AUTH_SPEC_STR)['module_spec']['statistics']
})
# setup faked auth statistics
self.__init_auth_stat()
@@ -530,24 +375,24 @@ class SimpleStats(stats.Stats):
def __init_auth_stat(self):
self._queries_tcp = 3
self._queries_udp = 2
- self.__queries_per_zone = [{
+ self._queries_per_zone = [{
'zonename': 'test1.example', 'queries.tcp': 5, 'queries.udp': 4
}]
- self.__nds_queries_per_zone = \
+ self._nds_queries_per_zone = \
{ 'test10.example': { 'queries.tcp': 5, 'queries.udp': 4 } }
self._auth_sdata = \
{ 'queries.tcp': self._queries_tcp,
'queries.udp': self._queries_udp,
- 'queries.perzone' : self.__queries_per_zone,
+ 'queries.perzone' : self._queries_per_zone,
'nds_queries.perzone' : {
'test10.example': {
'queries.tcp': isc.cc.data.find(
- self.__nds_queries_per_zone,
+ self._nds_queries_per_zone,
'test10.example/queries.tcp')
}
},
'nds_queries.perzone/test10.example/queries.udp' :
- isc.cc.data.find(self.__nds_queries_per_zone,
+ isc.cc.data.find(self._nds_queries_per_zone,
'test10.example/queries.udp')
}
@@ -589,32 +434,62 @@ class SimpleStats(stats.Stats):
answer, _ = self.__group_recvmsg(None, None)
return isc.config.ccsession.parse_answer(answer)[1]
-class MyStats(stats.Stats):
-
- stats._BASETIME = CONST_BASETIME
- stats.get_timestamp = lambda: time.mktime(CONST_BASETIME)
- stats.get_datetime = lambda x=None: time.strftime("%Y-%m-%dT%H:%M:%SZ", CONST_BASETIME)
-
- def __init__(self):
- self._started = threading.Event()
- stats.Stats.__init__(self)
+class MyStatsHttpd(stats_httpd.StatsHttpd):
+ """A faked StatsHttpd class for unit tests.
- def run(self):
- self._started.set()
- try:
- self.start()
- except Exception:
- pass
+ This class inherits most of the real StatsHttpd class, but replaces the
+ ModuleCCSession with a fake one so we can avoid network I/O in tests,
+ and can also inspect or tweak messages via the session more easily.
- def shutdown(self):
- self.command_shutdown()
+ """
-class MyStatsHttpd(stats_httpd.StatsHttpd):
ORIG_SPECFILE_LOCATION = stats_httpd.SPECFILE_LOCATION
def __init__(self, *server_address):
self._started = threading.Event()
+ self.__dummy_sock = None # see below
+
+ # Prepare commonly used statistics schema and data requested in
+ # stats-httpd tests. For the purpose of these tests, the content of
+ # statistics data is not so important (they don't test whther the
+ # counter values are correct, etc), so hardcoding the common case
+ # should suffice. Note also that some of the statistics values and
+ # specs don't make sense in practice (see also comments on
+ # AUTH_SPEC_STR).
+ with open(stats.SPECFILE_LOCATION) as f:
+ stat_spec_str = f.read()
+ self.__default_spec_answer = {
+ 'Init': json.loads(INIT_SPEC_STR)['module_spec']['statistics'],
+ 'Auth': json.loads(AUTH_SPEC_STR)['module_spec']['statistics'],
+ 'Stats': json.loads(stat_spec_str)['module_spec']['statistics']
+ }
+ self.__default_data_answer = {
+ 'Init': {'boot_time':
+ time.strftime('%Y-%m-%dT%H:%M:%SZ', CONST_BASETIME)},
+ 'Stats': {'last_update_time':
+ time.strftime('%Y-%m-%dT%H:%M:%SZ', CONST_BASETIME),
+ 'report_time':
+ time.strftime('%Y-%m-%dT%H:%M:%SZ', CONST_BASETIME),
+ 'lname': 'test-lname',
+ 'boot_time':
+ time.strftime('%Y-%m-%dT%H:%M:%SZ', CONST_BASETIME),
+ 'timestamp': time.mktime(CONST_BASETIME)},
+ 'Auth': {'queries.udp': 4, 'queries.tcp': 6,
+ 'queries.perzone': [
+ {'queries.udp': 8, 'queries.tcp': 10,
+ 'zonename': 'test1.example'},
+ {'queries.udp': 6, 'queries.tcp': 8,
+ 'zonename': 'test2.example'}],
+ 'nds_queries.perzone': {
+ 'test10.example': {'queries.udp': 8, 'queries.tcp': 10},
+ 'test20.example': {'queries.udp': 6, 'queries.tcp': 8}}}}
+
+ # if set, use them as faked response to rpc_call (see below).
+ # it's a list of answer data of rpc_call.
+ self._rpc_answers = []
+
if server_address:
- stats_httpd.SPECFILE_LOCATION = self.create_specfile(*server_address)
+ stats_httpd.SPECFILE_LOCATION = \
+ self.__create_specfile(*server_address)
try:
stats_httpd.StatsHttpd.__init__(self)
finally:
@@ -624,7 +499,51 @@ class MyStatsHttpd(stats_httpd.StatsHttpd):
else:
stats_httpd.StatsHttpd.__init__(self)
- def create_specfile(self, *server_address):
+ # replace some (faked) ModuleCCSession methods so we can inspect/fake.
+ # in order to satisfy select.select() we need some real socket. We
+ # use an unusable AF_UNIX socket; we won't actually use it for
+ # communication.
+ self.cc_session.rpc_call = self.__rpc_call
+ self.__dummy_sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ self.mccs.get_socket = lambda: self.__dummy_sock
+
+ def open_mccs(self):
+ self.mccs = MyModuleCCSession(stats_httpd.SPECFILE_LOCATION,
+ self.config_handler,
+ self.command_handler)
+ self.cc_session = self.mccs._session
+ self.mccs.start = self.load_config # force reload
+
+ def close_mccs(self):
+ super().close_mccs()
+ if self.__dummy_sock is not None:
+ self.__dummy_sock.close()
+ self.__dummy_sock = None
+
+ def __rpc_call(self, command, group, params={}):
+ """Faked ModuleCCSession.rpc_call for tests.
+
+ The stats httpd module only issues two commands: 'showschema' and
+ 'show'. In most cases we can simply use the prepared default
+ answer. If customization is needed, the test case can add a
+ faked answer by appending it to _rpc_answers. If the added object
+ is of Exception type this method raises it instead of return it,
+ emulating the situation where rpc_call() results in an exception.
+
+ """
+ if len(self._rpc_answers) == 0:
+ if command == 'showschema':
+ return self.__default_spec_answer
+ elif command == 'show':
+ return self.__default_data_answer
+ assert False, "unexpected command for faked rpc_call: " + command
+
+ answer = self._rpc_answers.pop(0)
+ if issubclass(type(answer), Exception):
+ raise answer
+ return answer
+
+ def __create_specfile(self, *server_address):
spec_io = open(self.ORIG_SPECFILE_LOCATION)
try:
spec = json.load(spec_io)
@@ -633,7 +552,8 @@ class MyStatsHttpd(stats_httpd.StatsHttpd):
for i in range(len(config)):
if config[i]['item_name'] == 'listen_on':
config[i]['item_default'] = \
- [ dict(address=a[0], port=a[1]) for a in server_address ]
+ [ dict(address=a[0], port=a[1])
+ for a in server_address ]
break
return io.StringIO(json.dumps(spec))
finally:
@@ -641,53 +561,4 @@ class MyStatsHttpd(stats_httpd.StatsHttpd):
def run(self):
self._started.set()
- try:
- self.start()
- except Exception:
- pass
-
- def shutdown(self):
- self.command_handler('shutdown', None)
-
-class BaseModules:
- def __init__(self):
- # MockMsgq
- self.msgq = ThreadingServerManager(MockMsgq)
- self.msgq.run()
- # Check whether msgq is ready. A SessionTimeout is raised here if not.
- isc.cc.session.Session().close()
- # MockCfgmgr
- self.cfgmgr = ThreadingServerManager(MockCfgmgr)
- self.cfgmgr.run()
- # MockInit
- self.b10_init = ThreadingServerManager(MockInit)
- self.b10_init.run()
- # MockAuth
- self.auth = ThreadingServerManager(MockAuth)
- self.auth.run()
- self.auth2 = ThreadingServerManager(MockAuth)
- self.auth2.run()
-
-
- def shutdown(self):
- # MockMsgq. We need to wait (blocking) for it, otherwise it'll wipe out
- # a socket for another test during its shutdown.
- self.msgq.shutdown(True)
-
- # We also wait for the others, but these are just so we don't create
- # too many threads in parallel.
-
- # MockAuth
- self.auth2.shutdown(True)
- self.auth.shutdown(True)
- # MockInit
- self.b10_init.shutdown(True)
- # MockCfgmgr
- self.cfgmgr.shutdown(True)
- # remove the unused socket file
- socket_file = self.msgq.server.msgq.socket_file
- try:
- if os.path.exists(socket_file):
- os.remove(socket_file)
- except OSError:
- pass
+ self.start()
diff --git a/src/bin/tests/Makefile.am b/src/bin/tests/Makefile.am
index 41b497f..535257c 100644
--- a/src/bin/tests/Makefile.am
+++ b/src/bin/tests/Makefile.am
@@ -8,7 +8,7 @@ noinst_SCRIPTS = $(PYTESTS)
# required by loadable python modules.
LIBRARY_PATH_PLACEHOLDER =
if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/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/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/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:$$$(ENV_LIBRARY_PATH)
endif
# test using command-line arguments, so use check-local target instead of TESTS
diff --git a/src/bin/usermgr/b10-cmdctl-usermgr.py.in b/src/bin/usermgr/b10-cmdctl-usermgr.py.in
index 5fd033e..37be304 100755
--- a/src/bin/usermgr/b10-cmdctl-usermgr.py.in
+++ b/src/bin/usermgr/b10-cmdctl-usermgr.py.in
@@ -19,6 +19,7 @@
This tool implements user management for b10-cmdctl. It is used to
add and remove users from the accounts file.
'''
+import sys; sys.path.append ('@@PYTHONPATH@@')
from bind10_config import SYSCONFPATH
from collections import OrderedDict
import random
@@ -27,7 +28,6 @@ import csv
import getpass
from optparse import OptionParser, OptionValueError
import os
-import sys; sys.path.append ('@@PYTHONPATH@@')
import isc.util.process
isc.util.process.rename()
diff --git a/src/bin/xfrin/b10-xfrin.xml b/src/bin/xfrin/b10-xfrin.xml
index 588048a..0da7ce0 100644
--- a/src/bin/xfrin/b10-xfrin.xml
+++ b/src/bin/xfrin/b10-xfrin.xml
@@ -213,6 +213,132 @@ operation
-->
</refsect1>
+ <refsect1>
+ <title>STATISTICS DATA</title>
+
+ <para>
+ The statistics data collected by the <command>b10-xfrin</command>
+ daemon for <quote>Xfrin</quote> include:
+ </para>
+
+ <variablelist>
+
+ <varlistentry>
+ <term>zones</term>
+ <listitem><simpara>
+ A directory name of per-zone statistics
+ </simpara>
+ <variablelist>
+
+ <varlistentry>
+ <term><replaceable>zonename</replaceable></term>
+ <listitem><simpara>
+ An actual zone name or special zone name
+ <quote>_SERVER_</quote> representing the entire server.
+ Zone classes (e.g. IN, CH, and HS) are mixed and counted so
+ far. But these will be distinguished in future release.
+ </simpara>
+ <variablelist>
+
+ <varlistentry>
+ <term>soaoutv4</term>
+ <listitem><simpara>
+ Number of IPv4 SOA queries sent from Xfrin
+ </simpara></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>soaoutv6</term>
+ <listitem><simpara>
+ Number of IPv6 SOA queries sent from Xfrin
+ </simpara></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>axfrreqv4</term>
+ <listitem><simpara>
+ Number of IPv4 AXFR requests sent from Xfrin
+ </simpara></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>axfrreqv6</term>
+ <listitem><simpara>
+ Number of IPv6 AXFR requests sent from Xfrin
+ </simpara></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>ixfrreqv4</term>
+ <listitem><simpara>
+ Number of IPv4 IXFR requests sent from Xfrin
+ </simpara></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>ixfrreqv6</term>
+ <listitem><simpara>
+ Number of IPv6 IXFR requests sent from Xfrin
+ </simpara></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>xfrsuccess</term>
+ <listitem><simpara>
+ Number of zone transfer requests succeeded.
+ These include the case where the zone turns
+ out to be the latest as a result of an
+ initial SOA query (and there is actually no
+ AXFR or IXFR transaction).
+ </simpara></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>xfrfail</term>
+ <listitem><simpara>
+ Number of zone transfer requests failed
+ </simpara></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>last_axfr_duration</term>
+ <listitem><simpara>
+ Duration in seconds of the last successful AXFR. 0.0
+ means no successful AXFR done or means a successful AXFR
+ done in less than a microsecond. If an AXFR is aborted
+ due to some failure, this duration won't be updated.
+ </simpara></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>last_ixfr_duration</term>
+ <listitem><simpara>
+ Duration in seconds of the last successful IXFR. 0.0
+ means no successful IXFR done or means a successful IXFR
+ done in less than a microsecond. If an IXFR is aborted
+ due to some failure, this duration won't be updated.
+ </simpara></listitem>
+ </varlistentry>
+
+ </variablelist>
+ </listitem>
+ </varlistentry><!-- end of zonename -->
+
+ </variablelist>
+ </listitem>
+ </varlistentry><!-- end of zones -->
+
+ </variablelist>
+
+ <para>
+ In per-zone counters the special zone name <quote>_SERVER_</quote>
+ exists.
+ It doesn't mean a specific zone. It represents the entire server
+ and the counter value of this special zone is the total of the
+ same counter for all zones.
+ </para>
+
+ </refsect1>
<!--
<refsect1>
diff --git a/src/bin/xfrin/tests/Makefile.am b/src/bin/xfrin/tests/Makefile.am
index c1ca278..66a13f3 100644
--- a/src/bin/xfrin/tests/Makefile.am
+++ b/src/bin/xfrin/tests/Makefile.am
@@ -8,7 +8,7 @@ EXTRA_DIST = $(PYTESTS)
# required by loadable python modules.
LIBRARY_PATH_PLACEHOLDER =
if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/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/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/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:$$$(ENV_LIBRARY_PATH)
else
# Some systems need the ds path even if not all paths are necessary
LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/datasrc/.libs
diff --git a/src/bin/xfrin/tests/xfrin_test.py b/src/bin/xfrin/tests/xfrin_test.py
index 86c2ef5..2305cdb 100644
--- a/src/bin/xfrin/tests/xfrin_test.py
+++ b/src/bin/xfrin/tests/xfrin_test.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009-2011 Internet Systems Consortium.
+# Copyright (C) 2009-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
@@ -19,6 +19,7 @@ import shutil
import socket
import sys
import io
+from datetime import datetime
from isc.testutils.tsigctx_mock import MockTSIGContext
from isc.testutils.ccsession_mock import MockModuleCCSession
from isc.testutils.rrset_utils import *
@@ -717,7 +718,7 @@ class TestXfrinConnection(unittest.TestCase):
self.sock_map = {}
self.conn = MockXfrinConnection(self.sock_map, TEST_ZONE_NAME,
TEST_RRCLASS, None, threading.Event(),
- TEST_MASTER_IPV4_ADDRINFO)
+ self._master_addrinfo)
self.conn.init_socket()
self.soa_response_params = {
'questions': [example_soa_question],
@@ -749,6 +750,10 @@ class TestXfrinConnection(unittest.TestCase):
os.remove(TEST_DB_FILE)
xfrin.check_zone = self.__orig_check_zone
+ @property
+ def _master_addrinfo(self):
+ return TEST_MASTER_IPV4_ADDRINFO
+
def __check_zone(self, name, rrclass, rrsets, callbacks):
'''
A mock function used instead of dns.check_zone.
@@ -1065,6 +1070,20 @@ class TestAXFR(TestXfrinConnection):
self.assertRaises(XfrinProtocolError,
self.conn._handle_xfrin_responses)
+ def test_ipver_str(self):
+ addrs = (((socket.AF_INET, socket.SOCK_STREAM), 'v4'),
+ ((socket.AF_INET6, socket.SOCK_STREAM), 'v6'),
+ ((socket.AF_UNIX, socket.SOCK_STREAM), None))
+ for (info, ver) in addrs:
+ c = MockXfrinConnection({}, TEST_ZONE_NAME, RRClass.CH, None,
+ threading.Event(), info)
+ c.init_socket()
+ if ver is not None:
+ self.assertEqual(ver, c._get_ipver_str())
+ else:
+ self.assertRaises(ValueError, c._get_ipver_str)
+ c.close()
+
def test_soacheck(self):
# we need to defer the creation until we know the QID, which is
# determined in _check_soa_serial(), so we use response_generator.
@@ -2104,6 +2123,187 @@ class TestXFRSessionWithSQLite3(TestXfrinConnection):
self.assertFalse(self.record_exist(Name('dns01.example.com'),
RRType.A))
+class TestStatisticsXfrinConn(TestXfrinConnection):
+ '''Test class based on TestXfrinConnection and including paramters
+ and methods related to statistics tests'''
+ def setUp(self):
+ super().setUp()
+ # clear all statistics counters before each test
+ self.conn._counters.clear_all()
+ # fake datetime
+ self.__orig_datetime = isc.statistics.counters.datetime
+ self.__orig_start_timer = isc.statistics.counters._start_timer
+ time1 = datetime(2000, 1, 1, 0, 0, 0, 0)
+ time2 = datetime(2000, 1, 1, 0, 0, 0, 1)
+ class FakeDateTime:
+ @classmethod
+ def now(cls): return time2
+ isc.statistics.counters.datetime = FakeDateTime
+ isc.statistics.counters._start_timer = lambda : time1
+ delta = time2 - time1
+ self._const_sec = round(delta.days * 86400 + delta.seconds +
+ delta.microseconds * 1E-6, 6)
+ # List of statistics counter names and expected initial values
+ self.__name_to_counter = (('axfrreqv4', 0),
+ ('axfrreqv6', 0),
+ ('ixfrreqv4', 0),
+ ('ixfrreqv6', 0),
+ ('last_axfr_duration', 0.0),
+ ('last_ixfr_duration', 0.0),
+ ('soaoutv4', 0),
+ ('soaoutv6', 0),
+ ('xfrfail', 0),
+ ('xfrsuccess', 0))
+ self.__zones = 'zones'
+
+ def tearDown(self):
+ super().tearDown()
+ isc.statistics.counters.datetime = self.__orig_datetime
+ isc.statistics.counters._start_timer = self.__orig_start_timer
+
+ @property
+ def _ipver(self):
+ return 'v4'
+
+ def _check_init_statistics(self):
+ '''checks exception being raised if not incremented statistics
+ counter gotten'''
+ for (name, exp) in self.__name_to_counter:
+ self.assertRaises(isc.cc.data.DataNotFoundError,
+ self.conn._counters.get, self.__zones,
+ TEST_ZONE_NAME_STR, name)
+
+ def _check_updated_statistics(self, overwrite):
+ '''checks getting expect values after updating the pairs of
+ statistics counter name and value on to the "overwrite"
+ dictionary'''
+ name2count = dict(self.__name_to_counter)
+ name2count.update(overwrite)
+ for (name, exp) in name2count.items():
+ act = self.conn._counters.get(self.__zones,
+ TEST_ZONE_NAME_STR,
+ name)
+ msg = '%s is expected %s but actually %s' % (name, exp, act)
+ self.assertEqual(exp, act, msg=msg)
+
+class TestStatisticsXfrinAXFRv4(TestStatisticsXfrinConn):
+ '''Xfrin AXFR tests for IPv4 to check statistics counters'''
+ def test_soaout(self):
+ '''tests that an soaoutv4 or soaoutv6 counter is incremented
+ when an soa query succeeds'''
+ self.conn.response_generator = self._create_soa_response_data
+ self._check_init_statistics()
+ self.assertEqual(self.conn._check_soa_serial(), XFRIN_OK)
+ self._check_updated_statistics({'soaout' + self._ipver: 1})
+
+ def test_axfrreq_xfrsuccess_last_axfr_duration(self):
+ '''tests that axfrreqv4 or axfrreqv6 and xfrsuccess counters
+ and last_axfr_duration timer are incremented when xfr succeeds'''
+ self.conn.response_generator = self._create_normal_response_data
+ self._check_init_statistics()
+ self.assertEqual(self.conn.do_xfrin(False), XFRIN_OK)
+ self._check_updated_statistics({'axfrreq' + self._ipver: 1,
+ 'xfrsuccess': 1,
+ 'last_axfr_duration': self._const_sec})
+
+ def test_axfrreq_xfrsuccess_last_axfr_duration2(self):
+ '''tests that axfrreqv4 or axfrreqv6 and xfrsuccess counters
+ and last_axfr_duration timer are incremented when raising
+ XfrinZoneUptodate. The exception is treated as success.'''
+ def exception_raiser():
+ raise XfrinZoneUptodate()
+ self.conn._handle_xfrin_responses = exception_raiser
+ self._check_init_statistics()
+ self.assertEqual(self.conn.do_xfrin(False), XFRIN_OK)
+ self._check_updated_statistics({'axfrreq' + self._ipver: 1,
+ 'xfrsuccess': 1,
+ 'last_axfr_duration':
+ self._const_sec})
+
+ def test_axfrreq_xfrfail(self):
+ '''tests that axfrreqv4 or axfrreqv6 and xfrfail counters are
+ incremented even if some failure exceptions are expected to be
+ raised inside do_xfrin(): XfrinZoneError, XfrinProtocolError,
+ XfrinException, and Exception'''
+ self._check_init_statistics()
+ count = 0
+ for ex in [XfrinZoneError, XfrinProtocolError, XfrinException,
+ Exception]:
+ def exception_raiser():
+ raise ex()
+ self.conn._handle_xfrin_responses = exception_raiser
+ self.assertEqual(self.conn.do_xfrin(False), XFRIN_FAIL)
+ count += 1
+ self._check_updated_statistics({'axfrreq' + self._ipver: count,
+ 'xfrfail': count})
+
+class TestStatisticsXfrinIXFRv4(TestStatisticsXfrinConn):
+ '''Xfrin IXFR tests for IPv4 to check statistics counters'''
+ def test_ixfrreq_xfrsuccess_last_ixfr_duration(self):
+ '''tests that ixfrreqv4 or ixfrreqv6 and xfrsuccess counters
+ and last_ixfr_duration timer are incremented when xfr succeeds'''
+ def create_ixfr_response():
+ self.conn.reply_data = self.conn.create_response_data(
+ questions=[Question(TEST_ZONE_NAME, TEST_RRCLASS,
+ RRType.IXFR)],
+ answers=[soa_rrset, begin_soa_rrset, soa_rrset, soa_rrset])
+ self.conn.response_generator = create_ixfr_response
+ self._check_init_statistics()
+ self.assertEqual(XFRIN_OK, self.conn.do_xfrin(False, RRType.IXFR))
+ self._check_updated_statistics({'ixfrreq' + self._ipver: 1,
+ 'xfrsuccess': 1,
+ 'last_ixfr_duration':
+ self._const_sec})
+
+ def test_ixfrreq_xfrsuccess_last_ixfr_duration2(self):
+ '''tests that ixfrreqv4 or ixfrreqv6 and xfrsuccess counters
+ and last_ixfr_duration timer are incremented when raising
+ XfrinZoneUptodate. The exception is treated as success.'''
+ def exception_raiser():
+ raise XfrinZoneUptodate()
+ self.conn._handle_xfrin_responses = exception_raiser
+ self._check_init_statistics()
+ self.assertEqual(self.conn.do_xfrin(False, RRType.IXFR), XFRIN_OK)
+ self._check_updated_statistics({'ixfrreq' + self._ipver: 1,
+ 'xfrsuccess': 1,
+ 'last_ixfr_duration':
+ self._const_sec})
+
+ def test_ixfrreq_xfrfail(self):
+ '''tests that ixfrreqv4 or ixfrreqv6 and xfrfail counters are
+ incremented even if some failure exceptions are expected to be
+ raised inside do_xfrin(): XfrinZoneError, XfrinProtocolError,
+ XfrinException, and Exception'''
+ self._check_init_statistics()
+ count = 0
+ for ex in [XfrinZoneError, XfrinProtocolError, XfrinException,
+ Exception]:
+ def exception_raiser():
+ raise ex()
+ self.conn._handle_xfrin_responses = exception_raiser
+ self.assertEqual(self.conn.do_xfrin(False, RRType.IXFR), XFRIN_FAIL)
+ count += 1
+ self._check_updated_statistics({'ixfrreq' + self._ipver: count,
+ 'xfrfail': count})
+
+class TestStatisticsXfrinAXFRv6(TestStatisticsXfrinAXFRv4):
+ '''Same tests as TestStatisticsXfrinAXFRv4 for IPv6'''
+ @property
+ def _master_addrinfo(self):
+ return TEST_MASTER_IPV6_ADDRINFO
+ @property
+ def _ipver(self):
+ return 'v6'
+
+class TestStatisticsIXFRv6(TestStatisticsXfrinIXFRv4):
+ '''Same tests as TestStatisticsXfrinIXFRv4 for IPv6'''
+ @property
+ def _master_addrinfo(self):
+ return TEST_MASTER_IPV6_ADDRINFO
+ @property
+ def _ipver(self):
+ return 'v6'
+
class TestXfrinRecorder(unittest.TestCase):
def setUp(self):
self.recorder = XfrinRecorder()
@@ -2193,7 +2393,7 @@ class TestXfrinProcess(unittest.TestCase):
master_addrinfo, tsig_key)
# An awkward check that would specifically identify an old bug
- # where initialziation of XfrinConnection._tsig_ctx_creator caused
+ # where initialization of XfrinConnection._tsig_ctx_creator caused
# self reference and subsequently led to reference leak.
orig_ref = sys.getrefcount(conn)
conn._tsig_ctx_creator = None
@@ -2421,7 +2621,7 @@ class TestXfrin(unittest.TestCase):
# there can be one more outstanding transfer.
self.assertEqual(self.xfr.command_handler("retransfer",
self.args)['result'][0], 0)
- # make sure the # xfrs would excceed the quota
+ # make sure the # xfrs would exceed the quota
self.xfr.recorder.increment(Name(str(self.xfr._max_transfers_in) + TEST_ZONE_NAME_STR))
# this one should fail
self.assertEqual(self.xfr.command_handler("retransfer",
@@ -2512,6 +2712,14 @@ class TestXfrin(unittest.TestCase):
self.assertEqual(self.xfr.config_handler({'transfers_in': 3})['result'][0], 0)
self.assertEqual(self.xfr._max_transfers_in, 3)
+ def test_command_handler_getstats(self):
+ module_spec = isc.config.module_spec_from_file(
+ xfrin.SPECFILE_LOCATION)
+ ans = isc.config.parse_answer(
+ self.xfr.command_handler("getstats", None))
+ self.assertEqual(0, ans[0])
+ self.assertTrue(module_spec.validate_statistics(False, ans[1]))
+
def _check_zones_config(self, config_given):
if 'transfers_in' in config_given:
self.assertEqual(config_given['transfers_in'],
diff --git a/src/bin/xfrin/xfrin.py.in b/src/bin/xfrin/xfrin.py.in
index 12a4a10..2066404 100755
--- a/src/bin/xfrin/xfrin.py.in
+++ b/src/bin/xfrin/xfrin.py.in
@@ -1,6 +1,6 @@
#!@PYTHON@
-# Copyright (C) 2009-2011 Internet Systems Consortium.
+# Copyright (C) 2009-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
@@ -28,6 +28,7 @@ import time
from functools import reduce
from optparse import OptionParser, OptionValueError
from isc.config.ccsession import *
+from isc.statistics import Counters
from isc.notify import notify_out
import isc.util.process
from isc.datasrc import DataSourceClient, ZoneFinder
@@ -187,7 +188,7 @@ def get_soa_serial(soa_rdata):
class XfrinState:
'''
- The states of the incomding *XFR state machine.
+ The states of the incoming *XFR state machine.
We (will) handle both IXFR and AXFR with a single integrated state
machine because they cannot be distinguished immediately - an AXFR
@@ -269,7 +270,7 @@ class XfrinState:
can be used as singleton objects. For now, however, we always instantiate
a new object for every state transition, partly because the introduction
of singleton will make a code bit complicated, and partly because
- the overhead of object instantiotion wouldn't be significant for xfrin.
+ the overhead of object instantiation wouldn't be significant for xfrin.
'''
def set_xfrstate(self, conn, new_state):
@@ -612,6 +613,7 @@ class XfrinConnection(asyncore.dispatcher):
# keep a record of this specific transfer to log on success
# (time, rr/s, etc)
self._transfer_stats = XfrinTransferStats()
+ self._counters = Counters(SPECFILE_LOCATION)
def init_socket(self):
'''Initialize the underlyig socket.
@@ -622,7 +624,7 @@ class XfrinConnection(asyncore.dispatcher):
it if the constructor raises an exception after opening the socket.
'''
self.create_socket(self._master_addrinfo[0], self._master_addrinfo[1])
- self.setblocking(1)
+ self.socket.setblocking(1)
def _get_zone_soa(self):
'''Retrieve the current SOA RR of the zone to be transferred.
@@ -891,6 +893,19 @@ class XfrinConnection(asyncore.dispatcher):
# All okay, return it
return soa
+ def _get_ipver_str(self):
+ """Returns a 'v4' or 'v6' string representing a IP version
+ depending on the socket family. This is for an internal use
+ only (except for tests). This is supported only for IP sockets.
+ It raises a ValueError exception on other address families.
+
+ """
+ if self.socket.family == socket.AF_INET:
+ return 'v4'
+ elif self.socket.family == socket.AF_INET6:
+ return 'v6'
+ raise ValueError("Invalid address family. "
+ "This is supported only for IP sockets")
def _check_soa_serial(self):
'''Send SOA query and compare the local and remote serials.
@@ -902,6 +917,9 @@ class XfrinConnection(asyncore.dispatcher):
'''
self._send_query(RRType.SOA)
+ # count soaoutv4 or soaoutv6 requests
+ self._counters.inc('zones', self._zone_name.to_text(),
+ 'soaout' + self._get_ipver_str())
data_len = self._get_request_response(2)
msg_len = socket.htons(struct.unpack('H', data_len)[0])
soa_response = self._get_request_response(msg_len)
@@ -931,9 +949,7 @@ class XfrinConnection(asyncore.dispatcher):
try:
ret = XFRIN_OK
self._request_type = request_type
- # Right now RRType.[IA]XFR().to_text() is 'TYPExxx', so we need
- # to hardcode here.
- req_str = 'IXFR' if request_type == RRType.IXFR else 'AXFR'
+ req_str = request_type.to_text()
if check_soa:
self._check_soa_serial()
self.close()
@@ -941,7 +957,16 @@ class XfrinConnection(asyncore.dispatcher):
if not self.connect_to_master():
raise XfrinException('Unable to reconnect to master')
+ # start statistics timer
+ # Note: If the timer for the zone is already started but
+ # not yet stopped due to some error, the last start time
+ # is overwritten at this point.
+ self._counters.start_timer('zones', self._zone_name.to_text(),
+ 'last_' + req_str.lower() + '_duration')
logger.info(XFRIN_XFR_TRANSFER_STARTED, req_str, self.zone_str())
+ # An AXFR or IXFR is being requested.
+ self._counters.inc('zones', self._zone_name.to_text(),
+ req_str.lower() + 'req' + self._get_ipver_str())
self._send_query(self._request_type)
self.__state = XfrinInitialSOA()
self._handle_xfrin_responses()
@@ -968,7 +993,6 @@ class XfrinConnection(asyncore.dispatcher):
"%.3f" % self._transfer_stats.get_running_time(),
"%.f" % self._transfer_stats.get_bytes_per_second()
)
-
except XfrinZoneUptodate:
# Eventually we'll probably have to treat this case as a trigger
# of trying another primary server, etc, but for now we treat it
@@ -1004,11 +1028,21 @@ class XfrinConnection(asyncore.dispatcher):
self.zone_str(), str(e))
ret = XFRIN_FAIL
finally:
+ # A xfrsuccess or xfrfail counter is incremented depending on
+ # the result.
+ result = {XFRIN_OK: 'xfrsuccess', XFRIN_FAIL: 'xfrfail'}[ret]
+ self._counters.inc('zones', self._zone_name.to_text(), result)
+ # The started statistics timer is finally stopped only in
+ # a successful case.
+ if ret == XFRIN_OK:
+ self._counters.stop_timer('zones',
+ self._zone_name.to_text(),
+ 'last_' + req_str.lower() +
+ '_duration')
# Make sure any remaining transaction in the diff is closed
# (if not yet - possible in case of xfr-level exception) as soon
# as possible
self._diff = None
-
return ret
def _check_response_header(self, msg):
@@ -1097,7 +1131,7 @@ def __process_xfrin(server, zone_name, rrclass, db_file,
if db_file is not None:
# temporary hardcoded sqlite initialization. Once we decide on
# the config specification, we need to update this (TODO)
- # this may depend on #1207, or any followup ticket created for #1207
+ # this may depend on #1207, or any follow-up ticket created for #1207
datasrc_type = "sqlite3"
datasrc_config = "{ \"database_file\": \"" + db_file + "\"}"
datasrc_client = DataSourceClient(datasrc_type, datasrc_config)
@@ -1339,6 +1373,7 @@ class Xfrin:
self._cc_setup()
self.recorder = XfrinRecorder()
self._shutdown_event = threading.Event()
+ self._counters = Counters(SPECFILE_LOCATION)
def _cc_setup(self):
'''This method is used only as part of initialization, but is
@@ -1484,6 +1519,7 @@ class Xfrin:
th.join()
def command_handler(self, command, args):
+ logger.debug(DBG_XFRIN_TRACE, XFRIN_RECEIVED_COMMAND, command)
answer = create_answer(0)
try:
if command == 'shutdown':
@@ -1552,6 +1588,14 @@ class Xfrin:
(False if command == 'retransfer' else True))
answer = create_answer(ret[0], ret[1])
+ # return statistics data to the stats daemon
+ elif command == "getstats":
+ # The log level is here set to debug in order to avoid
+ # that a log becomes too verbose. Because the
+ # b10-stats daemon is periodically asking to the
+ # b10-xfrin daemon.
+ answer = create_answer(0, self._counters.get_statistics())
+
else:
answer = create_answer(1, 'unknown command: ' + command)
except XfrinException as err:
diff --git a/src/bin/xfrin/xfrin.spec b/src/bin/xfrin/xfrin.spec
index 7ab1085..dc993f7 100644
--- a/src/bin/xfrin/xfrin.spec
+++ b/src/bin/xfrin/xfrin.spec
@@ -94,6 +94,119 @@
}
]
}
+ ],
+ "statistics": [
+ {
+ "item_name": "zones",
+ "item_type": "named_set",
+ "item_optional": false,
+ "item_default": {
+ "_SERVER_" : {
+ "soaoutv4": 0,
+ "soaoutv6": 0,
+ "axfrreqv4": 0,
+ "axfrreqv6": 0,
+ "ixfrreqv4": 0,
+ "ixfrreqv6": 0,
+ "xfrsuccess": 0,
+ "xfrfail": 0,
+ "last_ixfr_duration": 0.0,
+ "last_axfr_duration": 0.0
+ }
+ },
+ "item_title": "Zone names",
+ "item_description": "A directory name of per-zone statistics",
+ "named_set_item_spec": {
+ "item_name": "zonename",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "item_title": "Zone name",
+ "item_description": "An actual zone name or special zone name _SERVER_ representing the entire server. Zone classes (e.g. IN, CH, and HS) are mixed and counted so far. But these will be distinguished in future release.",
+ "map_item_spec": [
+ {
+ "item_name": "soaoutv4",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "SOAOutv4",
+ "item_description": "Number of IPv4 SOA queries sent from Xfrin"
+ },
+ {
+ "item_name": "soaoutv6",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "SOAOutv6",
+ "item_description": "Number of IPv6 SOA queries sent from Xfrin"
+ },
+ {
+ "item_name": "axfrreqv4",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "AXFRReqv4",
+ "item_description": "Number of IPv4 AXFR requests sent from Xfrin"
+ },
+ {
+ "item_name": "axfrreqv6",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "AXFRReqv6",
+ "item_description": "Number of IPv6 AXFR requests sent from Xfrin"
+ },
+ {
+ "item_name": "ixfrreqv4",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "IXFRReqv4",
+ "item_description": "Number of IPv4 IXFR requests sent from Xfrin"
+ },
+ {
+ "item_name": "ixfrreqv6",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "IXFRReqv6",
+ "item_description": "Number of IPv6 IXFR requests sent from Xfrin"
+ },
+ {
+ "item_name": "xfrsuccess",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "XfrSuccess",
+ "item_description": "Number of zone transfer requests succeeded. These include the case where the zone turns out to be the latest as a result of an initial SOA query (and there is actually no AXFR or IXFR transaction)."
+ },
+ {
+ "item_name": "xfrfail",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "XfrFail",
+ "item_description": "Number of zone transfer requests failed"
+ },
+ {
+ "item_name": "last_axfr_duration",
+ "item_type": "real",
+ "item_optional": false,
+ "item_default": 0.0,
+ "item_title": "Last AXFR duration",
+ "item_description": "Duration in seconds of the last successful AXFR. 0.0 means no successful AXFR done or means a successful AXFR done in less than a microsecond. If an AXFR is aborted due to some failure, this duration won't be updated."
+ },
+ {
+ "item_name": "last_ixfr_duration",
+ "item_type": "real",
+ "item_optional": false,
+ "item_default": 0.0,
+ "item_title": "Last IXFR duration",
+ "item_description": "Duration in seconds of the last successful IXFR. 0.0 means no successful IXFR done or means a successful IXFR done in less than a microsecond. If an IXFR is aborted due to some failure, this duration won't be updated."
+ }
+ ]
+ }
+ }
]
}
}
diff --git a/src/bin/xfrin/xfrin_messages.mes b/src/bin/xfrin/xfrin_messages.mes
index 1d90b75..eeddee9 100644
--- a/src/bin/xfrin/xfrin_messages.mes
+++ b/src/bin/xfrin/xfrin_messages.mes
@@ -135,6 +135,9 @@ from does not match the master address in the Xfrin configuration. The notify
is ignored. This may indicate that the configuration for the master is wrong,
that a wrong machine is sending notifies, or that fake notifies are being sent.
+% XFRIN_RECEIVED_COMMAND received command: %1
+The xfrin daemon received a command on the command channel.
+
% XFRIN_RETRANSFER_UNKNOWN_ZONE got notification to retransfer unknown zone %1
There was an internal command to retransfer the given zone, but the
zone is not known to the system. This may indicate that the configuration
diff --git a/src/bin/xfrout/tests/Makefile.am b/src/bin/xfrout/tests/Makefile.am
index ad6d7e6..acaee34 100644
--- a/src/bin/xfrout/tests/Makefile.am
+++ b/src/bin/xfrout/tests/Makefile.am
@@ -10,7 +10,7 @@ EXTRA_DIST += testdata/example.com testdata/creatediff.py
# required by loadable python modules.
LIBRARY_PATH_PLACEHOLDER =
if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/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/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)
+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)
else
# Some systems need the ds path even if not all paths are necessary
LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/datasrc/.libs
diff --git a/src/bin/xfrout/xfrout.py.in b/src/bin/xfrout/xfrout.py.in
index 2939dc0..1863ad0 100755
--- a/src/bin/xfrout/xfrout.py.in
+++ b/src/bin/xfrout/xfrout.py.in
@@ -502,7 +502,7 @@ class XfroutSession():
return self._reply_query_with_error_rcode(msg, sock_fd,
Rcode.FORMERR)
elif not quota_ok:
- logger.warn(XFROUT_QUERY_QUOTA_EXCCEEDED, self._request_typestr,
+ logger.warn(XFROUT_QUERY_QUOTA_EXCEEDED, self._request_typestr,
format_addrinfo(self._remote),
self._server._max_transfers_out)
return self._reply_query_with_error_rcode(msg, sock_fd,
diff --git a/src/bin/xfrout/xfrout_messages.mes b/src/bin/xfrout/xfrout_messages.mes
index d49981d..505eb7c 100644
--- a/src/bin/xfrout/xfrout_messages.mes
+++ b/src/bin/xfrout/xfrout_messages.mes
@@ -131,7 +131,7 @@ given host. This is required by the ACLs. The %2 represents the IP
address and port of the peer requesting the transfer, and the %3
represents the zone name and class.
-% XFROUT_QUERY_QUOTA_EXCCEEDED %1 client %2: request denied due to quota (%3)
+% XFROUT_QUERY_QUOTA_EXCEEDED %1 client %2: request denied due to quota (%3)
The xfr request was rejected because the server was already handling
the maximum number of allowable transfers as specified in the transfers_out
configuration parameter, which is also shown in the log message. The
diff --git a/src/bin/zonemgr/tests/Makefile.am b/src/bin/zonemgr/tests/Makefile.am
index b60fae7..72b842a 100644
--- a/src/bin/zonemgr/tests/Makefile.am
+++ b/src/bin/zonemgr/tests/Makefile.am
@@ -7,7 +7,7 @@ CLEANFILES = initdb.file
# 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/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/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:$$$(ENV_LIBRARY_PATH)
endif
# test using command-line arguments, so use check-local target instead of TESTS
diff --git a/src/bin/zonemgr/tests/zonemgr_test.py b/src/bin/zonemgr/tests/zonemgr_test.py
index 81c5392..111d650 100644
--- a/src/bin/zonemgr/tests/zonemgr_test.py
+++ b/src/bin/zonemgr/tests/zonemgr_test.py
@@ -314,16 +314,22 @@ class TestZonemgrRefresh(unittest.TestCase):
sqlite3_ds.get_zone_soa = old_get_zone_soa
def test_zone_handle_notify(self):
- self.zone_refresh.zone_handle_notify(ZONE_NAME_CLASS1_IN,"127.0.0.1")
- notify_master = self.zone_refresh._zonemgr_refresh_info[ZONE_NAME_CLASS1_IN]["notify_master"]
+ self.assertTrue(self.zone_refresh.zone_handle_notify(
+ ZONE_NAME_CLASS1_IN, "127.0.0.1"))
+ notify_master = self.zone_refresh.\
+ _zonemgr_refresh_info[ZONE_NAME_CLASS1_IN]["notify_master"]
self.assertEqual("127.0.0.1", notify_master)
- zone_timeout = self.zone_refresh._zonemgr_refresh_info[ZONE_NAME_CLASS1_IN]["next_refresh_time"]
+ zone_timeout = self.zone_refresh.\
+ _zonemgr_refresh_info[ZONE_NAME_CLASS1_IN]["next_refresh_time"]
current_time = time.time()
self.assertTrue(zone_timeout <= current_time)
- self.assertRaises(ZonemgrException, self.zone_refresh.zone_handle_notify,\
- ZONE_NAME_CLASS3_CH, "127.0.0.1")
- self.assertRaises(ZonemgrException, self.zone_refresh.zone_handle_notify,\
- ZONE_NAME_CLASS3_IN, "127.0.0.1")
+
+ # If the specified zone does not in the configured secondary list,
+ # it should return False.
+ self.assertFalse(self.zone_refresh.zone_handle_notify(
+ ZONE_NAME_CLASS3_CH, "127.0.0.1"))
+ self.assertFalse(self.zone_refresh.zone_handle_notify(
+ ZONE_NAME_CLASS3_IN, "127.0.0.1"))
def test_zone_refresh_success(self):
soa_rdata = 'a.example.net. root.example.net. 2009073106 1800 900 2419200 21600'
@@ -607,6 +613,19 @@ class TestZonemgrRefresh(unittest.TestCase):
config, self.cc_session)
class MyZonemgr(Zonemgr):
+ class DummySocket:
+ """This dummy class simply steal send() to record any transmitted data.
+
+ """
+ def __init__(self):
+ self.sent_data = []
+
+ def send(self, data):
+ self.sent_data.append(data)
+
+ class DummyLock:
+ def __enter__(self): pass
+ def __exit__(self, type, value, traceback): pass
def __init__(self):
self._db_file = TEST_SQLITE3_DBFILE
@@ -621,6 +640,8 @@ class MyZonemgr(Zonemgr):
"reload_jitter" : 0.75,
"secondary_zones": []
}
+ self._lock = self.DummyLock()
+ self._master_socket = self.DummySocket()
def _start_zone_refresh_timer(self):
pass
@@ -672,15 +693,21 @@ class TestZonemgr(unittest.TestCase):
self.assertEqual(TEST_SQLITE3_DBFILE, self.zonemgr.get_db_file())
def test_parse_cmd_params(self):
- params1 = {"zone_name" : "example.com.", "zone_class" : "CH", "master" : "127.0.0.1"}
+ params1 = {"zone_name" : "example.com.", "zone_class" : "CH",
+ "master" : "127.0.0.1"}
answer1 = (ZONE_NAME_CLASS3_CH, "127.0.0.1")
- self.assertEqual(answer1, self.zonemgr._parse_cmd_params(params1, ZONE_NOTIFY_COMMAND))
+ self.assertEqual(answer1,
+ self.zonemgr._parse_cmd_params(params1,
+ ZONE_NOTIFY_COMMAND))
params2 = {"zone_name" : "example.com.", "zone_class" : "IN"}
answer2 = ZONE_NAME_CLASS3_IN
- self.assertEqual(answer2, self.zonemgr._parse_cmd_params(params2, notify_out.ZONE_NEW_DATA_READY_CMD))
- self.assertRaises(ZonemgrException, self.zonemgr._parse_cmd_params, params2, ZONE_NOTIFY_COMMAND)
+ self.assertEqual(answer2, self.zonemgr._parse_cmd_params(
+ params2, notify_out.ZONE_NEW_DATA_READY_CMD))
+ self.assertRaises(ZonemgrException, self.zonemgr._parse_cmd_params,
+ params2, ZONE_NOTIFY_COMMAND)
params1 = {"zone_class" : "CH"}
- self.assertRaises(ZonemgrException, self.zonemgr._parse_cmd_params, params2, ZONE_NOTIFY_COMMAND)
+ self.assertRaises(ZonemgrException, self.zonemgr._parse_cmd_params,
+ params2, ZONE_NOTIFY_COMMAND)
def test_config_data_check(self):
# jitter should not be bigger than half of the original value
@@ -697,6 +724,26 @@ class TestZonemgr(unittest.TestCase):
self.zonemgr.run()
self.assertTrue(self.zonemgr._module_cc.stopped)
+ def test_command_handler_notify(self):
+ """Check the result of NOTIFY command."""
+ self.zonemgr._zone_refresh = MyZonemgrRefresh()
+
+ # On successful case, the other thread will be notified via
+ # _master_socket.
+ self.zonemgr._zone_refresh.zone_handle_notify = lambda x, y: True
+ self.zonemgr.command_handler("notify", {"zone_name": "example.",
+ "zone_class": "IN",
+ "master": "192.0.2.1"})
+ self.assertEqual([b" "], self.zonemgr._master_socket.sent_data)
+
+ # If the specified is not found in the secondary list, it doesn't
+ # bother to wake the thread (sent_data shouldn't change)
+ self.zonemgr._zone_refresh.zone_handle_notify = lambda x, y: False
+ self.zonemgr.command_handler("notify", {"zone_name": "example.",
+ "zone_class": "IN",
+ "master": "192.0.2.1"})
+ self.assertEqual([b" "], self.zonemgr._master_socket.sent_data)
+
if __name__== "__main__":
isc.log.resetUnitTestRootLogger()
unittest.main()
diff --git a/src/bin/zonemgr/zonemgr.py.in b/src/bin/zonemgr/zonemgr.py.in
index 59900c4..fcb929a 100755
--- a/src/bin/zonemgr/zonemgr.py.in
+++ b/src/bin/zonemgr/zonemgr.py.in
@@ -191,14 +191,31 @@ class ZonemgrRefresh:
self._set_zone_retry_timer(zone_name_class)
def zone_handle_notify(self, zone_name_class, master):
- """Handle zone notify"""
- if (self._zone_not_exist(zone_name_class)):
- logger.error(ZONEMGR_UNKNOWN_ZONE_NOTIFIED, zone_name_class[0],
- zone_name_class[1], master)
- raise ZonemgrException("[b10-zonemgr] Notified zone (%s, %s) "
- "doesn't belong to zonemgr" % zone_name_class)
+ """Handle an incoming NOTIFY message via the Auth module.
+
+ It returns True if the specified zone matches one of the locally
+ configured list of secondary zones; otherwise returns False.
+ In the latter case it assumes the server is a primary (master) of the
+ zone; the Auth module should have rejected the case where it's not
+ even authoritative for the zone.
+
+ Note: to be more robust and less independent from other module's
+ behavior, it's probably safer to check the authority condition here,
+ too. But right now it uses SQLite3 specific API (to be deprecated),
+ so we rather rely on Auth.
+
+ Parameters:
+ zone_name_class (Name, RRClass): the notified zone name and class.
+ master (str): textual address of the NOTIFY sender.
+
+ """
+ if self._zone_not_exist(zone_name_class):
+ logger.debug(DBG_ZONEMGR_BASIC, ZONEMGR_ZONE_NOTIFY_NOT_SECONDARY,
+ zone_name_class[0], zone_name_class[1], master)
+ return False
self._set_zone_notifier_master(zone_name_class, master)
self._set_zone_notify_timer(zone_name_class)
+ return True
def zonemgr_reload_zone(self, zone_name_class):
""" Reload a zone."""
@@ -423,7 +440,7 @@ class ZonemgrRefresh:
# Ask the thread to stop
self._running = False
- self._write_sock.send(b'shutdown') # make self._read_sock readble
+ self._write_sock.send(b'shutdown') # make self._read_sock readable
# Wait for it to actually finnish
self._thread.join()
# Wipe out what we do not need
@@ -630,27 +647,33 @@ class Zonemgr:
""" Handle Auth notify command"""
# master is the source sender of the notify message.
zone_name_class, master = self._parse_cmd_params(args, command)
- logger.debug(DBG_ZONEMGR_COMMAND, ZONEMGR_RECEIVE_NOTIFY, zone_name_class[0], zone_name_class[1])
+ logger.debug(DBG_ZONEMGR_COMMAND, ZONEMGR_RECEIVE_NOTIFY,
+ zone_name_class[0], zone_name_class[1])
with self._lock:
- self._zone_refresh.zone_handle_notify(zone_name_class, master)
- # Send notification to zonemgr timer thread
- self._master_socket.send(b" ")# make self._slave_socket readble
+ need_refresh = self._zone_refresh.zone_handle_notify(
+ zone_name_class, master)
+ if need_refresh:
+ # Send notification to zonemgr timer thread by making
+ # self._slave_socket readable.
+ self._master_socket.send(b" ")
elif command == notify_out.ZONE_NEW_DATA_READY_CMD:
""" Handle xfrin success command"""
zone_name_class = self._parse_cmd_params(args, command)
- logger.debug(DBG_ZONEMGR_COMMAND, ZONEMGR_RECEIVE_XFRIN_SUCCESS, zone_name_class[0], zone_name_class[1])
+ logger.debug(DBG_ZONEMGR_COMMAND, ZONEMGR_RECEIVE_XFRIN_SUCCESS,
+ zone_name_class[0], zone_name_class[1])
with self._lock:
self._zone_refresh.zone_refresh_success(zone_name_class)
- self._master_socket.send(b" ")# make self._slave_socket readble
+ self._master_socket.send(b" ")# make self._slave_socket readable
elif command == notify_out.ZONE_XFRIN_FAILED:
""" Handle xfrin fail command"""
zone_name_class = self._parse_cmd_params(args, command)
- logger.debug(DBG_ZONEMGR_COMMAND, ZONEMGR_RECEIVE_XFRIN_FAILED, zone_name_class[0], zone_name_class[1])
+ logger.debug(DBG_ZONEMGR_COMMAND, ZONEMGR_RECEIVE_XFRIN_FAILED,
+ zone_name_class[0], zone_name_class[1])
with self._lock:
self._zone_refresh.zone_refresh_fail(zone_name_class)
- self._master_socket.send(b" ")# make self._slave_socket readble
+ self._master_socket.send(b" ")# make self._slave_socket readable
elif command == "shutdown":
logger.debug(DBG_ZONEMGR_COMMAND, ZONEMGR_RECEIVE_SHUTDOWN)
@@ -667,7 +690,19 @@ class Zonemgr:
self.running = True
try:
while not self._shutdown_event.is_set():
- self._module_cc.check_command(False)
+ fileno = self._module_cc.get_socket().fileno()
+ # 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,
+ # but due to the way the zonemgr does threading, we
+ # can't have a blocking read loop here.
+ try:
+ (reads, _, _) = select.select([fileno], [], [])
+ except select.error as se:
+ if se.args[0] != errno.EINTR:
+ raise
+ if fileno in reads:
+ self._module_cc.check_command(True)
finally:
self._module_cc.send_stopping()
diff --git a/src/bin/zonemgr/zonemgr_messages.mes b/src/bin/zonemgr/zonemgr_messages.mes
index f67b5b9..0334858 100644
--- a/src/bin/zonemgr/zonemgr_messages.mes
+++ b/src/bin/zonemgr/zonemgr_messages.mes
@@ -138,14 +138,19 @@ zone, or, if this error appears without the administrator giving transfer
commands, it can indicate an error in the program, as it should not have
initiated transfers of unknown zones on its own.
-% ZONEMGR_UNKNOWN_ZONE_NOTIFIED notified zone %1/%2 from %3 is not known to the zone manager
-A NOTIFY was received but the zone that was the subject of the operation
-is not being managed by the zone manager. This may indicate an error
-in the program (as the operation should not have been initiated if this
-were the case). Please submit a bug report.
-
% ZONEMGR_UNKNOWN_ZONE_SUCCESS zone %1 (class %2) is not known to the zone manager
An XFRIN operation has succeeded but the zone received is not being
managed by the zone manager. This may indicate an error in the program
(as the operation should not have been initiated if this were the case).
Please submit a bug report.
+
+% ZONEMGR_ZONE_NOTIFY_NOT_SECONDARY notify for zone %1/%2 from %3 received but not in secondaries
+A NOTIFY was received but the zone is not listed in the configured
+secondary zones of the zone manager. The most common reason for this
+is that it's simply received by a primary server of the zone. Another
+possibility is a configuration error that it's not configured as a
+secondary while it should be. In either case, the zone manager does
+not take action in terms of zone management, and the authoritative
+server will respond to it like in the secondary case. If this is a
+configuration error, it will be noticed by the fact that the zone
+isn't updated even after a change is made in the primary server.
diff --git a/src/lib/acl/loader.h b/src/lib/acl/loader.h
index fc69b44..52fdc74 100644
--- a/src/lib/acl/loader.h
+++ b/src/lib/acl/loader.h
@@ -329,7 +329,7 @@ public:
const List &list(description->listValue());
boost::shared_ptr<ACL<Context, Action> > result(
new ACL<Context, Action>(default_action_));
- // Run trough the list of elements
+ // Run through the list of elements
for (List::const_iterator i(list.begin()); i != list.end(); ++i) {
Map map;
try {
@@ -417,7 +417,7 @@ private:
}
default: {
// This is the AND-abbreviated form. We need to create an
- // AND (or "ALL") operator, loop trough the whole map and
+ // AND (or "ALL") operator, loop through the whole map and
// fill it in. We do a small trick - we create bunch of
// single-item maps, call this loader recursively (therefore
// it will get into the "case 1" branch, where there is
diff --git a/src/lib/asiodns/asiodns_messages.mes b/src/lib/asiodns/asiodns_messages.mes
index db2902e..5f2a264 100644
--- a/src/lib/asiodns/asiodns_messages.mes
+++ b/src/lib/asiodns/asiodns_messages.mes
@@ -53,6 +53,78 @@ The asynchronous I/O code encountered an error when trying to send data to
the specified address on the given protocol. The number of the system
error that caused the problem is given in the message.
+% ASIODNS_SYNC_UDP_CLOSE_FAIL failed to close a DNS/UDP socket: %1
+This is the same to ASIODNS_UDP_CLOSE_FAIL but happens on the
+"synchronous UDP server", mainly used for the authoritative DNS server
+daemon.
+
+% ASIODNS_TCP_ACCEPT_FAIL failed to accept TCP DNS connection: %1
+Accepting a TCP connection from a DNS client failed due to an error
+that could happen but should be rare. The reason for the error is
+included in the log message. The server still keeps accepting new
+connections, so unless it happens often it's probably okay to ignore
+this error. If the shown error indicates something like "too many
+open files", it's probably because the run time environment is too
+restrictive on this limitation, so consider adjusing the limit using
+a tool such as ulimit. If you see other types of errors too often,
+there may be something overlooked; please file a bug report in that case.
+
+% ASIODNS_TCP_CLEANUP_CLOSE_FAIL failed to close a DNS/TCP socket on port cleanup: %1
+A TCP DNS server tried to close a TCP socket (one created on accepting
+a new connection or is already unused) as a step of cleaning up the
+corresponding listening port, but it failed to do that. This is
+generally an unexpected event and so is logged as an error.
+See also the description of ASIODNS_TCP_CLOSE_ACCEPTOR_FAIL.
+
+% ASIODNS_TCP_CLOSE_ACCEPTOR_FAIL failed to close listening TCP socket: %1
+A TCP DNS server tried to close a listening TCP socket (for accepting
+new connections) as a step of cleaning up the corresponding listening
+port (e.g., on server shutdown or updating port configuration), but it
+failed to do that. This is generally an unexpected event and so is
+logged as an error. See ASIODNS_TCP_CLOSE_FAIL on the implication of
+related system resources.
+
+% ASIODNS_TCP_CLOSE_FAIL failed to close DNS/TCP socket with a client: %1
+A TCP DNS server tried to close a TCP socket used to communicate with
+a client, but it failed to do that. While closing a socket should
+normally be an error-free operation, there have been known cases where
+this happened with a "connection reset by peer" error. This might be
+because of some odd client behavior, such as sending a TCP RST after
+establishing the connection and before the server closes the socket,
+but how exactly this could happen seems to be system dependent (i.e,
+it's not part of the standard socket API), so it's difficult to
+provide a general explanation. In any case, it is believed that an
+error on closing a socket doesn't mean leaking system resources (the
+kernel should clean up any internal resource related to the socket,
+just reporting an error detected in the close call), but, again, it
+seems to be system dependent. This message is logged at a debug level
+as it's known to happen and could be triggered by a remote node and it
+would be better to not be too verbose, but you might want to increase
+the log level and make sure there's no resource leak or other system
+level troubles when it's logged.
+
+% ASIODNS_TCP_GETREMOTE_FAIL failed to get remote address of a DNS TCP connection: %1
+A TCP DNS server tried to get the address and port of a remote client
+on a connected socket but failed. It's expected to be rare but can
+still happen. See also ASIODNS_TCP_READLEN_FAIL.
+
+% ASIODNS_TCP_READDATA_FAIL failed to get DNS data on a TCP socket: %1
+A TCP DNS server tried to read a DNS message (that follows a 2-byte
+length field) but failed. It's expected to be rare but can still happen.
+See also ASIODNS_TCP_READLEN_FAIL.
+
+% ASIODNS_TCP_READLEN_FAIL failed to get DNS data length on a TCP socket: %1
+A TCP DNS server tried to get the length field of a DNS message (the first
+2 bytes of a new chunk of data) but failed. This is generally expected to
+be rare but can still happen, e.g, due to an unexpected reset of the
+connection. A specific reason for the failure is included in the log
+message.
+
+% ASIODNS_TCP_WRITE_FAIL failed to send DNS message over a TCP socket: %1
+A TCP DNS server tried to send a DNS message to a remote client but
+failed. It's expected to be rare but can still happen. See also
+ASIODNS_TCP_READLEN_FAIL.
+
% ASIODNS_UDP_ASYNC_SEND_FAIL Error sending UDP packet to %1: %2
The low-level ASIO library reported an error when trying to send a UDP
packet in asynchronous UDP mode. This can be any error reported by
@@ -64,6 +136,23 @@ If you see a single occurrence of this message, it probably does not
indicate any significant problem, but if it is logged often, it is probably
a good idea to inspect your network traffic.
+% ASIODNS_UDP_CLOSE_FAIL failed to close a DNS/UDP socket: %1
+A UDP DNS server tried to close its UDP socket, but failed to do that.
+This is generally an unexpected event and so is logged as an error.
+
+% ASIODNS_UDP_RECEIVE_FAIL failed to receive UDP DNS packet: %1
+Receiving a UDP packet from a DNS client failed due to an error that
+could happen but should be very rare. The server still keeps
+receiving UDP packets on this socket. The reason for the error is
+included in the log message. This log message is basically not
+expected to appear at all in practice; if it does, there may be some
+system level failure and other system logs may have to be checked.
+
+% ASIODNS_UDP_SYNC_RECEIVE_FAIL failed to receive UDP DNS packet: %1
+This is the same to ASIODNS_UDP_RECEIVE_FAIL but happens on the
+"synchronous UDP server", mainly used for the authoritative DNS server
+daemon.
+
% ASIODNS_UDP_SYNC_SEND_FAIL Error sending UDP packet to %1: %2
The low-level ASIO library reported an error when trying to send a UDP
packet in synchronous UDP mode. See ASIODNS_UDP_ASYNC_SEND_FAIL for
diff --git a/src/lib/asiodns/dns_answer.h b/src/lib/asiodns/dns_answer.h
index 4b4576b..89896da 100644
--- a/src/lib/asiodns/dns_answer.h
+++ b/src/lib/asiodns/dns_answer.h
@@ -32,7 +32,7 @@ namespace asiodns {
/// via its constructor.
///
/// A DNS Answer provider function takes answer data that has been obtained
-/// from a DNS Lookup provider functon and readies it to be sent to the
+/// from a DNS Lookup provider function and readies it to be sent to the
/// client. After it has run, the OutputBuffer object passed to it should
/// contain the answer to the query rendered into wire format.
class DNSAnswer {
diff --git a/src/lib/asiodns/dns_server.h b/src/lib/asiodns/dns_server.h
index 3b8758f..35811c0 100644
--- a/src/lib/asiodns/dns_server.h
+++ b/src/lib/asiodns/dns_server.h
@@ -71,7 +71,7 @@ public:
/// without losing access to derived class data.
///
//@{
- /// \brief The funtion operator
+ /// \brief The function operator
virtual void operator()(asio::error_code ec = asio::error_code(),
size_t length = 0)
{
diff --git a/src/lib/asiodns/dns_service.cc b/src/lib/asiodns/dns_service.cc
index f72d24b..0324980 100644
--- a/src/lib/asiodns/dns_service.cc
+++ b/src/lib/asiodns/dns_service.cc
@@ -58,9 +58,15 @@ public:
template<class Ptr, class Server> void addServerFromFD(int fd, int af) {
Ptr server(new Server(io_service_.get_io_service(), fd, af, checkin_,
lookup_, answer_));
- server->setTCPRecvTimeout(tcp_recv_timeout_);
- (*server)();
- servers_.push_back(server);
+ startServer(server);
+ }
+
+ // SyncUDPServer has different constructor signature so it cannot be
+ // templated.
+ void addSyncUDPServerFromFD(int fd, int af) {
+ SyncUDPServerPtr server(new SyncUDPServer(io_service_.get_io_service(),
+ fd, af, lookup_));
+ startServer(server);
}
void setTCPRecvTimeout(size_t timeout) {
@@ -72,6 +78,13 @@ public:
(*it)->setTCPRecvTimeout(timeout);
}
}
+
+private:
+ void startServer(DNSServerPtr server) {
+ server->setTCPRecvTimeout(tcp_recv_timeout_);
+ (*server)();
+ servers_.push_back(server);
+ }
};
DNSService::DNSService(IOService& io_service, SimpleCallback* checkin,
@@ -95,8 +108,7 @@ void DNSService::addServerUDPFromFD(int fd, int af, ServerFlag options) {
<< options);
}
if ((options & SERVER_SYNC_OK) != 0) {
- impl_->addServerFromFD<DNSServiceImpl::SyncUDPServerPtr,
- SyncUDPServer>(fd, af);
+ impl_->addSyncUDPServerFromFD(fd, af);
} else {
impl_->addServerFromFD<DNSServiceImpl::UDPServerPtr, UDPServer>(
fd, af);
diff --git a/src/lib/asiodns/sync_udp_server.cc b/src/lib/asiodns/sync_udp_server.cc
index d5d2a7a..989484f 100644
--- a/src/lib/asiodns/sync_udp_server.cc
+++ b/src/lib/asiodns/sync_udp_server.cc
@@ -39,18 +39,21 @@ namespace isc {
namespace asiodns {
SyncUDPServer::SyncUDPServer(asio::io_service& io_service, const int fd,
- const int af, asiolink::SimpleCallback* checkin,
- DNSLookup* lookup, DNSAnswer* answer) :
+ const int af, DNSLookup* lookup) :
output_buffer_(new isc::util::OutputBuffer(0)),
query_(new isc::dns::Message(isc::dns::Message::PARSE)),
- answer_(new isc::dns::Message(isc::dns::Message::RENDER)),
- checkin_callback_(checkin), lookup_callback_(lookup),
- answer_callback_(answer), stopped_(false)
+ udp_endpoint_(sender_), lookup_callback_(lookup),
+ resume_called_(false), done_(false), stopped_(false),
+ recv_callback_(boost::bind(&SyncUDPServer::handleRead, this, _1, _2))
{
if (af != AF_INET && af != AF_INET6) {
isc_throw(InvalidParameter, "Address family must be either AF_INET "
"or AF_INET6, not " << af);
}
+ if (!lookup) {
+ isc_throw(InvalidParameter, "null lookup callback given to "
+ "SyncUDPServer");
+ }
LOG_DEBUG(logger, DBGLVL_TRACE_BASIC, ASIODNS_FD_ADD_UDP).arg(fd);
try {
socket_.reset(new asio::ip::udp::socket(io_service));
@@ -61,59 +64,45 @@ SyncUDPServer::SyncUDPServer(asio::io_service& io_service, const int fd,
// convert it
isc_throw(IOError, exception.what());
}
+ udp_socket_.reset(new UDPSocket<DummyIOCallback>(*socket_));
}
void
SyncUDPServer::scheduleRead() {
- socket_->async_receive_from(asio::buffer(data_, MAX_LENGTH), sender_,
- boost::bind(&SyncUDPServer::handleRead, this,
- _1, _2));
+ socket_->async_receive_from(asio::mutable_buffers_1(data_, MAX_LENGTH),
+ sender_, recv_callback_);
}
void
SyncUDPServer::handleRead(const asio::error_code& ec, const size_t length) {
- // Abort on fatal errors
+ // If the server has been stopped, it could even have been destroyed
+ // by the time of this call. We'll solve this problem in #2946, but
+ // until then we exit as soon as possible without accessing any other
+ // invalidated fields (note that referencing stopped_ is also incorrect,
+ // but experiments showed it often keeps the original value in practice,
+ // so we live with it until the complete fix).
+ if (stopped_) {
+ return;
+ }
if (ec) {
using namespace asio::error;
- if (ec.value() != would_block && ec.value() != try_again &&
- ec.value() != interrupted) {
+ const asio::error_code::value_type err_val = ec.value();
+
+ // See TCPServer::operator() for details on error handling.
+ if (err_val == operation_aborted || err_val == bad_descriptor) {
return;
}
+ if (err_val != would_block && err_val != try_again &&
+ err_val != interrupted) {
+ LOG_ERROR(logger, ASIODNS_UDP_SYNC_RECEIVE_FAIL).arg(ec.message());
+ }
}
- // Some kind of interrupt, spurious wakeup, or like that. Just try reading
- // again.
if (ec || length == 0) {
scheduleRead();
return;
}
// OK, we have a real packet of data. Let's dig into it!
- // XXX: This is taken (and ported) from UDPSocket class. What the hell does
- // it really mean?
-
- // The UDP socket class has been extended with asynchronous functions
- // and takes as a template parameter a completion callback class. As
- // UDPServer does not use these extended functions (only those defined
- // in the IOSocket base class) - but needs a UDPSocket to get hold of
- // the underlying Boost UDP socket - DummyIOCallback is used. This
- // provides the appropriate operator() but is otherwise functionless.
- UDPSocket<DummyIOCallback> socket(*socket_);
- UDPEndpoint endpoint(sender_);
- IOMessage message(data_, length, socket, endpoint);
- if (checkin_callback_ != NULL) {
- (*checkin_callback_)(message);
- if (stopped_) {
- return;
- }
- }
-
- // If we don't have a DNS Lookup provider, there's no point in
- // continuing; we exit the coroutine permanently.
- if (lookup_callback_ == NULL) {
- scheduleRead();
- return;
- }
-
// Make sure the buffers are fresh. Note that we don't touch query_
// because it's supposed to be cleared in lookup_callback_. We should
// eventually even remove this member variable (and remove it from
@@ -121,13 +110,13 @@ SyncUDPServer::handleRead(const asio::error_code& ec, const size_t length) {
// implementation should be careful that it's the responsibility of
// the callback implementation. See also #2239).
output_buffer_->clear();
- answer_->clear(isc::dns::Message::RENDER);
// Mark that we don't have an answer yet.
done_ = false;
resume_called_ = false;
// Call the actual lookup
+ const IOMessage message(data_, length, *udp_socket_, udp_endpoint_);
(*lookup_callback_)(message, query_, answer_, output_buffer_, this);
if (!resume_called_) {
@@ -135,27 +124,14 @@ SyncUDPServer::handleRead(const asio::error_code& ec, const size_t length) {
"No resume called from the lookup callback");
}
- if (stopped_) {
- return;
- }
-
if (done_) {
// Good, there's an answer.
- // Call the answer callback to render it.
- (*answer_callback_)(message, query_, answer_, output_buffer_);
-
- if (stopped_) {
- return;
- }
-
- asio::error_code ec;
- socket_->send_to(asio::buffer(output_buffer_->getData(),
- output_buffer_->getLength()),
- sender_, 0, ec);
- if (ec) {
+ socket_->send_to(asio::const_buffers_1(output_buffer_->getData(),
+ output_buffer_->getLength()),
+ sender_, 0, ec_);
+ if (ec_) {
LOG_ERROR(logger, ASIODNS_UDP_SYNC_SEND_FAIL).
- arg(sender_.address().to_string()).
- arg(ec.message());
+ arg(sender_.address().to_string()).arg(ec_.message());
}
}
@@ -181,13 +157,13 @@ SyncUDPServer::stop() {
/// for it won't be scheduled by io service not matter it is
/// submit to io service before or after close call. And we will
/// get bad_descriptor error.
- socket_->close();
+ socket_->close(ec_);
stopped_ = true;
+ if (ec_) {
+ LOG_ERROR(logger, ASIODNS_SYNC_UDP_CLOSE_FAIL).arg(ec_.message());
+ }
}
-/// Post this coroutine on the ASIO service queue so that it will
-/// resume processing where it left off. The 'done' parameter indicates
-/// whether there is an answer to return to the client.
void
SyncUDPServer::resume(const bool done) {
resume_called_ = true;
diff --git a/src/lib/asiodns/sync_udp_server.h b/src/lib/asiodns/sync_udp_server.h
index 14ec42a..cabc8bb 100644
--- a/src/lib/asiodns/sync_udp_server.h
+++ b/src/lib/asiodns/sync_udp_server.h
@@ -25,10 +25,14 @@
#include <dns/message.h>
#include <asiolink/simple_callback.h>
+#include <asiolink/dummy_io_cb.h>
+#include <asiolink/udp_socket.h>
#include <util/buffer.h>
#include <exceptions/exceptions.h>
+#include <boost/function.hpp>
#include <boost/noncopyable.hpp>
+#include <boost/scoped_ptr.hpp>
#include <stdint.h>
@@ -39,29 +43,39 @@ namespace asiodns {
///
/// That means, the lookup handler must provide the answer right away.
/// This allows for implementation with less overhead, compared with
-/// the UDPClass.
+/// the \c UDPServer class.
class SyncUDPServer : public DNSServer, public boost::noncopyable {
public:
/// \brief Constructor
+ ///
+ /// Due to the nature of this server, it's meaningless if the lookup
+ /// callback is NULL. So the constructor explicitly rejects that case
+ /// with an exception. Likewise, it doesn't take "checkin" or "answer"
+ /// callbacks. In fact, calling "checkin" from receive callback does not
+ /// make sense for any of the DNSServer variants (see Trac #2935);
+ /// "answer" callback is simply unnecessary for this class because a
+ /// complete answer is built in the lookup callback (it's the user's
+ /// responsibility to guarantee that condition).
+ ///
/// \param io_service the asio::io_service to work with
/// \param fd the file descriptor of opened UDP socket
/// \param af address family, either AF_INET or AF_INET6
- /// \param checkin the callbackprovider for non-DNS events
- /// \param lookup the callbackprovider for DNS lookup events
- /// \param answer the callbackprovider for DNS answer events
+ /// \param lookup the callbackprovider for DNS lookup events (must not be
+ /// NULL)
+ ///
/// \throw isc::InvalidParameter if af is neither AF_INET nor AF_INET6
+ /// \throw isc::InvalidParameter lookup is NULL
/// \throw isc::asiolink::IOError when a low-level error happens, like the
/// fd is not a valid descriptor.
SyncUDPServer(asio::io_service& io_service, const int fd, const int af,
- isc::asiolink::SimpleCallback* checkin = NULL,
- DNSLookup* lookup = NULL, DNSAnswer* answer = NULL);
+ DNSLookup* lookup);
/// \brief Start the SyncUDPServer.
///
/// This is the function operator to keep interface with other server
/// classes. They need that because they're coroutines.
virtual void operator()(asio::error_code ec = asio::error_code(),
- size_t length = 0);
+ size_t length = 0);
/// \brief Calls the lookup callback
virtual void asyncLookup() {
@@ -114,22 +128,52 @@ private:
// If it was OK to have just a buffer, not the wrapper class,
// we could reuse the data_
isc::util::OutputBufferPtr output_buffer_;
- // Objects to hold the query message and the answer
+ // Objects to hold the query message and the answer. The latter isn't
+ // used and only defined as a placeholder as the callback signature
+ // requires it.
isc::dns::MessagePtr query_, answer_;
// The socket used for the communication
std::auto_ptr<asio::ip::udp::socket> socket_;
+ // Wrapper of socket_ in the form of asiolink::IOSocket.
+ // "DummyIOCallback" is not necessary for this class, but using the
+ // template is the easiest way to create a UDP instance of IOSocket.
+ boost::scoped_ptr<asiolink::UDPSocket<asiolink::DummyIOCallback> >
+ udp_socket_;
// Place the socket puts the sender of a packet when it is received
asio::ip::udp::endpoint sender_;
- // Callbacks
- const asiolink::SimpleCallback* checkin_callback_;
+ // Wrapper of sender_ in the form of asiolink::IOEndpoint. It's set to
+ // refer to sender_ on initialization, and keeps the reference throughout
+ // this server class.
+ asiolink::UDPEndpoint udp_endpoint_;
+ // Callback
const DNSLookup* lookup_callback_;
- const DNSAnswer* answer_callback_;
// Answers from the lookup callback (not sent directly, but signalled
// through resume()
bool resume_called_, done_;
// This turns true when the server stops. Allows for not sending the
// answer after we closed the socket.
bool stopped_;
+ // Placeholder for error code object. It will be passed to ASIO library
+ // to have it set in case of error.
+ asio::error_code ec_;
+ // The callback functor for internal asynchronous read event. This is
+ // stateless (and it will be copied in the ASIO library anyway), so
+ // can be const.
+ // SunStudio doesn't like a boost::function object to be passed, so
+ // we use the wrapper class as a workaround.
+ class CallbackWrapper {
+ public:
+ CallbackWrapper(boost::function<void(const asio::error_code&, size_t)>
+ callback) :
+ callback_(callback)
+ {}
+ void operator()(const asio::error_code& error, size_t len) {
+ callback_(error, len);
+ }
+ private:
+ boost::function<void(const asio::error_code&, size_t)> callback_;
+ };
+ const CallbackWrapper recv_callback_;
// Auxiliary functions
@@ -144,3 +188,7 @@ private:
} // namespace asiodns
} // namespace isc
#endif // SYNC_UDP_SERVER_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/asiodns/tcp_server.cc b/src/lib/asiodns/tcp_server.cc
index 397e004..32a43ce 100644
--- a/src/lib/asiodns/tcp_server.cc
+++ b/src/lib/asiodns/tcp_server.cc
@@ -14,13 +14,6 @@
#include <config.h>
-#include <unistd.h> // for some IPC/network system calls
-#include <netinet/in.h>
-#include <sys/socket.h>
-#include <errno.h>
-
-#include <boost/shared_array.hpp>
-
#include <log/dummylog.h>
#include <util/buffer.h>
@@ -32,6 +25,14 @@
#include <asiodns/tcp_server.h>
#include <asiodns/logger.h>
+#include <boost/shared_array.hpp>
+
+#include <cassert>
+#include <unistd.h> // for some IPC/network system calls
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <errno.h>
+
using namespace asio;
using asio::ip::udp;
using asio::ip::tcp;
@@ -100,41 +101,58 @@ TCPServer::operator()(asio::error_code ec, size_t length) {
CORO_REENTER (this) {
do {
- /// Create a socket to listen for connections
+ /// Create a socket to listen for connections (no-throw operation)
socket_.reset(new tcp::socket(acceptor_->get_io_service()));
/// Wait for new connections. In the event of non-fatal error,
/// try again
do {
CORO_YIELD acceptor_->async_accept(*socket_, *this);
-
- // Abort on fatal errors
- // TODO: Log error?
if (ec) {
using namespace asio::error;
- if (ec.value() != would_block && ec.value() != try_again &&
- ec.value() != connection_aborted &&
- ec.value() != interrupted) {
+ const error_code::value_type err_val = ec.value();
+ // The following two cases can happen when this server is
+ // stopped: operation_aborted in case it's stopped after
+ // starting accept(). bad_descriptor in case it's stopped
+ // even before starting. In these cases we should simply
+ // stop handling events.
+ if (err_val == operation_aborted ||
+ err_val == bad_descriptor) {
return;
}
+ // Other errors should generally be temporary and we should
+ // keep waiting for new connections. We log errors that
+ // should really be rare and would only be caused by an
+ // internal erroneous condition (not an odd remote
+ // behavior).
+ if (err_val != would_block && err_val != try_again &&
+ err_val != connection_aborted &&
+ err_val != interrupted) {
+ LOG_ERROR(logger, ASIODNS_TCP_ACCEPT_FAIL).
+ arg(ec.message());
+ }
}
} while (ec);
/// Fork the coroutine by creating a copy of this one and
/// scheduling it on the ASIO service queue. The parent
- /// will continue listening for DNS connections while the
+ /// will continue listening for DNS connections while the child
/// handles the one that has just arrived.
CORO_FORK io_.post(TCPServer(*this));
} while (is_parent());
+ // From this point, we'll simply return on error, which will
+ // immediately trigger destroying this object, cleaning up all
+ // resources including any open sockets.
+
/// Instantiate the data buffer that will be used by the
/// asynchronous read call.
data_.reset(new char[MAX_LENGTH]);
/// Start a timer to drop the connection if it is idle
if (*tcp_recv_timeout_ > 0) {
- timeout_.reset(new asio::deadline_timer(io_));
- timeout_->expires_from_now(
+ timeout_.reset(new asio::deadline_timer(io_)); // shouldn't throw
+ timeout_->expires_from_now( // consider any exception fatal.
boost::posix_time::milliseconds(*tcp_recv_timeout_));
timeout_->async_wait(boost::bind(&do_timeout, boost::ref(*socket_),
asio::placeholders::error));
@@ -144,29 +162,22 @@ TCPServer::operator()(asio::error_code ec, size_t length) {
CORO_YIELD async_read(*socket_, asio::buffer(data_.get(),
TCP_MESSAGE_LENGTHSIZE), *this);
if (ec) {
- socket_->close();
- CORO_YIELD return;
+ LOG_DEBUG(logger, DBGLVL_TRACE_BASIC, ASIODNS_TCP_READLEN_FAIL).
+ arg(ec.message());
+ return;
}
/// Now read the message itself. (This is done in a different scope
/// to allow inline variable declarations.)
CORO_YIELD {
InputBuffer dnsbuffer(data_.get(), length);
- uint16_t msglen = dnsbuffer.readUint16();
+ const uint16_t msglen = dnsbuffer.readUint16();
async_read(*socket_, asio::buffer(data_.get(), msglen), *this);
}
-
if (ec) {
- socket_->close();
- CORO_YIELD return;
- }
-
- // Due to possible timeouts and other bad behaviour, after the
- // timely reads are done, there is a chance the socket has
- // been closed already. So before we move on to the actual
- // processing, check that, and stop if so.
- if (!socket_->is_open()) {
- CORO_YIELD return;
+ LOG_DEBUG(logger, DBGLVL_TRACE_BASIC, ASIODNS_TCP_READDATA_FAIL).
+ arg(ec.message());
+ return;
}
// Create an \c IOMessage object to store the query.
@@ -174,7 +185,12 @@ TCPServer::operator()(asio::error_code ec, size_t length) {
// (XXX: It would be good to write a factory function
// that would quickly generate an IOMessage object without
// all these calls to "new".)
- peer_.reset(new TCPEndpoint(socket_->remote_endpoint()));
+ peer_.reset(new TCPEndpoint(socket_->remote_endpoint(ec)));
+ if (ec) {
+ LOG_DEBUG(logger, DBGLVL_TRACE_BASIC, ASIODNS_TCP_GETREMOTE_FAIL).
+ arg(ec.message());
+ return;
+ }
// The TCP socket class has been extended with asynchronous functions
// and takes as a template parameter a completion callback class. As
@@ -183,7 +199,8 @@ TCPServer::operator()(asio::error_code ec, size_t length) {
// the underlying Boost TCP socket - DummyIOCallback is used. This
// provides the appropriate operator() but is otherwise functionless.
iosock_.reset(new TCPSocket<DummyIOCallback>(*socket_));
- io_message_.reset(new IOMessage(data_.get(), length, *iosock_, *peer_));
+ io_message_.reset(new IOMessage(data_.get(), length, *iosock_,
+ *peer_));
// Perform any necessary operations prior to processing the incoming
// packet (e.g., checking for queued configuration messages).
@@ -198,8 +215,7 @@ TCPServer::operator()(asio::error_code ec, size_t length) {
// If we don't have a DNS Lookup provider, there's no point in
// continuing; we exit the coroutine permanently.
if (lookup_callback_ == NULL) {
- socket_->close();
- CORO_YIELD return;
+ return;
}
// Reset or instantiate objects that will be needed by the
@@ -210,25 +226,24 @@ TCPServer::operator()(asio::error_code ec, size_t length) {
// Schedule a DNS lookup, and yield. When the lookup is
// finished, the coroutine will resume immediately after
- // this point.
+ // this point. On resume, this method should be called with its
+ // default parameter values (because of the signature of post()'s
+ // handler), so ec shouldn't indicate any error.
CORO_YIELD io_.post(AsyncLookup<TCPServer>(*this));
+ assert(!ec);
// The 'done_' flag indicates whether we have an answer
// to send back. If not, exit the coroutine permanently.
if (!done_) {
// TODO: should we keep the connection open for a short time
// to see if new requests come in?
- socket_->close();
- CORO_YIELD return;
+ return;
}
- if (ec) {
- CORO_YIELD return;
- }
// Call the DNS answer provider to render the answer into
// wire format
- (*answer_callback_)(*io_message_, query_message_,
- answer_message_, respbuf_);
+ (*answer_callback_)(*io_message_, query_message_, answer_message_,
+ respbuf_);
// Set up the response, beginning with two length bytes.
lenbuf.writeUint16(respbuf_->getLength());
@@ -240,13 +255,22 @@ TCPServer::operator()(asio::error_code ec, size_t length) {
// (though we have nothing further to do, so the coroutine
// will simply exit at that time).
CORO_YIELD async_write(*socket_, bufs, *this);
+ if (ec) {
+ LOG_DEBUG(logger, DBGLVL_TRACE_BASIC, ASIODNS_TCP_WRITE_FAIL).
+ arg(ec.message());
+ }
- // All done, cancel the timeout timer
+ // All done, cancel the timeout timer. if it throws, consider it fatal.
timeout_->cancel();
// TODO: should we keep the connection open for a short time
// to see if new requests come in?
- socket_->close();
+ socket_->close(ec);
+ if (ec) {
+ // close() should be unlikely to fail, but we've seen it fail once,
+ // so we log the event (at the lowest level of debug).
+ LOG_DEBUG(logger, 0, ASIODNS_TCP_CLOSE_FAIL).arg(ec.message());
+ }
}
}
@@ -259,14 +283,23 @@ TCPServer::asyncLookup() {
}
void TCPServer::stop() {
+ asio::error_code ec;
+
/// we use close instead of cancel, with the same reason
/// with udp server stop, refer to the udp server code
- acceptor_->close();
+ acceptor_->close(ec);
+ if (ec) {
+ LOG_ERROR(logger, ASIODNS_TCP_CLOSE_ACCEPTOR_FAIL).arg(ec.message());
+ }
+
// User may stop the server even when it hasn't started to
- // run, in that that socket_ is empty
+ // run, in that case socket_ is empty
if (socket_) {
- socket_->close();
+ socket_->close(ec);
+ if (ec) {
+ LOG_ERROR(logger, ASIODNS_TCP_CLEANUP_CLOSE_FAIL).arg(ec.message());
+ }
}
}
/// Post this coroutine on the ASIO service queue so that it will
@@ -275,6 +308,10 @@ void TCPServer::stop() {
void
TCPServer::resume(const bool done) {
done_ = done;
+
+ // post() can throw due to memory allocation failure, but as like other
+ // cases of the entire BIND 10 implementation, we consider it fatal and
+ // let the exception be propagated.
io_.post(*this);
}
diff --git a/src/lib/asiodns/tests/dns_server_unittest.cc b/src/lib/asiodns/tests/dns_server_unittest.cc
index 51fb6b8..dd92dcb 100644
--- a/src/lib/asiodns/tests/dns_server_unittest.cc
+++ b/src/lib/asiodns/tests/dns_server_unittest.cc
@@ -117,7 +117,7 @@ public:
DummyLookup() :
allow_resume_(true)
{ }
- void operator()(const IOMessage& io_message,
+ virtual void operator()(const IOMessage& io_message,
isc::dns::MessagePtr message,
isc::dns::MessagePtr answer_message,
isc::util::OutputBufferPtr buffer,
@@ -147,6 +147,24 @@ class SimpleAnswer : public DNSAnswer, public ServerStopper {
};
+/// \brief Mixture of DummyLookup and SimpleAnswer: build the answer in the
+/// lookup callback. Used with SyncUDPServer.
+class SyncDummyLookup : public DummyLookup {
+public:
+ virtual void operator()(const IOMessage& io_message,
+ isc::dns::MessagePtr message,
+ isc::dns::MessagePtr answer_message,
+ isc::util::OutputBufferPtr buffer,
+ DNSServer* server) const
+ {
+ buffer->writeData(io_message.getData(), io_message.getDataSize());
+ stopServer();
+ if (allow_resume_) {
+ server->resume(true);
+ }
+ }
+};
+
// \brief simple client, send one string to server and wait for response
// in case, server stopped and client can't get response, there is a timer wait
// for specified seconds (the value is just a estimate since server process logic is quite
@@ -353,6 +371,7 @@ class DNSServerTestBase : public::testing::Test {
server_address_(ip::address::from_string(server_ip)),
checker_(new DummyChecker()),
lookup_(new DummyLookup()),
+ sync_lookup_(new SyncDummyLookup()),
answer_(new SimpleAnswer()),
udp_client_(new UDPClient(service,
ip::udp::endpoint(server_address_,
@@ -375,6 +394,7 @@ class DNSServerTestBase : public::testing::Test {
}
delete checker_;
delete lookup_;
+ delete sync_lookup_;
delete answer_;
delete udp_server_;
delete udp_client_;
@@ -421,7 +441,8 @@ class DNSServerTestBase : public::testing::Test {
asio::io_service service;
const ip::address server_address_;
DummyChecker* const checker_;
- DummyLookup* const lookup_;
+ DummyLookup* lookup_; // we need to replace it in some cases
+ SyncDummyLookup* const sync_lookup_;
SimpleAnswer* const answer_;
UDPClient* const udp_client_;
TCPClient* const tcp_client_;
@@ -482,19 +503,34 @@ private:
protected:
// Using SetUp here so we can ASSERT_*
void SetUp() {
- const int fdUDP(getFd(SOCK_DGRAM));
- ASSERT_NE(-1, fdUDP) << strerror(errno);
- this->udp_server_ = new UDPServerClass(this->service, fdUDP, AF_INET6,
- this->checker_, this->lookup_,
- this->answer_);
- const int fdTCP(getFd(SOCK_STREAM));
- ASSERT_NE(-1, fdTCP) << strerror(errno);
- this->tcp_server_ = new TCPServer(this->service, fdTCP, AF_INET6,
+ const int fd_udp(getFd(SOCK_DGRAM));
+ ASSERT_NE(-1, fd_udp) << strerror(errno);
+ this->udp_server_ = createServer(fd_udp, AF_INET6);
+ const int fd_tcp(getFd(SOCK_STREAM));
+ ASSERT_NE(-1, fd_tcp) << strerror(errno);
+ this->tcp_server_ = new TCPServer(this->service, fd_tcp, AF_INET6,
this->checker_, this->lookup_,
this->answer_);
}
+
+ // A helper factory of the tested UDP server class: allow customization
+ // by template specialization.
+ UDPServerClass* createServer(int fd, int af) {
+ return (new UDPServerClass(this->service, fd, af,
+ this->checker_, this->lookup_,
+ this->answer_));
+ }
};
+// Specialization for SyncUDPServer. It needs to use SyncDummyLookup.
+template<>
+SyncUDPServer*
+FdInit<SyncUDPServer>::createServer(int fd, int af) {
+ delete this->lookup_;
+ this->lookup_ = new SyncDummyLookup;
+ return (new SyncUDPServer(this->service, fd, af, this->lookup_));
+}
+
// This makes it the template as gtest wants it.
template<class Parent>
class DNSServerTest : public Parent { };
@@ -503,6 +539,11 @@ typedef ::testing::Types<FdInit<UDPServer>, FdInit<SyncUDPServer> >
ServerTypes;
TYPED_TEST_CASE(DNSServerTest, ServerTypes);
+// Some tests work only for SyncUDPServer, some others work only for
+// (non Sync)UDPServer. We specialize these tests.
+typedef FdInit<UDPServer> AsyncServerTest;
+typedef FdInit<SyncUDPServer> SyncServerTest;
+
typedef ::testing::Types<UDPServer, SyncUDPServer> UDPServerTypes;
TYPED_TEST_CASE(DNSServerTestBase, UDPServerTypes);
@@ -513,7 +554,7 @@ asio::io_service* DNSServerTestBase<UDPServerClass>::current_service(NULL);
// Test whether server stopped successfully after client get response
// client will send query and start to wait for response, once client
-// get response, udp server will be stopped, the io service won't quit
+// get response, UDP server will be stopped, the io service won't quit
// if udp server doesn't stop successfully.
TYPED_TEST(DNSServerTest, stopUDPServerAfterOneQuery) {
this->testStopServerByStopper(this->udp_server_, this->udp_client_,
@@ -532,8 +573,10 @@ TYPED_TEST(DNSServerTest, stopUDPServerBeforeItStartServing) {
}
-// Test whether udp server stopped successfully during message check
-TYPED_TEST(DNSServerTest, stopUDPServerDuringMessageCheck) {
+// Test whether udp server stopped successfully during message check.
+// This only works for non-sync server; SyncUDPServer doesn't use checkin
+// callback.
+TEST_F(AsyncServerTest, stopUDPServerDuringMessageCheck) {
this->testStopServerByStopper(this->udp_server_, this->udp_client_,
this->checker_);
EXPECT_EQ(std::string(""), this->udp_client_->getReceivedData());
@@ -548,12 +591,13 @@ TYPED_TEST(DNSServerTest, stopUDPServerDuringQueryLookup) {
EXPECT_TRUE(this->serverStopSucceed());
}
-// Test whether udp server stopped successfully during composing answer
-TYPED_TEST(DNSServerTest, stopUDPServerDuringPrepareAnswer) {
- this->testStopServerByStopper(this->udp_server_, this->udp_client_,
- this->answer_);
- EXPECT_EQ(std::string(""), this->udp_client_->getReceivedData());
- EXPECT_TRUE(this->serverStopSucceed());
+// Test whether UDP server stopped successfully during composing answer.
+// Only works for (non-sync) server because SyncUDPServer doesn't use answer
+// callback.
+TEST_F(AsyncServerTest, stopUDPServerDuringPrepareAnswer) {
+ testStopServerByStopper(udp_server_, udp_client_, answer_);
+ EXPECT_EQ(std::string(""), udp_client_->getReceivedData());
+ EXPECT_TRUE(serverStopSucceed());
}
void
@@ -565,7 +609,7 @@ stopServerManyTimes(DNSServer *server, unsigned int times) {
// Test whether udp server stop interface can be invoked several times without
// throw any exception
-TYPED_TEST(DNSServerTest, stopUDPServeMoreThanOnce) {
+TYPED_TEST(DNSServerTest, stopUDPServerMoreThanOnce) {
ASSERT_NO_THROW({
boost::function<void()> stop_server_3_times
= boost::bind(stopServerManyTimes, this->udp_server_, 3);
@@ -668,14 +712,15 @@ TYPED_TEST(DNSServerTest, stopTCPServeMoreThanOnce) {
TYPED_TEST(DNSServerTestBase, invalidFamily) {
// We abuse DNSServerTestBase for this test, as we don't need the
// initialization.
- EXPECT_THROW(TypeParam(this->service, 0, AF_UNIX, this->checker_,
- this->lookup_, this->answer_),
- isc::InvalidParameter);
EXPECT_THROW(TCPServer(this->service, 0, AF_UNIX, this->checker_,
this->lookup_, this->answer_),
isc::InvalidParameter);
}
+TYPED_TEST(DNSServerTest, invalidFamilyUDP) {
+ EXPECT_THROW(this->createServer(0, AF_UNIX), isc::InvalidParameter);
+}
+
// It raises an exception when invalid address family is passed
TYPED_TEST(DNSServerTestBase, invalidTCPFD) {
// We abuse DNSServerTestBase for this test, as we don't need the
@@ -694,7 +739,7 @@ TYPED_TEST(DNSServerTestBase, invalidTCPFD) {
isc::asiolink::IOError);
}
-TYPED_TEST(DNSServerTestBase, DISABLED_invalidUDPFD) {
+TYPED_TEST(DNSServerTest, DISABLED_invalidUDPFD) {
/*
FIXME: The UDP server doesn't fail reliably with an invalid FD.
We need to find a way to trigger it reliably (it seems epoll
@@ -702,14 +747,9 @@ TYPED_TEST(DNSServerTestBase, DISABLED_invalidUDPFD) {
not the others, maybe we could make it run this at least on epoll-based
systems).
*/
- EXPECT_THROW(TypeParam(this->service, -1, AF_INET, this->checker_,
- this->lookup_, this->answer_),
- isc::asiolink::IOError);
+ EXPECT_THROW(this->createServer(-1, AF_INET), isc::asiolink::IOError);
}
-// A specialized test type for SyncUDPServer.
-typedef FdInit<SyncUDPServer> SyncServerTest;
-
// Check it rejects some of the unsupported operations
TEST_F(SyncServerTest, unsupportedOps) {
EXPECT_THROW(udp_server_->clone(), isc::Unexpected);
@@ -723,4 +763,10 @@ TEST_F(SyncServerTest, mustResume) {
isc::Unexpected);
}
+// SyncUDPServer doesn't allow NULL lookup callback.
+TEST_F(SyncServerTest, nullLookupCallback) {
+ EXPECT_THROW(SyncUDPServer(service, 0, AF_INET, NULL),
+ isc::InvalidParameter);
+}
+
}
diff --git a/src/lib/asiodns/udp_server.cc b/src/lib/asiodns/udp_server.cc
index f84a4d6..7221296 100644
--- a/src/lib/asiodns/udp_server.cc
+++ b/src/lib/asiodns/udp_server.cc
@@ -82,8 +82,8 @@ struct UDPServer::Data {
answer_callback_(answer)
{
if (af != AF_INET && af != AF_INET6) {
- isc_throw(InvalidParameter, "Address family must be either AF_INET "
- "or AF_INET6, not " << af);
+ isc_throw(InvalidParameter, "Address family must be either AF_INET"
+ " or AF_INET6, not " << af);
}
LOG_DEBUG(logger, DBGLVL_TRACE_BASIC, ASIODNS_FD_ADD_UDP).arg(fd);
try {
@@ -212,14 +212,19 @@ UDPServer::operator()(asio::error_code ec, size_t length) {
buffer(data_->data_.get(), MAX_LENGTH), *data_->sender_,
*this);
- // Abort on fatal errors
- // TODO: add log
+ // See TCPServer::operator() for details on error handling.
if (ec) {
using namespace asio::error;
- if (ec.value() != would_block && ec.value() != try_again &&
- ec.value() != interrupted) {
+ const error_code::value_type err_val = ec.value();
+ if (err_val == operation_aborted ||
+ err_val == bad_descriptor) {
return;
}
+ if (err_val != would_block && err_val != try_again &&
+ err_val != interrupted) {
+ LOG_ERROR(logger, ASIODNS_UDP_RECEIVE_FAIL).
+ arg(ec.message());
+ }
}
} while (ec || length == 0);
@@ -270,7 +275,7 @@ UDPServer::operator()(asio::error_code ec, size_t length) {
// If we don't have a DNS Lookup provider, there's no point in
// continuing; we exit the coroutine permanently.
if (data_->lookup_callback_ == NULL) {
- CORO_YIELD return;
+ return;
}
// Instantiate objects that will be needed by the
@@ -287,7 +292,7 @@ UDPServer::operator()(asio::error_code ec, size_t length) {
// The 'done_' flag indicates whether we have an answer
// to send back. If not, exit the coroutine permanently.
if (!data_->done_) {
- CORO_YIELD return;
+ return;
}
// Call the DNS answer provider to render the answer into
@@ -322,6 +327,8 @@ UDPServer::asyncLookup() {
/// Stop the UDPServer
void
UDPServer::stop() {
+ asio::error_code ec;
+
/// Using close instead of cancel, because cancel
/// will only cancel the asynchronized event already submitted
/// to io service, the events post to io service after
@@ -330,7 +337,10 @@ UDPServer::stop() {
/// for it won't be scheduled by io service not matter it is
/// submit to io service before or after close call. And we will
// get bad_descriptor error.
- data_->socket_->close();
+ data_->socket_->close(ec);
+ if (ec) {
+ LOG_ERROR(logger, ASIODNS_UDP_CLOSE_FAIL).arg(ec.message());
+ }
}
/// Post this coroutine on the ASIO service queue so that it will
@@ -339,7 +349,7 @@ UDPServer::stop() {
void
UDPServer::resume(const bool done) {
data_->done_ = done;
- data_->io_.post(*this);
+ data_->io_.post(*this); // this can throw, but can be considered fatal.
}
} // namespace asiodns
diff --git a/src/lib/asiolink/io_service.cc b/src/lib/asiolink/io_service.cc
index 15fad0c..df08316 100644
--- a/src/lib/asiolink/io_service.cc
+++ b/src/lib/asiolink/io_service.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011,2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -24,6 +24,23 @@
namespace isc {
namespace asiolink {
+namespace {
+// A trivial wrapper for boost::function. SunStudio doesn't seem to be capable
+// of handling a boost::function object if directly passed to
+// io_service::post().
+class CallbackWrapper {
+public:
+ CallbackWrapper(const boost::function<void()>& callback) :
+ callback_(callback)
+ {}
+ void operator()() {
+ callback_();
+ }
+private:
+ boost::function<void()> callback_;
+};
+}
+
class IOServiceImpl {
private:
IOServiceImpl(const IOService& source);
@@ -63,6 +80,10 @@ public:
/// It will eventually be removed once the wrapper interface is
/// generalized.
asio::io_service& get_io_service() { return io_service_; };
+ void post(const boost::function<void ()>& callback) {
+ const CallbackWrapper wrapper(callback);
+ io_service_.post(wrapper);
+ }
private:
asio::io_service io_service_;
asio::io_service::work work_;
@@ -96,5 +117,10 @@ IOService::get_io_service() {
return (io_impl_->get_io_service());
}
+void
+IOService::post(const boost::function<void ()>& callback) {
+ return (io_impl_->post(callback));
+}
+
} // namespace asiolink
} // namespace isc
diff --git a/src/lib/asiolink/io_service.h b/src/lib/asiolink/io_service.h
index e0198dd..e086997 100644
--- a/src/lib/asiolink/io_service.h
+++ b/src/lib/asiolink/io_service.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011,2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -15,6 +15,8 @@
#ifndef ASIOLINK_IO_SERVICE_H
#define ASIOLINK_IO_SERVICE_H 1
+#include <boost/function.hpp>
+
namespace asio {
class io_service;
}
@@ -70,6 +72,17 @@ public:
/// generalized.
asio::io_service& get_io_service();
+ /// \brief Post a callback to the end of the queue.
+ ///
+ /// Requests the callback be called sometime later. It is not guaranteed
+ /// by the underlying asio, but it can reasonably be expected the callback
+ /// is put to the end of the callback queue. It is not called from within
+ /// this function.
+ ///
+ /// It may be used to implement "background" work, for example (doing stuff
+ /// by small bits that are called from time to time).
+ void post(const boost::function<void ()>& callback);
+
private:
IOServiceImpl* io_impl_;
};
diff --git a/src/lib/asiolink/tcp_endpoint.h b/src/lib/asiolink/tcp_endpoint.h
index 3d6a87a..2fe64f3 100644
--- a/src/lib/asiolink/tcp_endpoint.h
+++ b/src/lib/asiolink/tcp_endpoint.h
@@ -100,7 +100,7 @@ public:
return (asio_endpoint_.protocol().family());
}
- // This is not part of the exosed IOEndpoint API but allows
+ // This is not part of the exposed IOEndpoint API but allows
// direct access to the ASIO implementation of the endpoint
inline const asio::ip::tcp::endpoint& getASIOEndpoint() const {
return (asio_endpoint_);
diff --git a/src/lib/asiolink/tests/Makefile.am b/src/lib/asiolink/tests/Makefile.am
index b401834..70b94dc 100644
--- a/src/lib/asiolink/tests/Makefile.am
+++ b/src/lib/asiolink/tests/Makefile.am
@@ -33,6 +33,7 @@ run_unittests_SOURCES += tcp_endpoint_unittest.cc
run_unittests_SOURCES += tcp_socket_unittest.cc
run_unittests_SOURCES += udp_endpoint_unittest.cc
run_unittests_SOURCES += udp_socket_unittest.cc
+run_unittests_SOURCES += io_service_unittest.cc
run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
diff --git a/src/lib/asiolink/tests/io_service_unittest.cc b/src/lib/asiolink/tests/io_service_unittest.cc
new file mode 100644
index 0000000..2fe4f12
--- /dev/null
+++ b/src/lib/asiolink/tests/io_service_unittest.cc
@@ -0,0 +1,48 @@
+// 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 <asiolink/io_service.h>
+
+#include <gtest/gtest.h>
+#include <boost/bind.hpp>
+#include <vector>
+
+using namespace isc::asiolink;
+
+namespace {
+
+void
+postedEvent(std::vector<int>* destination, int value) {
+ destination->push_back(value);
+}
+
+// Check the posted events are called, in the same order they are posted.
+TEST(IOService, post) {
+ std::vector<int> called;
+ IOService service;
+ // Post two events
+ service.post(boost::bind(&postedEvent, &called, 1));
+ service.post(boost::bind(&postedEvent, &called, 2));
+ // They have not yet been called
+ EXPECT_TRUE(called.empty());
+ // Process two events
+ service.run_one();
+ service.run_one();
+ // Both events were called in the right order
+ ASSERT_EQ(2, called.size());
+ EXPECT_EQ(1, called[0]);
+ EXPECT_EQ(2, called[1]);
+}
+
+}
diff --git a/src/lib/asiolink/tests/tcp_socket_unittest.cc b/src/lib/asiolink/tests/tcp_socket_unittest.cc
index 20b6cd8..9de366c 100644
--- a/src/lib/asiolink/tests/tcp_socket_unittest.cc
+++ b/src/lib/asiolink/tests/tcp_socket_unittest.cc
@@ -14,7 +14,7 @@
/// \brief Test of TCPSocket
///
-/// Tests the fuctionality of a TCPSocket by working through an open-send-
+/// Tests the functionality of a TCPSocket by working through an open-send-
/// receive-close sequence and checking that the asynchronous notifications
/// work.
diff --git a/src/lib/asiolink/tests/udp_socket_unittest.cc b/src/lib/asiolink/tests/udp_socket_unittest.cc
index 1ab1a09..3d91874 100644
--- a/src/lib/asiolink/tests/udp_socket_unittest.cc
+++ b/src/lib/asiolink/tests/udp_socket_unittest.cc
@@ -14,7 +14,7 @@
/// \brief Test of UDPSocket
///
-/// Tests the fuctionality of a UDPSocket by working through an open-send-
+/// Tests the functionality of a UDPSocket by working through an open-send-
/// receive-close sequence and checking that the asynchronous notifications
/// work.
diff --git a/src/lib/asiolink/udp_endpoint.h b/src/lib/asiolink/udp_endpoint.h
index 34701b9..02e800d 100644
--- a/src/lib/asiolink/udp_endpoint.h
+++ b/src/lib/asiolink/udp_endpoint.h
@@ -100,7 +100,7 @@ public:
return (asio_endpoint_.protocol().family());
}
- // This is not part of the exosed IOEndpoint API but allows
+ // This is not part of the exposed IOEndpoint API but allows
// direct access to the ASIO implementation of the endpoint
inline const asio::ip::udp::endpoint& getASIOEndpoint() const {
return (asio_endpoint_);
diff --git a/src/lib/bench/benchmark.h b/src/lib/bench/benchmark.h
index 3e380dc..23dc364 100644
--- a/src/lib/bench/benchmark.h
+++ b/src/lib/bench/benchmark.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2010,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
@@ -93,7 +93,7 @@ namespace bench {
/// vector<int>::const_iterator end_key = keys_.end();
/// for (iter = keys_.begin(); iter != end_key; ++iter) {
/// data_.find(*iter);
-/// }
+/// }
/// return (keys_.size());
/// }
/// const set<int>& data_;
diff --git a/src/lib/cache/tests/testdata/message_nxdomain_large_ttl.wire b/src/lib/cache/tests/testdata/message_nxdomain_large_ttl.wire
index 142d8cf..f1971de 100644
--- a/src/lib/cache/tests/testdata/message_nxdomain_large_ttl.wire
+++ b/src/lib/cache/tests/testdata/message_nxdomain_large_ttl.wire
@@ -19,7 +19,7 @@ b1fe 8583
## Authority
##
# example.org: type SOA, class IN, mname ns1.example.org
-# TTL: 3 Hourse, 1 second (10801seconds)
+# TTL: 3 Hours, 1 second (10801 seconds)
c0 0e 00 06 00 01 00 00 2a 31 00 22 03 6e 73 31 c0
0e 05 61 64 6d 69 6e c0 0e 00 00 04 d2 00 00 0e
10 00 00 07 08 00 24 ea 00 00 00 2a 31
diff --git a/src/lib/cc/cc_messages.mes b/src/lib/cc/cc_messages.mes
index b561784..9be9c7c 100644
--- a/src/lib/cc/cc_messages.mes
+++ b/src/lib/cc/cc_messages.mes
@@ -37,7 +37,7 @@ socket listed in the output.
This debug message indicates that the connection was successfully made, this
should follow CC_ESTABLISH.
-% CC_GROUP_RECEIVE trying to receive a message
+% CC_GROUP_RECEIVE trying to receive a message with seq %1
Debug message, noting that a message is expected to come over the command
channel.
diff --git a/src/lib/cc/data.h b/src/lib/cc/data.h
index 74e5c12..d0e0d03 100644
--- a/src/lib/cc/data.h
+++ b/src/lib/cc/data.h
@@ -216,7 +216,7 @@ public:
//@{
/// Returns the ElementPtr at the given key
/// \param name The key of the Element to return
- /// \return The ElementPtr at the given key
+ /// \return The ElementPtr at the given key, or null if not present
virtual ConstElementPtr get(const std::string& name) const;
/// Sets the ElementPtr at the given key
diff --git a/src/lib/cc/proto_defs.cc b/src/lib/cc/proto_defs.cc
index 5eb8575..8f7fb91 100644
--- a/src/lib/cc/proto_defs.cc
+++ b/src/lib/cc/proto_defs.cc
@@ -34,12 +34,22 @@ const char* const CC_HEADER_WANT_ANSWER = "want_answer";
const char* const CC_HEADER_REPLY = "reply";
// The commands in the "type" header
const char* const CC_COMMAND_SEND = "send";
+const char* const CC_COMMAND_SUBSCRIBE = "subscribe";
+const char* const CC_COMMAND_UNSUBSCRIBE = "unsubscribe";
+const char* const CC_COMMAND_GET_LNAME = "getlname";
+const char* const CC_COMMAND_PING = "ping";
+const char* const CC_COMMAND_PONG = "pong";
+const char* const CC_COMMAND_STOP = "stop";
// The wildcards of some headers
const char* const CC_TO_WILDCARD = "*";
const char* const CC_INSTANCE_WILDCARD = "*";
// Reply codes
-const int CC_REPLY_SUCCESS = 0;
const int CC_REPLY_NO_RECPT = -1;
+const int CC_REPLY_SUCCESS = 0;
+// Payload in the message
+const char *const CC_PAYLOAD_LNAME = "lname";
+const char *const CC_PAYLOAD_RESULT = "result";
+const char *const CC_PAYLOAD_COMMAND = "command";
}
}
diff --git a/src/lib/cc/session.cc b/src/lib/cc/session.cc
index c2f39ed..cb1ca39 100644
--- a/src/lib/cc/session.cc
+++ b/src/lib/cc/session.cc
@@ -325,14 +325,14 @@ Session::establish(const char* socket_file) {
//
// send a request for our local name, and wait for a response
//
- ConstElementPtr get_lname_msg =
- Element::fromJSON("{ \"type\": \"getlname\" }");
+ ElementPtr get_lname_msg(Element::createMap());
+ get_lname_msg->set(CC_HEADER_TYPE, Element::create(CC_COMMAND_GET_LNAME));
sendmsg(get_lname_msg);
ConstElementPtr routing, msg;
recvmsg(routing, msg, false);
- impl_->lname_ = msg->get("lname")->stringValue();
+ impl_->lname_ = msg->get(CC_PAYLOAD_LNAME)->stringValue();
LOG_DEBUG(logger, DBG_TRACE_DETAILED, CC_LNAME_RECEIVED).arg(impl_->lname_);
// At this point there's no risk of resource leak.
@@ -387,10 +387,10 @@ Session::recvmsg(ConstElementPtr& env, ConstElementPtr& msg,
for (size_t i = 0; i < impl_->queue_->size(); i++) {
q_el = impl_->queue_->get(i);
if (( seq == -1 &&
- !q_el->get(0)->contains("reply")
+ !q_el->get(0)->contains(CC_HEADER_REPLY)
) || (
- q_el->get(0)->contains("reply") &&
- q_el->get(0)->get("reply")->intValue() == seq
+ q_el->get(0)->contains(CC_HEADER_REPLY) &&
+ q_el->get(0)->get(CC_HEADER_REPLY)->intValue() == seq
)
) {
env = q_el->get(0);
@@ -429,10 +429,10 @@ Session::recvmsg(ConstElementPtr& env, ConstElementPtr& msg,
ConstElementPtr l_msg =
Element::fromWire(body_wire_stream, length - header_length);
if ((seq == -1 &&
- !l_env->contains("reply")
+ !l_env->contains(CC_HEADER_REPLY)
) || (
- l_env->contains("reply") &&
- l_env->get("reply")->intValue() == seq
+ l_env->contains(CC_HEADER_REPLY) &&
+ l_env->get(CC_HEADER_REPLY)->intValue() == seq
)
) {
env = l_env;
@@ -453,9 +453,9 @@ Session::subscribe(std::string group, std::string instance) {
LOG_DEBUG(logger, DBG_TRACE_DETAILED, CC_SUBSCRIBE).arg(group);
ElementPtr env = Element::createMap();
- env->set("type", Element::create("subscribe"));
- env->set("group", Element::create(group));
- env->set("instance", Element::create(instance));
+ env->set(CC_HEADER_TYPE, Element::create(CC_COMMAND_SUBSCRIBE));
+ env->set(CC_HEADER_GROUP, Element::create(group));
+ env->set(CC_HEADER_INSTANCE, Element::create(instance));
sendmsg(env);
}
@@ -465,9 +465,9 @@ Session::unsubscribe(std::string group, std::string instance) {
LOG_DEBUG(logger, DBG_TRACE_DETAILED, CC_UNSUBSCRIBE).arg(group);
ElementPtr env = Element::createMap();
- env->set("type", Element::create("unsubscribe"));
- env->set("group", Element::create(group));
- env->set("instance", Element::create(instance));
+ env->set(CC_HEADER_TYPE, Element::create(CC_COMMAND_UNSUBSCRIBE));
+ env->set(CC_HEADER_GROUP, Element::create(group));
+ env->set(CC_HEADER_INSTANCE, Element::create(instance));
sendmsg(env);
}
@@ -498,7 +498,7 @@ bool
Session::group_recvmsg(ConstElementPtr& envelope, ConstElementPtr& msg,
bool nonblock, int seq)
{
- LOG_DEBUG(logger, DBG_TRACE_DETAILED, CC_GROUP_RECEIVE);
+ LOG_DEBUG(logger, DBG_TRACE_DETAILED, CC_GROUP_RECEIVE).arg(seq);
bool result(recvmsg(envelope, msg, nonblock, seq));
if (result) {
LOG_DEBUG(logger, DBG_TRACE_DETAILED, CC_GROUP_RECEIVED).
@@ -516,13 +516,17 @@ Session::reply(ConstElementPtr envelope, ConstElementPtr newmsg) {
ElementPtr env = Element::createMap();
long int nseq = ++impl_->sequence_;
- env->set("type", Element::create("send"));
- env->set("from", Element::create(impl_->lname_));
- env->set("to", Element::create(envelope->get("from")->stringValue()));
- env->set("group", Element::create(envelope->get("group")->stringValue()));
- env->set("instance", Element::create(envelope->get("instance")->stringValue()));
- env->set("seq", Element::create(nseq));
- env->set("reply", Element::create(envelope->get("seq")->intValue()));
+ env->set(CC_HEADER_TYPE, Element::create(CC_COMMAND_SEND));
+ env->set(CC_HEADER_FROM, Element::create(impl_->lname_));
+ env->set(CC_HEADER_TO,
+ Element::create(envelope->get(CC_HEADER_FROM)->stringValue()));
+ env->set(CC_HEADER_GROUP,
+ Element::create(envelope->get(CC_HEADER_GROUP)->stringValue()));
+ env->set(CC_HEADER_INSTANCE,
+ Element::create(envelope->get(CC_HEADER_INSTANCE)->stringValue()));
+ env->set(CC_HEADER_SEQ, Element::create(nseq));
+ env->set(CC_HEADER_REPLY,
+ Element::create(envelope->get(CC_HEADER_SEQ)->intValue()));
sendmsg(env, newmsg);
diff --git a/src/lib/cc/tests/session_unittests.cc b/src/lib/cc/tests/session_unittests.cc
index ed86529..2531ce1 100644
--- a/src/lib/cc/tests/session_unittests.cc
+++ b/src/lib/cc/tests/session_unittests.cc
@@ -293,7 +293,7 @@ TEST_F(SessionTest, run_with_handler_timeout) {
msg = isc::data::Element::fromJSON("{ \"a third\": \"message\" }");
tds->sendmsg(env, msg);
- // No followup message, should time out.
+ // No follow-up message, should time out.
ASSERT_THROW(my_io_service.run(), SessionTimeout);
}
diff --git a/src/lib/config/ccsession.cc b/src/lib/config/ccsession.cc
index 378f2b4..d094ab9 100644
--- a/src/lib/config/ccsession.cc
+++ b/src/lib/config/ccsession.cc
@@ -32,7 +32,7 @@
#include <boost/foreach.hpp>
#include <cc/data.h>
-#include <module_spec.h>
+#include <config/module_spec.h>
#include <cc/session.h>
#include <exceptions/exceptions.h>
@@ -57,10 +57,10 @@ namespace config {
/// Creates a standard config/command protocol answer message
ConstElementPtr
createAnswer() {
- ElementPtr answer = Element::fromJSON("{\"result\": [] }");
+ ElementPtr answer = Element::createMap();
ElementPtr answer_content = Element::createList();
- answer_content->add(Element::create(0));
- answer->set("result", answer_content);
+ answer_content->add(Element::create(isc::cc::CC_REPLY_SUCCESS));
+ answer->set(isc::cc::CC_PAYLOAD_RESULT, answer_content);
return (answer);
}
@@ -70,22 +70,22 @@ createAnswer(const int rcode, ConstElementPtr arg) {
if (rcode != 0 && (!arg || arg->getType() != Element::string)) {
isc_throw(CCSessionError, "Bad or no argument for rcode != 0");
}
- ElementPtr answer = Element::fromJSON("{\"result\": [] }");
+ ElementPtr answer = Element::createMap();
ElementPtr answer_content = Element::createList();
answer_content->add(Element::create(rcode));
answer_content->add(arg);
- answer->set("result", answer_content);
+ answer->set(isc::cc::CC_PAYLOAD_RESULT, answer_content);
return (answer);
}
ConstElementPtr
createAnswer(const int rcode, const std::string& arg) {
- ElementPtr answer = Element::fromJSON("{\"result\": [] }");
+ ElementPtr answer = Element::createMap();
ElementPtr answer_content = Element::createList();
answer_content->add(Element::create(rcode));
answer_content->add(Element::create(arg));
- answer->set("result", answer_content);
+ answer->set(isc::cc::CC_PAYLOAD_RESULT, answer_content);
return (answer);
}
@@ -94,8 +94,8 @@ ConstElementPtr
parseAnswer(int &rcode, ConstElementPtr msg) {
if (msg &&
msg->getType() == Element::map &&
- msg->contains("result")) {
- ConstElementPtr result = msg->get("result");
+ msg->contains(isc::cc::CC_PAYLOAD_RESULT)) {
+ ConstElementPtr result = msg->get(isc::cc::CC_PAYLOAD_RESULT);
if (result->getType() != Element::list) {
isc_throw(CCSessionError, "Result element in answer message is not a list");
} else if (result->get(0)->getType() != Element::integer) {
@@ -133,7 +133,7 @@ createCommand(const std::string& command, ConstElementPtr arg) {
if (arg) {
cmd_parts->add(arg);
}
- cmd->set("command", cmd_parts);
+ cmd->set(isc::cc::CC_PAYLOAD_COMMAND, cmd_parts);
return (cmd);
}
@@ -141,8 +141,8 @@ std::string
parseCommand(ConstElementPtr& arg, ConstElementPtr command) {
if (command &&
command->getType() == Element::map &&
- command->contains("command")) {
- ConstElementPtr cmd = command->get("command");
+ command->contains(isc::cc::CC_PAYLOAD_COMMAND)) {
+ ConstElementPtr cmd = command->get(isc::cc::CC_PAYLOAD_COMMAND);
if (cmd->getType() == Element::list &&
cmd->size() > 0 &&
cmd->get(0)->getType() == Element::string) {
@@ -165,7 +165,7 @@ namespace {
// getValue() (main problem described in ticket #993)
// This returns either the value set for the given relative id,
// or its default value
-// (intentially defined here so this interface does not get
+// (intentionally defined here so this interface does not get
// included in ConfigData as it is)
ConstElementPtr getValueOrDefault(ConstElementPtr config_part,
const std::string& relative_id,
@@ -463,10 +463,13 @@ ModuleCCSession::ModuleCCSession(
isc_throw(CCSessionInitError, answer->str());
}
- setLocalConfig(Element::fromJSON("{}"));
+ setLocalConfig(Element::createMap());
// get any stored configuration from the manager
if (config_handler_) {
- ConstElementPtr cmd = Element::fromJSON("{ \"command\": [\"get_config\", {\"module_name\":\"" + module_name_ + "\"} ] }");
+ ConstElementPtr cmd =
+ createCommand("get_config",
+ Element::fromJSON("{\"module_name\":\"" +
+ module_name_ + "\"}"));
seq = session_.group_sendmsg(cmd, "ConfigManager");
session_.group_recvmsg(env, answer, false, seq);
ConstElementPtr new_config = parseAnswer(rcode, answer);
@@ -608,14 +611,16 @@ ModuleCCSession::checkCommand() {
/* ignore result messages (in case we're out of sync, to prevent
* pingpongs */
- if (data->getType() != Element::map || data->contains("result")) {
+ if (data->getType() != Element::map ||
+ data->contains(isc::cc::CC_PAYLOAD_RESULT)) {
return (0);
}
ConstElementPtr arg;
ConstElementPtr answer;
try {
std::string cmd_str = parseCommand(arg, data);
- std::string target_module = routing->get("group")->stringValue();
+ std::string target_module =
+ routing->get(isc::cc::CC_HEADER_GROUP)->stringValue();
if (cmd_str == "config_update") {
answer = checkConfigUpdateCommand(target_module, arg);
} else {
@@ -832,19 +837,19 @@ bool
ModuleCCSession::requestMatch(const AsyncRecvRequest& request,
const ConstElementPtr& envelope) const
{
- if (request.is_reply != envelope->contains("reply")) {
+ if (request.is_reply != envelope->contains(isc::cc::CC_HEADER_REPLY)) {
// Wrong type of message
return (false);
}
if (request.is_reply &&
(request.seq == -1 ||
- request.seq == envelope->get("reply")->intValue())) {
+ request.seq == envelope->get(isc::cc::CC_HEADER_REPLY)->intValue())) {
// This is the correct reply
return (true);
}
if (!request.is_reply &&
- (request.recipient.empty() ||
- request.recipient == envelope->get("group")->stringValue())) {
+ (request.recipient.empty() || request.recipient ==
+ envelope->get(isc::cc::CC_HEADER_GROUP)->stringValue())) {
// This is the correct command
return (true);
}
@@ -857,5 +862,27 @@ ModuleCCSession::cancelAsyncRecv(const AsyncRecvRequestID& id) {
async_recv_requests_.erase(id);
}
+ConstElementPtr
+ModuleCCSession::rpcCall(const std::string &command, const std::string &group,
+ const std::string &instance, const std::string &to,
+ const ConstElementPtr ¶ms)
+{
+ ConstElementPtr command_el(createCommand(command, params));
+ const int seq = groupSendMsg(command_el, group, instance, to, true);
+ ConstElementPtr env, answer;
+ LOG_DEBUG(config_logger, DBGLVL_TRACE_DETAIL, CONFIG_RPC_SEQ).arg(command).
+ arg(group).arg(seq);
+ groupRecvMsg(env, answer, true, seq);
+ int rcode;
+ const ConstElementPtr result(parseAnswer(rcode, answer));
+ if (rcode == isc::cc::CC_REPLY_NO_RECPT) {
+ isc_throw(RPCRecipientMissing, result);
+ } else if (rcode != isc::cc::CC_REPLY_SUCCESS) {
+ isc_throw_1(RPCError, result, rcode);
+ } else {
+ return (result);
+ }
+}
+
}
}
diff --git a/src/lib/config/ccsession.h b/src/lib/config/ccsession.h
index b4a44d0..995a5cd 100644
--- a/src/lib/config/ccsession.h
+++ b/src/lib/config/ccsession.h
@@ -20,6 +20,7 @@
#include <cc/session.h>
#include <cc/data.h>
+#include <cc/proto_defs.h>
#include <string>
#include <list>
@@ -146,6 +147,34 @@ public:
isc::Exception(file, line, what) {}
};
+/// \brief Exception thrown when there's a problem with the remote call.
+///
+/// This usually means either the command couldn't be called or the remote
+/// side sent an error as a response.
+class RPCError: public CCSessionError {
+public:
+ RPCError(const char* file, size_t line, const char* what, int rcode) :
+ CCSessionError(file, line, what),
+ rcode_(rcode)
+ {}
+
+ /// \brief The error code for the error.
+ int rcode() const {
+ return (rcode_);
+ }
+private:
+ const int rcode_;
+};
+
+/// \brief Specific version of RPCError for the case the recipient of command
+/// doesn't exist.
+class RPCRecipientMissing: public RPCError {
+public:
+ RPCRecipientMissing(const char* file, size_t line, const char* what) :
+ RPCError(file, line, what, isc::cc::CC_REPLY_NO_RECPT)
+ {}
+};
+
///
/// \brief This module keeps a connection to the command channel,
/// holds configuration information, and handles messages from
@@ -284,12 +313,12 @@ public:
* spec_is_filename is true (the default), then a
* filename is assumed, otherwise a module name.
* \param handler The handler functor called whenever there's a change.
- * Called once initally from this function. May be NULL
+ * Called once initially from this function. May be NULL
* if you don't want any handler to be called and you're
* fine with requesting the data through
* getRemoteConfigValue() each time.
*
- * The handler should not throw, or it'll fall trough and
+ * The handler should not throw, or it'll fall through and
* the exception will get into strange places, probably
* aborting the application.
* \param spec_is_filename Says if spec_name is filename or module name.
@@ -335,31 +364,81 @@ public:
* \param group see isc::cc::Session::group_sendmsg()
* \param instance see isc::cc::Session::group_sendmsg()
* \param to see isc::cc::Session::group_sendmsg()
+ * \param want_answer see isc::cc::Session::group_sendmsg()
* \return see isc::cc::Session::group_sendmsg()
*/
int groupSendMsg(isc::data::ConstElementPtr msg,
std::string group,
- std::string instance = "*",
- std::string to = "*") {
- return (session_.group_sendmsg(msg, group, instance, to));
+ std::string instance = isc::cc::CC_INSTANCE_WILDCARD,
+ std::string to = isc::cc::CC_TO_WILDCARD,
+ bool want_answer = false) {
+ return (session_.group_sendmsg(msg, group, instance, to, want_answer));
};
- /**
- * Receive a message from the underlying CC session.
- * This has the same interface as isc::cc::Session::group_recvmsg()
- *
- * \param envelope see isc::cc::Session::group_recvmsg()
- * \param msg see isc::cc::Session::group_recvmsg()
- * \param nonblock see isc::cc::Session::group_recvmsg()
- * \param seq see isc::cc::Session::group_recvmsg()
- * \return see isc::cc::Session::group_recvmsg()
- */
+ /// \brief Receive a message from the underlying CC session.
+ /// This has the same interface as isc::cc::Session::group_recvmsg()
+ ///
+ /// NOTE: until #2804 is resolved this method wouldn't work except in
+ /// very limited cases; don't try to use it until then.
+ ///
+ /// \param envelope see isc::cc::Session::group_recvmsg()
+ /// \param msg see isc::cc::Session::group_recvmsg()
+ /// \param nonblock see isc::cc::Session::group_recvmsg()
+ /// \param seq see isc::cc::Session::group_recvmsg()
+ /// \return see isc::cc::Session::group_recvmsg()
bool groupRecvMsg(isc::data::ConstElementPtr& envelope,
isc::data::ConstElementPtr& msg,
bool nonblock = true,
int seq = -1) {
return (session_.group_recvmsg(envelope, msg, nonblock, seq));
- };
+ }
+
+ /// \brief Send a command message and wait for the answer.
+ ///
+ /// NOTE: until #2804 is resolved this method wouldn't work except in
+ /// very limited cases; don't try to use it until then.
+ ///
+ /// This is mostly a convenience wrapper around groupSendMsg
+ /// and groupRecvMsg, with some error handling.
+ ///
+ /// \param command Name of the command to call.
+ /// \param group Name of the remote module to call the command on.
+ /// \param instance Instance part of recipient address.
+ /// \param to The lname to send it to. Can be used to override the
+ /// addressing and use a direct recipient.
+ /// \param params Parameters for the command. Can be left NULL if
+ /// no parameters are needed.
+ /// \return Return value of the successfull remote call. It can be
+ /// NULL if the remote command is void function (returns nothing).
+ /// \throw RPCError if the call fails (for example when the other
+ /// side responds with error code).
+ /// \throw RPCRecipientMissing if the recipient doesn't exist.
+ /// \throw CCSessionError if some lower-level error happens (eg.
+ /// the response was malformed).
+ isc::data::ConstElementPtr rpcCall(const std::string& command,
+ const std::string& group,
+ const std::string& instance =
+ isc::cc::CC_INSTANCE_WILDCARD,
+ const std::string& to =
+ isc::cc::CC_TO_WILDCARD,
+ const isc::data::ConstElementPtr&
+ params =
+ isc::data::ConstElementPtr());
+
+ /// \brief Convenience version of rpcCall
+ ///
+ /// This is exactly the same as the previous version of rpcCall, except
+ /// that the instance and to parameters are at their default. This
+ /// allows to sending a command with parameters to a named module
+ /// without long typing of the parameters.
+ isc::data::ConstElementPtr rpcCall(const std::string& command,
+ const std::string& group,
+ const isc::data::ConstElementPtr&
+ params)
+ {
+ return rpcCall(command, group, isc::cc::CC_INSTANCE_WILDCARD,
+ isc::cc::CC_TO_WILDCARD, params);
+ }
/// \brief Forward declaration of internal data structure.
///
diff --git a/src/lib/config/config_messages.mes b/src/lib/config/config_messages.mes
index 552256c..6735b16 100644
--- a/src/lib/config/config_messages.mes
+++ b/src/lib/config/config_messages.mes
@@ -94,3 +94,7 @@ manager.
% CONFIG_OPEN_FAIL error opening %1: %2
There was an error opening the given file. The reason for the failure
is included in the message.
+
+% CONFIG_RPC_SEQ RPC call %1 to %2 with seq %3
+Debug message, saying there's a RPC call of given command to given module. It
+has internal sequence number as listed in the message.
diff --git a/src/lib/config/tests/ccsession_unittests.cc b/src/lib/config/tests/ccsession_unittests.cc
index 2a5e758..c11cd24 100644
--- a/src/lib/config/tests/ccsession_unittests.cc
+++ b/src/lib/config/tests/ccsession_unittests.cc
@@ -53,11 +53,33 @@ protected:
// upon creation of a ModuleCCSession, the class
// sends its specification to the config manager.
- // it expects an ok answer back, so everytime we
+ // it expects an ok answer back, so every time we
// create a ModuleCCSession, we must set an initial
// ok answer.
session.getMessages()->add(createAnswer());
}
+ ConstElementPtr rpcCheck(const std::string& reply) {
+ ModuleCCSession mccs(ccspecfile("spec1.spec"), session, NULL, NULL,
+ false, false);
+ // Prepare the answer beforehand, it'll block until it gets one
+ const ConstElementPtr reply_el(el(reply));
+ session.getMessages()->add(reply_el);
+ const ConstElementPtr
+ result(mccs.rpcCall("test", "Spec2",
+ el("{\"param1\": \"Param 1\","
+ "\"param2\": \"Param 2\"}")));
+ const ConstElementPtr
+ request(el("[\"Spec2\", \"*\", {"
+ " \"command\": [\"test\", {"
+ " \"param1\": \"Param 1\","
+ " \"param2\": \"Param 2\""
+ "}]}, -1, true]"));
+ // The 0th one is from the initialization, to ConfigManager.
+ // our is the 1st.
+ EXPECT_TRUE(request->equals(*session.getMsgQueue()->get(1))) <<
+ session.getMsgQueue()->get(1)->toWire();
+ return (result);
+ }
~CCSessionTest() {
isc::log::setRootLoggerName(root_name);
}
@@ -65,6 +87,36 @@ protected:
const std::string root_name;
};
+// Test we can send an RPC (command) and get an answer. The answer is success
+// in this case.
+TEST_F(CCSessionTest, rpcCallSuccess) {
+ const ConstElementPtr result =
+ rpcCheck("{\"result\": [0, {\"Hello\": \"a\"}]}");
+ EXPECT_TRUE(el("{\"Hello\": \"a\"}")->equals(*result));
+}
+
+// Test success of RPC, but the answer is empty (eg. a void function on the
+// remote side).
+TEST_F(CCSessionTest, rpcCallSuccessNone) {
+ EXPECT_FALSE(rpcCheck("{\"result\": [0]}"));
+}
+
+// Test it successfully raises CCSessionError if the answer is malformed.
+TEST_F(CCSessionTest, rpcCallMalformedAnswer) {
+ EXPECT_THROW(rpcCheck("[\"Nonsense\"]"), CCSessionError);
+}
+
+// Test it raises exception when the remote side reports an error
+TEST_F(CCSessionTest, rpcCallError) {
+ EXPECT_THROW(rpcCheck("{\"result\": [1, \"Error\"]}"), RPCError);
+}
+
+// Test it raises exception when the remote side doesn't exist
+TEST_F(CCSessionTest, rpcNoRecpt) {
+ EXPECT_THROW(rpcCheck("{\"result\": [-1, \"Error\"]}"),
+ RPCRecipientMissing);
+}
+
TEST_F(CCSessionTest, createAnswer) {
ConstElementPtr answer;
answer = createAnswer();
diff --git a/src/lib/config/tests/fake_session.cc b/src/lib/config/tests/fake_session.cc
index 4c19963..e6d569e 100644
--- a/src/lib/config/tests/fake_session.cc
+++ b/src/lib/config/tests/fake_session.cc
@@ -183,12 +183,12 @@ FakeSession::unsubscribe(std::string group, std::string instance) {
int
FakeSession::group_sendmsg(ConstElementPtr msg, std::string group,
- std::string to, std::string, bool)
+ std::string to, std::string, bool want_answer)
{
if (throw_on_send_) {
isc_throw(Exception, "Throw on send is set in FakeSession");
}
- addMessage(msg, group, to);
+ addMessage(msg, group, to, -1, want_answer);
return (1);
}
@@ -231,13 +231,16 @@ FakeSession::getFirstMessage(std::string& group, std::string& to) const {
void
FakeSession::addMessage(ConstElementPtr msg, const std::string& group,
- const std::string& to, int seq)
+ const std::string& to, int seq, bool want_answer)
{
ElementPtr m_el = Element::createList();
m_el->add(Element::create(group));
m_el->add(Element::create(to));
m_el->add(msg);
m_el->add(Element::create(seq));
+ if (want_answer) {
+ m_el->add(Element::create(want_answer));
+ }
if (!msg_queue_) {
msg_queue_ = Element::createList();
}
diff --git a/src/lib/config/tests/fake_session.h b/src/lib/config/tests/fake_session.h
index 0dbaadb..8a564a8 100644
--- a/src/lib/config/tests/fake_session.h
+++ b/src/lib/config/tests/fake_session.h
@@ -75,7 +75,8 @@ public:
isc::data::ConstElementPtr getFirstMessage(std::string& group,
std::string& to) const;
void addMessage(isc::data::ConstElementPtr, const std::string& group,
- const std::string& to, int seq = -1);
+ const std::string& to, int seq = -1,
+ bool want_answer = false);
bool haveSubscription(const std::string& group,
const std::string& instance);
bool haveSubscription(const isc::data::ConstElementPtr group,
diff --git a/src/lib/cryptolink/cryptolink.h b/src/lib/cryptolink/cryptolink.h
index 859065b..408ed00 100644
--- a/src/lib/cryptolink/cryptolink.h
+++ b/src/lib/cryptolink/cryptolink.h
@@ -101,7 +101,7 @@ class CryptoLinkImpl;
/// There is only one way to access it, through getCryptoLink(), which
/// returns a reference to the initialized library. On the first call,
/// it will be initialized automatically. You can however initialize it
-/// manually through a call to the initalize(), before your first call
+/// manually through a call to initialize(), before your first call
/// to getCryptoLink. Any subsequent call to initialize() will be a
/// noop.
///
diff --git a/src/lib/datasrc/.gitignore b/src/lib/datasrc/.gitignore
index 206ddca..4b199ed 100644
--- a/src/lib/datasrc/.gitignore
+++ b/src/lib/datasrc/.gitignore
@@ -3,3 +3,5 @@
/datasrc_config.h
/datasrc_config.h.pre
/static.zone
+/sqlite3_datasrc_messages.cc
+/sqlite3_datasrc_messages.h
diff --git a/src/lib/datasrc/Makefile.am b/src/lib/datasrc/Makefile.am
index be79557..5422f7d 100644
--- a/src/lib/datasrc/Makefile.am
+++ b/src/lib/datasrc/Makefile.am
@@ -19,12 +19,12 @@ static.zone: static.zone.pre $(top_builddir)/config.h $(top_srcdir)/AUTHORS
$(SED) -e 's/\(.*\)/AUTHORS.BIND. 0 CH TXT "\1"/' $(top_srcdir)/AUTHORS >>$@
CLEANFILES = *.gcno *.gcda datasrc_messages.h datasrc_messages.cc
+CLEANFILES += sqlite3_datasrc_messages.h sqlite3_datasrc_messages.cc
CLEANFILES += datasrc_config.h
CLEANFILES += static.zone
lib_LTLIBRARIES = libb10-datasrc.la
-libb10_datasrc_la_SOURCES = data_source.h
-libb10_datasrc_la_SOURCES += exceptions.h
+libb10_datasrc_la_SOURCES = exceptions.h
libb10_datasrc_la_SOURCES += zone.h zone_finder.h zone_finder.cc
libb10_datasrc_la_SOURCES += zone_finder_context.cc
libb10_datasrc_la_SOURCES += zone_iterator.h
@@ -38,25 +38,24 @@ libb10_datasrc_la_SOURCES += master_loader_callbacks.h
libb10_datasrc_la_SOURCES += master_loader_callbacks.cc
libb10_datasrc_la_SOURCES += rrset_collection_base.h rrset_collection_base.cc
libb10_datasrc_la_SOURCES += zone_loader.h zone_loader.cc
+libb10_datasrc_la_SOURCES += cache_config.h cache_config.cc
+libb10_datasrc_la_SOURCES += zone_table_accessor.h
+libb10_datasrc_la_SOURCES += zone_table_accessor_cache.h
+libb10_datasrc_la_SOURCES += zone_table_accessor_cache.cc
nodist_libb10_datasrc_la_SOURCES = datasrc_messages.h datasrc_messages.cc
libb10_datasrc_la_LDFLAGS = -no-undefined -version-info 1:0:1
-pkglib_LTLIBRARIES = sqlite3_ds.la static_ds.la
+pkglib_LTLIBRARIES = sqlite3_ds.la
sqlite3_ds_la_SOURCES = sqlite3_accessor.h sqlite3_accessor.cc
sqlite3_ds_la_SOURCES += sqlite3_accessor_link.cc
+nodist_sqlite3_ds_la_SOURCES = sqlite3_datasrc_messages.h sqlite3_datasrc_messages.cc
sqlite3_ds_la_LDFLAGS = -module -avoid-version
sqlite3_ds_la_LDFLAGS += -no-undefined -version-info 1:0:0
sqlite3_ds_la_LIBADD = $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
sqlite3_ds_la_LIBADD += libb10-datasrc.la
sqlite3_ds_la_LIBADD += $(SQLITE_LIBS)
-static_ds_la_SOURCES = static_datasrc_link.cc
-static_ds_la_SOURCES += static_datasrc.h
-static_ds_la_LDFLAGS = -module -avoid-version
-static_ds_la_LIBADD = $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
-static_ds_la_LIBADD += libb10-datasrc.la
-
libb10_datasrc_la_LIBADD = $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
libb10_datasrc_la_LIBADD += $(top_builddir)/src/lib/dns/libb10-dns++.la
libb10_datasrc_la_LIBADD += $(top_builddir)/src/lib/log/libb10-log.la
@@ -65,10 +64,13 @@ libb10_datasrc_la_LIBADD += $(top_builddir)/src/lib/datasrc/memory/libdatasrc_me
libb10_datasrc_la_LIBADD += $(SQLITE_LIBS)
BUILT_SOURCES = datasrc_config.h datasrc_messages.h datasrc_messages.cc
+BUILT_SOURCES += sqlite3_datasrc_messages.h sqlite3_datasrc_messages.cc
datasrc_messages.h datasrc_messages.cc: Makefile datasrc_messages.mes
$(top_builddir)/src/lib/log/compiler/message $(top_srcdir)/src/lib/datasrc/datasrc_messages.mes
+sqlite3_datasrc_messages.h sqlite3_datasrc_messages.cc: Makefile sqlite3_datasrc_messages.mes
+ $(top_builddir)/src/lib/log/compiler/message $(top_srcdir)/src/lib/datasrc/sqlite3_datasrc_messages.mes
-EXTRA_DIST = datasrc_messages.mes static.zone.pre
+EXTRA_DIST = datasrc_messages.mes sqlite3_datasrc_messages.mes static.zone.pre
zonedir = $(pkgdatadir)
zone_DATA = static.zone
diff --git a/src/lib/datasrc/cache_config.cc b/src/lib/datasrc/cache_config.cc
new file mode 100644
index 0000000..ec3cfeb
--- /dev/null
+++ b/src/lib/datasrc/cache_config.cc
@@ -0,0 +1,198 @@
+// 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 <datasrc/cache_config.h>
+#include <datasrc/client.h>
+#include <datasrc/memory/load_action.h>
+#include <datasrc/memory/zone_data_loader.h>
+
+#include <util/memory_segment.h>
+
+#include <dns/name.h>
+#include <dns/rrclass.h>
+
+#include <cc/data.h>
+#include <exceptions/exceptions.h>
+
+#include <boost/bind.hpp>
+
+#include <cassert>
+#include <map>
+#include <string>
+
+using namespace isc::data;
+
+namespace isc {
+namespace datasrc {
+namespace internal {
+
+namespace {
+bool
+getEnabledFromConf(const Element& conf) {
+ return (conf.contains("cache-enable") &&
+ conf.get("cache-enable")->boolValue());
+}
+
+std::string
+getSegmentTypeFromConf(const Element& conf) {
+ // If cache-type is not explicitly configured, use the default type.
+ // (Ideally we should retrieve the default from the spec).
+ if (!conf.contains("cache-type")) {
+ return ("local");
+ }
+ return (conf.get("cache-type")->stringValue());
+}
+}
+
+CacheConfig::CacheConfig(const std::string& datasrc_type,
+ const DataSourceClient* datasrc_client,
+ const Element& datasrc_conf,
+ bool allowed) :
+ enabled_(allowed && getEnabledFromConf(datasrc_conf)),
+ segment_type_(getSegmentTypeFromConf(datasrc_conf)),
+ datasrc_client_(datasrc_client)
+{
+ ConstElementPtr params = datasrc_conf.get("params");
+ if (!params) {
+ params.reset(new NullElement());
+ }
+ if (datasrc_type == "MasterFiles") {
+ if (datasrc_client_) {
+ isc_throw(InvalidParameter,
+ "data source client is given for MasterFiles");
+ }
+
+ if (!enabled_) {
+ isc_throw(CacheConfigError,
+ "The cache must be enabled for the MasterFiles type");
+ }
+
+ typedef std::map<std::string, ConstElementPtr> ZoneToFile;
+ const ZoneToFile& zone_to_file = params->mapValue();
+ ZoneToFile::const_iterator const it_end = zone_to_file.end();
+ for (ZoneToFile::const_iterator it = zone_to_file.begin();
+ it != it_end;
+ ++it)
+ {
+ zone_config_[dns::Name(it->first)] = it->second->stringValue();
+ }
+ } else {
+ if (!datasrc_client_) {
+ isc_throw(InvalidParameter,
+ "data source client is missing for data source type: "
+ << datasrc_type);
+ }
+ if (!enabled_) {
+ return;
+ }
+
+ if (!datasrc_conf.contains("cache-zones")) {
+ isc_throw(NotImplemented, "Auto-detection of zones "
+ "to cache is not yet implemented, supply "
+ "cache-zones parameter");
+ // TODO: Auto-detect list of all zones in the
+ // data source.
+ }
+
+ const ConstElementPtr zones = datasrc_conf.get("cache-zones");
+ for (size_t i = 0; i < zones->size(); ++i) {
+ const dns::Name zone_name(zones->get(i)->stringValue());
+ if (!zone_config_.insert(Zones::value_type(zone_name,
+ "")).second) {
+ isc_throw(CacheConfigError, "Duplicate cache zone: " <<
+ zone_name);
+ }
+ }
+ }
+}
+
+namespace {
+
+// We would like to use boost::bind for this. However, the loadZoneData takes
+// a reference, while we have a shared pointer to the iterator -- and we need
+// to keep it alive as long as the ZoneWriter is alive. Therefore we can't
+// really just dereference it and pass it, since it would get destroyed once
+// the getCachedZoneWriter would end. This class holds the shared pointer
+// alive, otherwise is mostly simple.
+//
+// It might be doable with nested boost::bind, but it would probably look
+// more awkward and complicated than this.
+class IteratorLoader {
+public:
+ IteratorLoader(const dns::RRClass& rrclass, const dns::Name& name,
+ const ZoneIteratorPtr& iterator) :
+ rrclass_(rrclass),
+ name_(name),
+ iterator_(iterator)
+ {}
+ memory::ZoneData* operator()(util::MemorySegment& segment) {
+ return (memory::loadZoneData(segment, rrclass_, name_, *iterator_));
+ }
+private:
+ const dns::RRClass rrclass_;
+ const dns::Name name_;
+ ZoneIteratorPtr iterator_;
+};
+
+// We can't use the loadZoneData function directly in boost::bind, since
+// it is overloaded and the compiler can't choose the correct version
+// reliably and fails. So we simply wrap it into an unique name.
+memory::ZoneData*
+loadZoneDataFromFile(util::MemorySegment& segment, const dns::RRClass& rrclass,
+ const dns::Name& name, const std::string& filename)
+{
+ return (memory::loadZoneData(segment, rrclass, name, filename));
+}
+
+} // unnamed namespace
+
+memory::LoadAction
+CacheConfig::getLoadAction(const dns::RRClass& rrclass,
+ const dns::Name& zone_name) const
+{
+ // First, check if the specified zone is configured to be cached.
+ Zones::const_iterator found = zone_config_.find(zone_name);
+ if (found == zone_config_.end()) {
+ return (memory::LoadAction());
+ }
+
+ if (!found->second.empty()) {
+ // This is "MasterFiles" data source.
+ return (boost::bind(loadZoneDataFromFile, _1, rrclass, zone_name,
+ found->second));
+ }
+
+ // Otherwise there must be a "source" data source (ensured by constructor)
+ assert(datasrc_client_);
+
+ // If the specified zone name does not exist in our client of the source,
+ // NoSuchZone is thrown, which is exactly the result what we
+ // want, so no need to handle it.
+ ZoneIteratorPtr iterator(datasrc_client_->getIterator(zone_name));
+ if (!iterator) {
+ // This shouldn't happen for a compliant implementation of
+ // DataSourceClient, but we'll protect ourselves from buggy
+ // implementations.
+ isc_throw(Unexpected, "getting LoadAction for " << zone_name
+ << "/" << rrclass << " resulted in Null zone iterator");
+ }
+
+ // Wrap the iterator into the correct functor (which keeps it alive as
+ // long as it is needed).
+ return (IteratorLoader(rrclass, zone_name, iterator));
+}
+
+} // namespace internal
+} // namespace datasrc
+} // namespace isc
diff --git a/src/lib/datasrc/cache_config.h b/src/lib/datasrc/cache_config.h
new file mode 100644
index 0000000..35b5ead
--- /dev/null
+++ b/src/lib/datasrc/cache_config.h
@@ -0,0 +1,219 @@
+// 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 DATASRC_CACHE_CONFIG_H
+#define DATASRC_CACHE_CONFIG_H
+
+#include <exceptions/exceptions.h>
+
+#include <dns/dns_fwd.h>
+#include <cc/data.h>
+#include <datasrc/memory/load_action.h>
+
+#include <boost/noncopyable.hpp>
+
+#include <map>
+#include <string>
+
+namespace isc {
+namespace datasrc {
+class DataSourceClient;
+
+namespace internal {
+
+/// \brief Exception thrown for configuration error related to in-memory cache.
+class CacheConfigError : public Exception {
+public:
+ CacheConfigError(const char* file, size_t line, const char* what) :
+ Exception(file, line, what)
+ {}
+};
+
+/// \brief Configuration for in-memory cache of a data source.
+///
+/// This class understands and validates the configuration parameters for
+/// \c DataSourceClient related to in-memory cache, and converts it to native,
+/// type-safe objects for the convenience of the user of this class.
+/// Specifically, it allows the user to get the underlying memory segment
+/// type for the cache as a string and to iterate over zone names to be
+/// cached in memory.
+///
+/// It also provides unified interface for getting \c memory::LoadAction
+/// object that can be used for loading zones, regardless of the underlying
+/// data source properties, i.e., whether it's special "MasterFiles" type
+/// or other generic data sources.
+///
+/// This class is publicly defined so it can be tested directly, but
+/// it's essentially private to the \c ConfigurableClientList class.
+/// It's therefore defined in an "internal" namespace, and isn't expected
+/// to be used by other classes or user applications. Likewise, this file
+/// is not expected to be installed with other publicly usable header files.
+///
+/// It's defined as noncopyable, simply because it's not expected to be
+/// copied in the intended usage for \c ConfigurableClientList. Prohibiting
+/// copies will help avoid unexpected disruption due to accidental copy and
+/// sharing internal resources as a result of that.
+class CacheConfig : boost::noncopyable {
+public:
+ /// \brief Constructor.
+ ///
+ /// It performs the following validation on the given configuration:
+ /// - For the "MasterFiles" type
+ /// - datasrc_client_ must not be provided (must be null); throws
+ /// InvalidParameter otherwise.
+ /// - cache must be enabled: "cache-enable" configuration item exists
+ /// and is true, and allowed parameter is true, too; throws
+ /// CacheConfigError otherwise.
+ /// - "params" configuration item must be provided and of a map type,
+ /// and each map entry maps a string to another string; throws
+ /// data::TypeError otherwise.
+ /// - the key string of each map entry must be a valid textual
+ /// representation of a domain name. Otherwise corresponding
+ /// exception from the dns::Name class will be thrown.
+ /// - For other types
+ /// - datasrc_client_ must be provided (must not be null); throws
+ /// InvalidParameter otherwise.
+ /// - (Unless cache is disabled) "cache-zones" configuration item must
+ /// exist and must be a list of strings; throws data::TypeError
+ /// otherwise.
+ /// - Each string value of cache-zones entries must be a valid textual
+ /// representation of a domain name. Otherwise corresponding
+ /// exception from the dns::Name class will be thrown.
+ /// - Names in the list must not have duplicates;
+ /// throws CacheConfigError otherwise.
+ ///
+ /// For other data source types than "MasterFiles", cache can be disabled.
+ /// In this case cache-zones configuration item is simply ignored, even
+ /// it contains an error that would otherwise trigger an exception.
+ ///
+ /// The specified set of zones (directly in "params" in case of
+ /// "MasterFile", and specified in "cache-zones" for others) can be
+ /// empty.
+ ///
+ /// This constructor also identifies the underlying memory segment type
+ /// used for the cache. It's given via the "cache-type" configuration
+ /// item if defined; otherwise it defaults to "local".
+ ///
+ /// \throw InvalidParameter Program error at the caller side rather than
+ /// in the configuration (see above)
+ /// \throw CacheConfigError There is a semantics error in the given
+ /// configuration (see above)
+ /// \throw data::TypeError Invalid type of data is found in the
+ /// configuration (see above)
+ /// \throw Other Exceptions from the dns::Name class when conversion from
+ /// text fails (see above)
+ ///
+ /// \param datasrc_type Type of data source. This must be the "type"
+ /// value of the data source configuration.
+ /// \param datasrc_client Client of the underlying data source for the
+ /// cache, if it's used; for MasterFiles types it's null.
+ /// \param datasrc_conf Configuration element for the data source.
+ /// This must be the value of, e.g., data_sources/classes/IN[0] of
+ /// BIND 10 configuration.
+ /// \param allowed Whether in-memory cache is allowed by the process.
+ /// This must be derived from the allow_cache parameter of
+ /// \c ConfigurableClientList::configure().
+ CacheConfig(const std::string& datasrc_type,
+ const DataSourceClient* datasrc_client,
+ const data::Element& datasrc_conf,
+ bool allowed);
+
+ /// \brief Return if the cache is enabled.
+ ///
+ /// The cache is considered enabled iff the "cache-enable" configuration
+ /// item (given on construction) existed and was set to true, and
+ /// the \c allowed parameter to the constructor was true.
+ ///
+ /// \throw None
+ bool isEnabled() const { return (enabled_); }
+
+ /// \brief Return the memory segment type to be used for the zone table.
+ ///
+ /// \throw None
+ const std::string& getSegmentType() const { return (segment_type_); }
+
+ /// \brief Return a \c LoadAction functor to load zone data into memory.
+ ///
+ /// This method returns an appropriate \c LoadAction functor that can be
+ /// passed to a \c memory::ZoneWriter object to load data of the specified
+ /// zone into memory. The source of the zone data differs depending on
+ /// the cache configuration (either a master file or another data source),
+ /// but this method hides the details and works as a unified interface
+ /// for the caller.
+ ///
+ /// If the specified zone is not configured to be cached, it returns an
+ /// empty functor (which can be evaluated to be \c false as a boolean).
+ /// It doesn't throw an exception in this case because the expected caller
+ /// of this method would handle such a case internally.
+ ///
+ /// \throw NoSuchZone The specified zone doesn't exist in the
+ /// underlying data source storing the original data to be cached.
+ /// \throw DataSourceError Other, unexpected but possible error happens
+ /// in the underlying data source.
+ /// \throw Unexpected Unexpected error happens in the underlying data
+ /// source. This shouldn't happen as long as the data source
+ /// implementation meets the public API requirement.
+ ///
+ /// \param rrclass The RR class of the zone
+ /// \param zone_name The origin name of the zone
+ /// \return A \c LoadAction functor to load zone data or an empty functor
+ /// (see above).
+ memory::LoadAction getLoadAction(const dns::RRClass& rrlcass,
+ const dns::Name& zone_name) const;
+
+ /// \brief Read only iterator type over configured cached zones.
+ ///
+ /// \note This initial version exposes the internal data structure (i.e.
+ /// map from name to string) through this public iterator type for
+ /// simplicity. In terms of data encapsulation it's better to introduce
+ /// a custom iterator type that only goes through the conceptual list
+ /// of zone names, but due to the limitation of the expected user of this
+ /// class that would probably be premature generalization. In future,
+ /// we might want to allow getting the list of zones directly from the
+ /// underlying data source. If and when that happens we should introduce
+ /// a custom type. In any case, the user of this class should only
+ /// use the typedef, not the original map iterator. It should also
+ /// use this iterator as a forward iterator (datasource-based iterator
+ /// wouldn't be able to be bidirectional), and it shouldn't use the
+ /// value of the map entry (a string, specifying a path to master file
+ /// for MasterFiles data source).
+ typedef std::map<dns::Name, std::string>::const_iterator ConstZoneIterator;
+
+ /// \brief Return the beginning of cached zones in the form of iterator.
+ ConstZoneIterator begin() const { return (zone_config_.begin()); }
+
+ /// \brief Return the end of cached zones in the form of iterator.
+ ConstZoneIterator end() const { return (zone_config_.end()); }
+
+private:
+ const bool enabled_; // if the use of in-memory zone table is enabled
+ const std::string segment_type_;
+ // client of underlying data source, will be NULL for MasterFile datasrc
+ const DataSourceClient* datasrc_client_;
+
+ // Maps each of zones to be cached to a string. For "MasterFiles" type
+ // of data source, the string is a path to the master zone file; for
+ // others it's an empty string.
+ typedef std::map<dns::Name, std::string> Zones;
+ Zones zone_config_;
+};
+}
+}
+}
+
+#endif // DATASRC_CACHE_CONFIG_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/datasrc/client.h b/src/lib/datasrc/client.h
index 9c5d262..169e461 100644
--- a/src/lib/datasrc/client.h
+++ b/src/lib/datasrc/client.h
@@ -201,12 +201,9 @@ public:
/// This allows for traversing the whole zone. The returned object can
/// provide the RRsets one by one.
///
- /// This throws DataSourceError when the zone does not exist in the
- /// datasource.
- ///
/// The default implementation throws isc::NotImplemented. This allows
/// for easy and fast deployment of minimal custom data sources, where
- /// the user/implementator doesn't have to care about anything else but
+ /// the user/implementer doesn't have to care about anything else but
/// the actual queries. Also, in some cases, it isn't possible to traverse
/// the zone from logic point of view (eg. dynamically generated zone
/// data).
@@ -214,6 +211,11 @@ public:
/// It is not fixed if a concrete implementation of this method can throw
/// anything else.
///
+ /// \throw NoSuchZone the zone does not exist in the datasource.
+ /// \throw Others Possibly implementation specific exceptions (it is
+ /// not fixed if a concrete implementation of this method can throw
+ /// anything else.)
+ ///
/// \param name The name of zone apex to be traversed. It doesn't do
/// nearest match as findZone.
/// \param separate_rrs If true, the iterator will return each RR as a
diff --git a/src/lib/datasrc/client_list.cc b/src/lib/datasrc/client_list.cc
index 0750fb6..0611ed9 100644
--- a/src/lib/datasrc/client_list.cc
+++ b/src/lib/datasrc/client_list.cc
@@ -13,22 +13,25 @@
// PERFORMANCE OF THIS SOFTWARE.
-#include "client_list.h"
-#include "exceptions.h"
-#include "client.h"
-#include "factory.h"
-#include "memory/memory_client.h"
-#include "memory/zone_table_segment.h"
-#include "memory/zone_writer.h"
-#include "memory/zone_data_loader.h"
-#include "memory/zone_data_updater.h"
-#include "logger.h"
+#include <datasrc/client_list.h>
+#include <datasrc/exceptions.h>
+#include <datasrc/client.h>
+#include <datasrc/factory.h>
+#include <datasrc/cache_config.h>
+#include <datasrc/memory/memory_client.h>
+#include <datasrc/memory/zone_table_segment.h>
+#include <datasrc/memory/zone_writer.h>
+#include <datasrc/memory/zone_data_loader.h>
+#include <datasrc/memory/zone_data_updater.h>
+#include <datasrc/logger.h>
#include <dns/masterload.h>
#include <util/memory_segment_local.h>
#include <memory>
+#include <set>
#include <boost/foreach.hpp>
#include <boost/bind.hpp>
+#include <boost/scoped_ptr.hpp>
using namespace isc::data;
using namespace isc::dns;
@@ -46,25 +49,18 @@ namespace datasrc {
ConfigurableClientList::DataSourceInfo::DataSourceInfo(
DataSourceClient* data_src_client,
- const DataSourceClientContainerPtr& container, bool has_cache,
- const RRClass& rrclass, const shared_ptr<ZoneTableSegment>& segment) :
+ const DataSourceClientContainerPtr& container,
+ boost::shared_ptr<internal::CacheConfig> cache_conf,
+ const RRClass& rrclass, const string& name) :
data_src_client_(data_src_client),
- container_(container)
+ container_(container),
+ name_(name),
+ cache_conf_(cache_conf)
{
- if (has_cache) {
- cache_.reset(new InMemoryClient(segment, rrclass));
- ztable_segment_ = segment;
- }
-}
-
-ConfigurableClientList::DataSourceInfo::DataSourceInfo(
- const RRClass& rrclass, const shared_ptr<ZoneTableSegment>& segment,
- bool has_cache) :
- data_src_client_(NULL)
-{
- if (has_cache) {
- cache_.reset(new InMemoryClient(segment, rrclass));
- ztable_segment_ = segment;
+ if (cache_conf_ && cache_conf_->isEnabled()) {
+ ztable_segment_.reset(ZoneTableSegment::create(
+ rrclass, cache_conf_->getSegmentType()));
+ cache_.reset(new InMemoryClient(ztable_segment_, rrclass));
}
}
@@ -90,8 +86,7 @@ ConfigurableClientList::configure(const ConstElementPtr& config,
size_t i(0); // Outside of the try to be able to access it in the catch
try {
vector<DataSourceInfo> new_data_sources;
- shared_ptr<ZoneTableSegment> ztable_segment(
- ZoneTableSegment::create(*config, rrclass_));
+ set<string> used_names;
for (; i < config->size(); ++i) {
// Extract the parameters
const ConstElementPtr dconf(config->get(i));
@@ -105,103 +100,77 @@ ConfigurableClientList::configure(const ConstElementPtr& config,
if (paramConf == ConstElementPtr()) {
paramConf.reset(new NullElement());
}
- const bool want_cache(allow_cache &&
- dconf->contains("cache-enable") &&
- dconf->get("cache-enable")->boolValue());
-
- if (type == "MasterFiles") {
- // In case the cache is not allowed, we just skip the master
- // files (at least for now)
- if (!allow_cache) {
- // We're not going to load these zones. Issue warnings about it.
- const map<string, ConstElementPtr>
- zones_files(paramConf->mapValue());
- for (map<string, ConstElementPtr>::const_iterator
- it(zones_files.begin()); it != zones_files.end();
- ++it) {
- LOG_WARN(logger, DATASRC_LIST_NOT_CACHED).
- arg(it->first).arg(rrclass_);
- }
- continue;
- }
- if (!want_cache) {
- isc_throw(ConfigurationError, "The cache must be enabled "
- "for the MasterFiles type");
- }
- new_data_sources.push_back(DataSourceInfo(rrclass_,
- ztable_segment,
- true));
- } else {
- // Ask the factory to create the data source for us
- const DataSourcePair ds(this->getDataSourceClient(type,
- paramConf));
- // And put it into the vector
- new_data_sources.push_back(DataSourceInfo(ds.first, ds.second,
- want_cache, rrclass_,
- ztable_segment));
+ // Get the name (either explicit, or guess)
+ const ConstElementPtr name_elem(dconf->get("name"));
+ const string datasrc_name =
+ name_elem ? name_elem->stringValue() : type;
+ if (!used_names.insert(datasrc_name).second) {
+ isc_throw(ConfigurationError, "Duplicate name in client list: "
+ << datasrc_name);
}
- if (want_cache) {
- if (!dconf->contains("cache-zones") && type != "MasterFiles") {
- isc_throw(isc::NotImplemented, "Auto-detection of zones "
- "to cache is not yet implemented, supply "
- "cache-zones parameter");
- // TODO: Auto-detect list of all zones in the
- // data source.
- }
+ // 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,
+ paramConf);
+ if (!allow_cache && !dsrc_pair.first) {
+ LOG_WARN(logger, DATASRC_LIST_NOT_CACHED).
+ arg(datasrc_name).arg(rrclass_);
+ continue;
+ }
- // List the zones we are loading
- vector<string> zones_origins;
- if (type == "MasterFiles") {
- const map<string, ConstElementPtr>
- zones_files(paramConf->mapValue());
- for (map<string, ConstElementPtr>::const_iterator
- it(zones_files.begin()); it != zones_files.end();
- ++it) {
- zones_origins.push_back(it->first);
- }
- } else {
- const ConstElementPtr zones(dconf->get("cache-zones"));
- for (size_t i(0); i < zones->size(); ++i) {
- zones_origins.push_back(zones->get(i)->stringValue());
- }
- }
+ // Build in-memory cache configuration, and create a set of
+ // related objects including the in-memory zone table for the
+ // cache.
+ boost::shared_ptr<internal::CacheConfig> cache_conf(
+ new internal::CacheConfig(type, dsrc_pair.first, *dconf,
+ allow_cache));
+ new_data_sources.push_back(DataSourceInfo(dsrc_pair.first,
+ dsrc_pair.second,
+ cache_conf, rrclass_,
+ datasrc_name));
+
+ // If cache is disabled, or the zone table segment is not (yet)
+ // writable, we are done for this data source.
+ // Otherwise load zones into the in-memory cache.
+ if (!cache_conf->isEnabled()) {
+ continue;
+ }
+ memory::ZoneTableSegment& zt_segment =
+ *new_data_sources.back().ztable_segment_;
+ if (!zt_segment.isWritable()) {
+ LOG_DEBUG(logger, DBGLVL_TRACE_BASIC,
+ DATASRC_LIST_CACHE_PENDING).arg(datasrc_name);
+ continue;
+ }
- const shared_ptr<InMemoryClient>
- cache(new_data_sources.back().cache_);
- const DataSourceClient* const
- client(new_data_sources.back().data_src_client_);
- for (vector<string>::const_iterator it(zones_origins.begin());
- it != zones_origins.end(); ++it) {
- const Name origin(*it);
- if (type == "MasterFiles") {
- try {
- cache->load(origin,
- paramConf->get(*it)->stringValue());
- } catch (const ZoneLoaderException& e) {
- LOG_ERROR(logger, DATASRC_LOAD_FROM_FILE_ERROR)
- .arg(origin).arg(e.what());
- }
- } else {
- ZoneIteratorPtr iterator;
- try {
- iterator = client->getIterator(origin);
- } catch (const DataSourceError&) {
- isc_throw(ConfigurationError, "Unable to "
- "cache non-existent zone "
- << origin);
- }
- if (!iterator) {
- isc_throw(isc::Unexpected, "Got NULL iterator "
- "for zone " << origin);
- }
- try {
- cache->load(origin, *iterator);
- } catch (const ZoneLoaderException& e) {
- LOG_ERROR(logger, DATASRC_LOAD_FROM_ITERATOR_ERROR)
- .arg(origin).arg(e.what());
- }
- }
+ internal::CacheConfig::ConstZoneIterator end_of_zones =
+ cache_conf->end();
+ for (internal::CacheConfig::ConstZoneIterator zone_it =
+ cache_conf->begin();
+ zone_it != end_of_zones;
+ ++zone_it)
+ {
+ const Name& zname = zone_it->first;
+ try {
+ const memory::LoadAction load_action =
+ cache_conf->getLoadAction(rrclass_, zname);
+ // in this loop this should be always true
+ assert(load_action);
+ memory::ZoneWriter writer(zt_segment,
+ load_action, zname, rrclass_);
+ writer.load();
+ writer.install();
+ writer.cleanup();
+ } catch (const NoSuchZone&) {
+ LOG_ERROR(logger, DATASRC_CACHE_ZONE_NOTFOUND).
+ arg(zname).arg(rrclass_).arg(datasrc_name);
+ } catch (const ZoneLoaderException& e) {
+ LOG_ERROR(logger, DATASRC_LOAD_ZONE_ERROR).
+ arg(zname).arg(rrclass_).arg(datasrc_name).
+ arg(e.what());
}
}
}
@@ -214,6 +183,9 @@ ConfigurableClientList::configure(const ConstElementPtr& config,
} catch (const TypeError& te) {
isc_throw(ConfigurationError, "Malformed configuration at data source "
"no. " << i << ": " << te.what());
+ } catch (const internal::CacheConfigError& ex) {
+ // convert to the "public" exception type.
+ isc_throw(ConfigurationError, ex.what());
}
}
@@ -345,108 +317,56 @@ ConfigurableClientList::findInternal(MutableResult& candidate,
// and the need_updater parameter is true, get the zone there.
}
-// We still provide this method for backward compatibility. But to not have
-// duplicate code, it is a thin wrapper around getCachedZoneWriter only.
-ConfigurableClientList::ReloadResult
-ConfigurableClientList::reload(const Name& name) {
- const ZoneWriterPair result(getCachedZoneWriter(name));
- if (result.first != ZONE_SUCCESS) {
- return (result.first);
- }
-
- assert(result.second);
- result.second->load();
- result.second->install();
- result.second->cleanup();
-
- return (ZONE_SUCCESS);
-}
-
-namespace {
-
-// We would like to use boost::bind for this. However, the loadZoneData takes
-// a reference, while we have a shared pointer to the iterator -- and we need
-// to keep it alive as long as the ZoneWriter is alive. Therefore we can't
-// really just dereference it and pass it, since it would get destroyed once
-// the getCachedZoneWriter would end. This class holds the shared pointer
-// alive, otherwise is mostly simple.
-//
-// It might be doable with nested boost::bind, but it would probably look
-// more awkward and complicated than this.
-class IteratorLoader {
-public:
- IteratorLoader(const RRClass& rrclass, const Name& name,
- const ZoneIteratorPtr& iterator) :
- rrclass_(rrclass),
- name_(name),
- iterator_(iterator)
- {}
- memory::ZoneData* operator()(util::MemorySegment& segment) {
- return (memory::loadZoneData(segment, rrclass_, name_, *iterator_));
- }
-private:
- const RRClass rrclass_;
- const Name name_;
- ZoneIteratorPtr iterator_;
-};
-
-// We can't use the loadZoneData function directly in boost::bind, since
-// it is overloaded and the compiler can't choose the correct version
-// reliably and fails. So we simply wrap it into an unique name.
-memory::ZoneData*
-loadZoneDataFromFile(util::MemorySegment& segment, const RRClass& rrclass,
- const Name& name, const string& filename)
-{
- return (memory::loadZoneData(segment, rrclass, name, filename));
-}
-
-}
-
ConfigurableClientList::ZoneWriterPair
-ConfigurableClientList::getCachedZoneWriter(const Name& name) {
+ConfigurableClientList::getCachedZoneWriter(const Name& name,
+ const std::string& datasrc_name)
+{
if (!allow_cache_) {
return (ZoneWriterPair(CACHE_DISABLED, ZoneWriterPtr()));
}
- // Try to find the correct zone.
- MutableResult result;
- findInternal(result, name, true, true);
- if (!result.finder) {
- return (ZoneWriterPair(ZONE_NOT_FOUND, ZoneWriterPtr()));
- }
- // Try to get the in-memory cache for the zone. If there's none,
- // we can't provide the result.
- if (!result.info->cache_) {
- return (ZoneWriterPair(ZONE_NOT_CACHED, ZoneWriterPtr()));
- }
- memory::LoadAction load_action;
- DataSourceClient* client(result.info->data_src_client_);
- if (client != NULL) {
- // Now finally provide the writer.
- // If it does not exist in client,
- // DataSourceError is thrown, which is exactly the result what we
- // want, so no need to handle it.
- ZoneIteratorPtr iterator(client->getIterator(name));
- if (!iterator) {
- isc_throw(isc::Unexpected, "Null iterator from " << name);
+
+ // Find the data source from which the zone to be loaded into memory.
+ // Then get the appropriate load action and create a zone writer.
+ BOOST_FOREACH(const DataSourceInfo& info, data_sources_) {
+ if (!datasrc_name.empty() && datasrc_name != info.name_) {
+ continue;
}
- // And wrap the iterator into the correct functor (which
- // keeps it alive as long as it is needed).
- load_action = IteratorLoader(rrclass_, name, iterator);
- } else {
- // The MasterFiles special case
- const string filename(result.info->cache_->getFileName(name));
- if (filename.empty()) {
- isc_throw(isc::Unexpected, "Confused about missing both filename "
- "and data source");
+ // If there's an underlying "real" data source and it doesn't contain
+ // the given name, obviously we cannot load it. If a specific data
+ // source is given by the name, search should stop here.
+ if (info.data_src_client_ &&
+ info.data_src_client_->findZone(name).code != result::SUCCESS) {
+ if (!datasrc_name.empty()) {
+ return (ZoneWriterPair(ZONE_NOT_FOUND, ZoneWriterPtr()));
+ }
+ continue;
+ }
+ // If the corresponding zone table segment is not (yet) writable,
+ // we cannot load at this time.
+ if (info.ztable_segment_ && !info.ztable_segment_->isWritable()) {
+ return (ZoneWriterPair(CACHE_NOT_WRITABLE, ZoneWriterPtr()));
+ }
+ // Note that getCacheConfig() must return non NULL in this module
+ // (only tests could set it to a bogus value).
+ const memory::LoadAction load_action =
+ info.getCacheConfig()->getLoadAction(rrclass_, name);
+ if (!load_action) {
+ return (ZoneWriterPair(ZONE_NOT_CACHED, ZoneWriterPtr()));
}
- // boost::bind is enough here.
- load_action = boost::bind(loadZoneDataFromFile, _1, rrclass_, name,
- filename);
+ return (ZoneWriterPair(ZONE_SUCCESS,
+ ZoneWriterPtr(
+ new memory::ZoneWriter(
+ *info.ztable_segment_,
+ load_action, name, rrclass_))));
}
- return (ZoneWriterPair(ZONE_SUCCESS,
- ZoneWriterPtr(
- result.info->ztable_segment_->
- getZoneWriter(load_action, name, rrclass_))));
+
+ // We can't find the specified zone. If a specific data source was
+ // given, this means the given name of data source doesn't exist, so
+ // we report it so.
+ if (!datasrc_name.empty()) {
+ return (ZoneWriterPair(DATASRC_NOT_FOUND, ZoneWriterPtr()));
+ }
+ return (ZoneWriterPair(ZONE_NOT_FOUND, ZoneWriterPtr()));
}
// NOTE: This function is not tested, it would be complicated. However, the
@@ -457,10 +377,27 @@ ConfigurableClientList::getDataSourceClient(const string& type,
const ConstElementPtr&
configuration)
{
+ if (type == "MasterFiles") {
+ return (DataSourcePair(0, DataSourceClientContainerPtr()));
+ }
+
DataSourceClientContainerPtr
container(new DataSourceClientContainer(type, configuration));
return (DataSourcePair(&container->getInstance(), container));
}
+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"));
+ }
+ return (result);
+}
+
}
}
diff --git a/src/lib/datasrc/client_list.h b/src/lib/datasrc/client_list.h
index d1a35b5..73bc2b3 100644
--- a/src/lib/datasrc/client_list.h
+++ b/src/lib/datasrc/client_list.h
@@ -46,6 +46,80 @@ class InMemoryClient;
class ZoneWriter;
}
+namespace internal {
+class CacheConfig;
+}
+
+/// \brief Segment status of the cache
+///
+/// Describes the status in which the memory segment for the in-memory cache of
+// /given data source is.
+enum MemorySegmentState {
+ /// \brief No segment used for this data source.
+ ///
+ /// This is usually a result of the cache being disabled.
+ SEGMENT_UNUSED,
+
+ /// \brief It is a mapped segment and we wait for information how to map
+ /// it.
+ SEGMENT_WAITING,
+
+ /// \brief The segment is ready to be used.
+ SEGMENT_INUSE
+};
+
+/// \brief Status of one data source.
+///
+/// This indicates the status a data soure is in. It is used with segment
+/// and cache management, to discover the data sources that need external
+/// mapping or local loading.
+///
+/// In future, it may be extended for other purposes, such as performing an
+/// operation on named data source.
+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.
+ DataSourceStatus(const std::string& name, MemorySegmentState state,
+ const std::string& type) :
+ name_(name),
+ type_(type),
+ state_(state)
+ {}
+
+ /// \brief Get the segment state
+ MemorySegmentState getSegmentState() const {
+ return (state_);
+ }
+
+ /// \brief Get the segment type
+ ///
+ /// \note Specific values of the type are only meaningful for the
+ /// corresponding memory segment implementation and modules that
+ /// directly manage the segments. Other normal applications should
+ /// treat them as opaque identifiers.
+ ///
+ /// \throw isc::InvalidOperation if called and state is SEGMENT_UNUSED.
+ const std::string& getSegmentType() const {
+ if (getSegmentState() == SEGMENT_UNUSED) {
+ isc_throw(isc::InvalidOperation,
+ "No segment used, no type therefore.");
+ }
+ return (type_);
+ }
+
+ /// \brief Get the name.
+ const std::string& getName() const {
+ return (name_);
+ }
+private:
+ std::string name_;
+ std::string type_;
+ MemorySegmentState state_;
+};
+
/// \brief The list of data source clients.
///
/// The purpose of this class is to hold several data source clients and search
@@ -100,7 +174,7 @@ public:
/// \brief Negative answer constructor.
///
- /// This conscructs a result for negative answer. Both pointers are
+ /// This constructs a result for negative answer. Both pointers are
/// NULL, and exact_match_ is false.
FindResult() :
dsrc_client_(NULL),
@@ -265,48 +339,51 @@ public:
return (configuration_);
}
- /// \brief Result of the reload() method.
- enum ReloadResult {
- CACHE_DISABLED, ///< The cache is not enabled in this list.
- ZONE_NOT_CACHED, ///< Zone is served directly, not from cache.
- ZONE_NOT_FOUND, ///< Zone does not exist or not cached.
- ZONE_SUCCESS ///< The zone was successfully reloaded or
- /// the writer provided.
- };
-
- /// \brief Reloads a cached zone.
- ///
- /// This method finds a zone which is loaded into a cache and reloads it.
- /// This may be used to renew the cache when the underlying data source
- /// changes.
- ///
- /// \param zone The origin of the zone to reload.
- /// \return A status if the command worked.
- /// \throw DataSourceError or anything else that the data source
- /// containing the zone might throw is propagated.
- /// \throw DataSourceError if something unexpected happens, like when
- /// the original data source no longer contains the cached zone.
- ReloadResult reload(const dns::Name& zone);
-
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.
+ enum CacheStatus {
+ CACHE_DISABLED, ///< The cache is not enabled in this list.
+ ZONE_NOT_CACHED, ///< Zone is not to be cached (including the case
+ /// where caching is disabled for the specific
+ /// data source).
+ ZONE_NOT_FOUND, ///< Zone does not exist in this list.
+ CACHE_NOT_WRITABLE, ///< The cache is not writable (and zones can't
+ /// be loaded)
+ DATASRC_NOT_FOUND, ///< Specific data source for load is specified
+ /// but it's not in the list
+ ZONE_SUCCESS ///< Zone to be cached is successfully found and
+ /// is ready to be loaded
+ };
/// \brief Return value of getCachedZoneWriter()
///
/// A pair containing status and the zone writer, for the
/// getCachedZoneWriter() method.
- typedef std::pair<ReloadResult, ZoneWriterPtr> ZoneWriterPair;
+ typedef std::pair<CacheStatus, ZoneWriterPtr> ZoneWriterPair;
- /// \brief Return a zone writer that can be used to reload a zone.
+ /// \brief Return a zone writer that can be used to (re)load a zone.
///
- /// This looks up a cached copy of zone and returns the ZoneWriter
- /// that can be used to reload the content of the zone. This can
- /// be used instead of reload() -- reload() works synchronously, which
- /// is not what is needed every time.
+ /// By default this method identifies the first data source in the list
+ /// that should serve the zone of the given name, and returns a ZoneWriter
+ /// object that can be used to load the content of the zone, in a specific
+ /// way for that data source.
///
- /// \param zone The origin of the zone to reload.
+ /// If the optional \c datasrc_name parameter is provided with a non empty
+ /// string, this method only tries to load the specified zone into or with
+ /// the data source which has the given name, regardless where in the list
+ /// that data source is placed. Even if the given name of zone doesn't
+ /// exist in the data source, other data sources are not searched and
+ /// this method simply returns ZONE_NOT_FOUND in the first element
+ /// of the pair.
+ ///
+ /// \param zone The origin of the zone to load.
+ /// \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
/// 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
@@ -314,9 +391,8 @@ public:
/// NULL.
/// \throw DataSourceError or anything else that the data source
/// containing the zone might throw is propagated.
- /// \throw DataSourceError if something unexpected happens, like when
- /// the original data source no longer contains the cached zone.
- ZoneWriterPair getCachedZoneWriter(const dns::Name& zone);
+ ZoneWriterPair getCachedZoneWriter(const dns::Name& zone,
+ const std::string& datasrc_name = "");
/// \brief Implementation of the ClientList::find.
virtual FindResult find(const dns::Name& zone,
@@ -327,18 +403,11 @@ public:
///
/// \todo The content yet to be defined.
struct DataSourceInfo {
- // Plays a role of default constructor too (for vector)
- DataSourceInfo(const dns::RRClass& rrclass,
- const boost::shared_ptr
- <isc::datasrc::memory::ZoneTableSegment>&
- ztable_segment,
- bool has_cache = false);
DataSourceInfo(DataSourceClient* data_src_client,
const DataSourceClientContainerPtr& container,
- bool has_cache, const dns::RRClass& rrclass,
- const boost::shared_ptr
- <isc::datasrc::memory::ZoneTableSegment>&
- ztable_segment);
+ boost::shared_ptr<internal::CacheConfig> cache_conf,
+ const dns::RRClass& rrclass,
+ const std::string& name);
DataSourceClient* data_src_client_;
DataSourceClientContainerPtr container_;
@@ -350,6 +419,15 @@ public:
const DataSourceClient* getCacheClient() const;
boost::shared_ptr<memory::InMemoryClient> cache_;
boost::shared_ptr<memory::ZoneTableSegment> ztable_segment_;
+ std::string name_;
+
+ // cache_conf_ can be accessed only from this read-only getter,
+ // to protect its integrity as much as possible.
+ const internal::CacheConfig* getCacheConfig() const {
+ return (cache_conf_.get());
+ }
+ private:
+ boost::shared_ptr<internal::CacheConfig> cache_conf_;
};
/// \brief The collection of data sources.
@@ -369,6 +447,13 @@ public:
/// Also, derived classes could want to create the data source clients
/// in a different way, though inheriting this class is not recommended.
///
+ /// Some types of data sources can be internal to the \c ClientList
+ /// implementation and do not require a corresponding dynamic module
+ /// loaded via \c DataSourceClientContainer. In such a case, this method
+ /// simply returns a pair of null pointers. It will help the caller reduce
+ /// type dependent processing. Currently, "MasterFiles" is considered to
+ /// be this type of data sources.
+ ///
/// The parameters are the same as of the constructor.
/// \return Pair containing both the data source client and the container.
/// The container might be NULL in the derived class, it is
@@ -379,6 +464,15 @@ public:
virtual DataSourcePair getDataSourceClient(const std::string& type,
const data::ConstElementPtr&
configuration);
+
+ /// \brief Get status information of all internal data sources.
+ ///
+ /// Get a DataSourceStatus for current state of each data source client
+ /// in this list.
+ ///
+ /// This may throw standard exceptions, such as std::bad_alloc. Otherwise,
+ /// it is exception free.
+ std::vector<DataSourceStatus> getStatus() const;
public:
/// \brief Access to the data source clients.
///
diff --git a/src/lib/datasrc/data_source.h b/src/lib/datasrc/data_source.h
deleted file mode 100644
index bf5a7d7..0000000
--- a/src/lib/datasrc/data_source.h
+++ /dev/null
@@ -1,68 +0,0 @@
-// 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.
-
-#ifndef DATA_SOURCE_H
-#define DATA_SOURCE_H
-
-#include <stdint.h>
-
-#include <vector>
-
-#include <boost/shared_ptr.hpp>
-
-#include <exceptions/exceptions.h>
-
-#include <dns/name.h>
-#include <dns/rrclass.h>
-#include <cc/data.h>
-
-namespace isc {
-
-namespace dns {
-class Name;
-class RRType;
-class RRset;
-class RRsetList;
-}
-
-namespace datasrc {
-
-/// This exception represents Backend-independent errors relating to
-/// data source operations.
-class DataSourceError : public Exception {
-public:
- DataSourceError(const char* file, size_t line, const char* what) :
- isc::Exception(file, line, what) {}
-};
-
-/// \brief No such serial number when obtaining difference iterator
-///
-/// Thrown if either the zone/start serial number or zone/end serial number
-/// combination does not exist in the differences table. (Note that this
-/// includes the case where the differences table contains no records related
-/// to that zone.)
-class NoSuchSerial : public DataSourceError {
-public:
- NoSuchSerial(const char* file, size_t line, const char* what) :
- DataSourceError(file, line, what) {}
-};
-
-}
-}
-
-#endif
-
-// Local Variables:
-// mode: c++
-// End:
diff --git a/src/lib/datasrc/database.cc b/src/lib/datasrc/database.cc
index f6d8252..9c85054 100644
--- a/src/lib/datasrc/database.cc
+++ b/src/lib/datasrc/database.cc
@@ -17,7 +17,7 @@
#include <vector>
#include <datasrc/database.h>
-#include <datasrc/data_source.h>
+#include <datasrc/exceptions.h>
#include <datasrc/zone_iterator.h>
#include <datasrc/rrset_collection_base.h>
@@ -30,7 +30,7 @@
#include <dns/rdataclass.h>
#include <dns/nsec3hash.h>
-#include <datasrc/data_source.h>
+#include <datasrc/exceptions.h>
#include <datasrc/logger.h>
#include <boost/foreach.hpp>
@@ -1233,7 +1233,7 @@ public:
const pair<bool, int> zone(accessor_->getZone(zone_name.toText()));
if (!zone.first) {
// No such zone, can't continue
- isc_throw(DataSourceError, "Zone " + zone_name.toText() +
+ isc_throw(NoSuchZone, "Zone " + zone_name.toText() +
" can not be iterated, because it doesn't exist "
"in this data source");
}
@@ -1644,17 +1644,23 @@ DatabaseUpdater::addRRset(const AbstractRRset& rrset) {
{ cvtr.getName(), cvtr.getType(), cvtr.getTTL(), rdata_txt };
accessor_->addRecordDiff(zone_id_, serial_.getValue(),
Accessor::DIFF_ADD, journal);
+ LOG_DEBUG(logger, DBG_TRACE_DETAILED, DATASRC_DATABASE_ADDDIFF).
+ arg(cvtr.getName()).arg(cvtr.getType()).arg(rdata_txt);
}
if (nsec3_type) {
const string nsec3_columns[Accessor::ADD_NSEC3_COLUMN_COUNT] =
{ cvtr.getNSEC3Name(), cvtr.getTTL(), cvtr.getType(),
rdata_txt };
accessor_->addNSEC3RecordToZone(nsec3_columns);
+ LOG_DEBUG(logger, DBG_TRACE_DETAILED, DATASRC_DATABASE_ADDNSEC3).
+ arg(cvtr.getNSEC3Name()).arg(rdata_txt);
} else {
const string columns[Accessor::ADD_COLUMN_COUNT] =
{ cvtr.getName(), cvtr.getRevName(), cvtr.getTTL(),
cvtr.getType(), sigtype, rdata_txt };
accessor_->addRecordToZone(columns);
+ LOG_DEBUG(logger, DBG_TRACE_DETAILED, DATASRC_DATABASE_ADDRR).
+ arg(cvtr.getName()).arg(cvtr.getType()).arg(rdata_txt);
}
}
}
@@ -1698,14 +1704,22 @@ DatabaseUpdater::deleteRRset(const AbstractRRset& rrset) {
{ cvtr.getName(), cvtr.getType(), cvtr.getTTL(), rdata_txt };
accessor_->addRecordDiff(zone_id_, serial_.getValue(),
Accessor::DIFF_DELETE, journal);
+ LOG_DEBUG(logger, DBG_TRACE_DETAILED, DATASRC_DATABASE_DELETEDIFF).
+ arg(cvtr.getName()).arg(cvtr.getType()).arg(rdata_txt);
}
- const string params[Accessor::DEL_PARAM_COUNT] =
- { nsec3_type ? cvtr.getNSEC3Name() : cvtr.getName(),
- cvtr.getType(), rdata_txt };
if (nsec3_type) {
+ const string params[Accessor::DEL_NSEC3_PARAM_COUNT] =
+ { cvtr.getNSEC3Name(), cvtr.getType(), rdata_txt };
accessor_->deleteNSEC3RecordInZone(params);
+ LOG_DEBUG(logger, DBG_TRACE_DETAILED, DATASRC_DATABASE_DELETENSEC3).
+ arg(cvtr.getNSEC3Name()).arg(rdata_txt);
} else {
+ const string params[Accessor::DEL_PARAM_COUNT] =
+ { cvtr.getName(), cvtr.getType(), rdata_txt,
+ cvtr.getRevName() };
accessor_->deleteRecordInZone(params);
+ LOG_DEBUG(logger, DBG_TRACE_DETAILED, DATASRC_DATABASE_DELETERR).
+ arg(cvtr.getName()).arg(cvtr.getType()).arg(rdata_txt);
}
}
}
diff --git a/src/lib/datasrc/database.h b/src/lib/datasrc/database.h
index 19c28eb..6e675e2 100644
--- a/src/lib/datasrc/database.h
+++ b/src/lib/datasrc/database.h
@@ -24,7 +24,7 @@
#include <dns/rrset.h>
#include <dns/rrtype.h>
-#include <datasrc/data_source.h>
+#include <datasrc/exceptions.h>
#include <datasrc/client.h>
#include <datasrc/zone.h>
#include <datasrc/logger.h>
@@ -116,18 +116,42 @@ public:
ADD_NSEC3_COLUMN_COUNT = 4 ///< Number of columns
};
- /// \brief Definitions of the fields to be passed to deleteRecordInZone()
- /// and deleteNSEC3RecordInZone()
+ /// \brief Definitions of the fields to be passed to deleteRecordInZone().
///
/// Each derived implementation of deleteRecordInZone() should expect
/// the "params" array to be filled with the values as described in this
/// enumeration, in this order.
+ ///
+ /// DEL_RNAME is included in case the reversed form is more convenient
+ /// for the underlying implementation to identify the record to be
+ /// deleted (reversed names are generally easier to sort, which may help
+ /// perform the search faster). It's up to the underlying implementation
+ /// which one (or both) it uses for the search. DEL_NAME and DEL_RNAME
+ /// are mutually convertible with the understanding of DNS names, and
+ /// in that sense redundant. But both are provided so the underlying
+ /// implementation doesn't have to deal with DNS level concepts.
enum DeleteRecordParams {
- DEL_NAME = 0, ///< The owner name of the record (a domain name)
- ///< or the hash label for deleteNSEC3RecordInZone()
+ DEL_NAME = 0, ///< The owner name of the record (a domain name).
DEL_TYPE = 1, ///< The RRType of the record (A/NS/TXT etc.)
DEL_RDATA = 2, ///< Full text representation of the record's RDATA
- DEL_PARAM_COUNT = 3 ///< Number of parameters
+ DEL_RNAME = 3, ///< As DEL_NAME, but with the labels of domain name
+ ///< in reverse order (eg. org.example.).
+ DEL_PARAM_COUNT = 4 ///< Number of parameters
+ };
+
+ /// \brief Definitions of the fields to be passed to
+ /// deleteNSEC3RecordInZone().
+ ///
+ /// Each derived implementation of deleteNSEC3RecordInZone() should expect
+ /// the "params" array to be filled with the values as described in this
+ /// enumeration, in this order.
+ enum DeleteNSEC3RecordParams {
+ DEL_NSEC3_HASH = 0, ///< The hash (1st) label of the owren name,
+ ///< excluding the dot character.
+ DEL_NSEC3_TYPE = 1, ///< The type of RR. Either RRSIG or NSEC3.
+ DEL_NSEC3_RDATA = 2, ///< Full text representation of the record's
+ ///< RDATA. Must match the one in the database.
+ DEL_NSEC3_PARAM_COUNT = 3 ///< Number of parameters.
};
/// \brief Operation mode when adding a record diff.
@@ -161,7 +185,7 @@ public:
///
/// This method looks up a zone for the given name in the database. It
/// should match only exact zone name (eg. name is equal to the zone's
- /// apex), as the DatabaseClient will loop trough the labels itself and
+ /// apex), as the DatabaseClient will loop through the labels itself and
/// find the most suitable zone.
///
/// It is not specified if and what implementation of this method may
@@ -314,7 +338,7 @@ public:
/// \note In case there are multiple NSEC3 chains and they collide
/// (unlikely, but it can happen), this can return multiple NSEC3
/// records.
- /// \exception any Since any implementaion can be used, the caller should
+ /// \exception any Since any implementation can be used, the caller should
/// expect any exception to be thrown.
/// \exception isc::NotImplemented in case the database does not support
/// NSEC3
@@ -576,11 +600,8 @@ public:
/// \c addRecordToZone() and \c addNSEC3RecordToZone(), and the same
/// notes apply to this method.
///
- /// This method uses the same set of parameters to specify the record
- /// to be deleted as \c deleteRecordInZone(), but the \c DEL_NAME column
- /// is expected to only store the hash label of the owner name.
- /// This is the same as \c ADD_NSEC3_HASH column for
- /// \c addNSEC3RecordToZone().
+ /// This method uses the \c DeleteNSEC3RecordParams enum to specify the
+ /// values.
///
/// \exception DataSourceError Invalid call without starting a transaction,
/// or other internal database error.
@@ -590,7 +611,7 @@ public:
/// \param params An array of strings that defines a record to be deleted
/// from the NSEC3 namespace of the zone.
virtual void deleteNSEC3RecordInZone(
- const std::string (¶ms)[DEL_PARAM_COUNT]) = 0;
+ const std::string (¶ms)[DEL_NSEC3_PARAM_COUNT]) = 0;
/// \brief Start a general transaction.
///
@@ -868,7 +889,7 @@ public:
/// database.
///
/// Application should not come directly in contact with this class
- /// (it should handle it trough generic ZoneFinder pointer), therefore
+ /// (it should handle it through generic ZoneFinder pointer), therefore
/// it could be completely hidden in the .cc file. But it is provided
/// to allow testing and for rare cases when a database needs slightly
/// different handling, so it can be subclassed.
diff --git a/src/lib/datasrc/datasrc_messages.mes b/src/lib/datasrc/datasrc_messages.mes
index 2235df8..dbba18d 100644
--- a/src/lib/datasrc/datasrc_messages.mes
+++ b/src/lib/datasrc/datasrc_messages.mes
@@ -70,6 +70,15 @@ The maximum allowed number of items of the hotspot cache is set to the given
number. If there are too many, some of them will be dropped. The size of 0
means no limit.
+% DATASRC_CACHE_ZONE_NOTFOUND Zone %1/%2 not found on data source '%3' to cache
+During data source configuration, a zone is to be loaded (cached) in
+to memory from the underlying data source, but the zone is not found
+in the data source. This particular zone was not loaded, but data
+source configuration continues, possibly loading other zones into
+memory. This is basically some kind of configuration or operation
+error: either the name is incorrect in the configuration, or the zone
+has been removed from the data source without updating the configuration.
+
% DATASRC_CHECK_ERROR post-load check of zone %1/%2 failed: %3
The zone was loaded into the data source successfully, but the content fails
basic sanity checks. See the message if you want to know what exactly is wrong
@@ -83,11 +92,37 @@ with the content. The problem does not stop the new version from being used
but it should still be checked and fixed. See the message to know what exactly
is wrong with the data.
+% DATASRC_DATABASE_ADDDIFF updated diff table for add: %1 %2 %3
+Debug message. A difference record for adding a record to the zone is being
+appended to the difference table. The name, type and rdata of the record is
+logged.
+
+% DATASRC_DATABASE_ADDNSEC3 added NSEC3 RR: %1 %2
+Debug message. A new NSEC3 record is added to the table. The hash and the rdata
+is logged.
+
+% DATASRC_DATABASE_ADDRR added RR: %1 %2 %3
+Debug message. A new resource record is added to the table. The name, type and
+rdata is logged.
+
% DATASRC_DATABASE_COVER_NSEC_UNSUPPORTED %1 doesn't support DNSSEC when asked for NSEC data covering %2
The datasource tried to provide an NSEC proof that the named domain does not
exist, but the database backend doesn't support DNSSEC. No proof is included
in the answer as a result.
+% DATASRC_DATABASE_DELETEDIFF updated diff table for delete: %1 %2 %3
+Debug message. A difference record for removing a record from the zone is being
+appended to the difference table. The name, type and rdata of the record is
+logged.
+
+% DATASRC_DATABASE_DELETENSEC3 deleted NSEC3 RR: %1 %2
+Debug message. An NSEC3 record is removed from the table. The name, type and
+rdata is logged.
+
+% DATASRC_DATABASE_DELETERR deleted RR: %1 %2 %3
+Debug message. A resource record is removed from the table. The name, type and
+rdata is logged.
+
% DATASRC_DATABASE_FINDNSEC3 Looking for NSEC3 for %1 in %2 mode
Debug information. A search in an database data source for NSEC3 that
matches or covers the given name is being started.
@@ -321,22 +356,26 @@ not contain RRs the requested type. AN NXRRSET indication is returned.
A debug message indicating that a query for the given name and RR type is being
processed.
-% DATASRC_LIST_NOT_CACHED zone %1/%2 not cached, cache disabled globally. Will not be available.
-The process disabled caching of RR data completely. However, the given zone
-is provided as a master file and it can be served from memory cache only.
-Therefore, the zone will not be available for this process. If this is
-a problem, you should move the zone to some database backend (sqlite3, for
-example) and use it from there.
-
-% DATASRC_LOAD_FROM_FILE_ERROR Error loading zone %1: %2
-An error was found in the zone data when it was being loaded from a
-file. The zone was not loaded. The specific error is shown in the
-message, and should be addressed.
-
-% DATASRC_LOAD_FROM_ITERATOR_ERROR Error loading zone %1: %2
-An error was found in the zone data when it was being loaded from
-another data source. The zone was not loaded. The specific error is
-shown in the message, and should be addressed.
+% DATASRC_LIST_CACHE_PENDING in-memory cache for data source '%1' is not yet writable, pending load
+While (re)configuring data source clients, zone data of the shown data
+source cannot be loaded to in-memory cache at that point because the
+cache is not yet ready for writing. This can happen for shared-memory
+type of cache, in which case the cache will be reset later, either
+by a higher level application or by a command from other module.
+
+% DATASRC_LIST_NOT_CACHED zones in data source %1 for class %2 not cached, cache disabled globally. Will not be available.
+The process disabled caching of RR data completely. However, this data source
+is provided from a master file and it can be served from memory cache only.
+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_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
+particular zone was not loaded, but data source configuration
+continues, possibly loading other zones into memory. The specific
+error is shown in the message, and should be addressed.
% DATASRC_MASTER_LOAD_ERROR %1:%2: Zone '%3/%4' contains error: %5
There's an error in the given master file. The zone won't be loaded for
@@ -550,131 +589,6 @@ given domain or record. The code is 1 for error and 2 for not implemented.
While processing a wildcard, a referral was met. But it wasn't possible to get
enough information for it. The code is 1 for error, 2 for not implemented.
-% DATASRC_SQLITE_CLOSE closing SQLite database
-Debug information. The SQLite data source is closing the database file.
-
-% DATASRC_SQLITE_COMPATIBLE_VERSION database schema V%1.%2 not up to date (expecting V%3.%4) but is compatible
-The version of the SQLite3 database schema used to hold the zone data
-is not the latest one - the current version of BIND 10 was written
-with a later schema version in mind. However, the database is
-compatible with the current version of BIND 10, and BIND 10 will run
-without any problems.
-
-Consult the release notes for your version of BIND 10. Depending on
-the changes made to the database schema, it is possible that improved
-performance could result if the database were upgraded.
-
-% DATASRC_SQLITE_CONNCLOSE Closing sqlite database
-The database file is no longer needed and is being closed.
-
-% DATASRC_SQLITE_CONNOPEN Opening sqlite database file '%1'
-The database file is being opened so it can start providing data.
-
-% DATASRC_SQLITE_CREATE SQLite data source created
-Debug information. An instance of SQLite data source is being created.
-
-% DATASRC_SQLITE_DESTROY SQLite data source destroyed
-Debug information. An instance of SQLite data source is being destroyed.
-
-% DATASRC_SQLITE_DROPCONN SQLite3Database is being deinitialized
-The object around a database connection is being destroyed.
-
-% DATASRC_SQLITE_ENCLOSURE looking for zone containing '%1'
-Debug information. The SQLite data source is trying to identify which zone
-should hold this domain.
-
-% DATASRC_SQLITE_ENCLOSURE_NOT_FOUND no zone contains '%1'
-Debug information. The last SQLITE_ENCLOSURE query was unsuccessful; there's
-no such zone in our data.
-
-% DATASRC_SQLITE_FIND looking for RRset '%1/%2'
-Debug information. The SQLite data source is looking up a resource record
-set.
-
-% DATASRC_SQLITE_FINDADDRS looking for A/AAAA addresses for '%1'
-Debug information. The data source is looking up the addresses for given
-domain name.
-
-% DATASRC_SQLITE_FINDADDRS_BAD_CLASS class mismatch looking for addresses ('%1' and '%2')
-The SQLite data source was looking up A/AAAA addresses, but the data source
-contains different class than the query was for.
-
-% DATASRC_SQLITE_FINDEXACT looking for exact RRset '%1/%2'
-Debug information. The SQLite data source is looking up an exact resource
-record.
-
-% DATASRC_SQLITE_FINDEXACT_BAD_CLASS class mismatch looking for an RRset ('%1' and '%2')
-The SQLite data source was looking up an exact RRset, but the data source
-contains different class than the query was for.
-
-% DATASRC_SQLITE_FINDREC looking for record '%1/%2'
-Debug information. The SQLite data source is looking up records of given name
-and type in the database.
-
-% DATASRC_SQLITE_FINDREF looking for referral at '%1'
-Debug information. The SQLite data source is identifying if this domain is
-a referral and where it goes.
-
-% DATASRC_SQLITE_FINDREF_BAD_CLASS class mismatch looking for referral ('%1' and '%2')
-The SQLite data source was trying to identify if there's a referral. But
-it contains different class than the query was for.
-
-% DATASRC_SQLITE_FIND_BAD_CLASS class mismatch looking for an RRset ('%1' and '%2')
-The SQLite data source was looking up an RRset, but the data source contains
-different class than the query was for.
-
-% DATASRC_SQLITE_FIND_NSEC3 looking for NSEC3 in zone '%1' for hash '%2'
-Debug information. We're trying to look up a NSEC3 record in the SQLite data
-source.
-
-% DATASRC_SQLITE_FIND_NSEC3_NO_ZONE no such zone '%1'
-The SQLite data source was asked to provide a NSEC3 record for given zone.
-But it doesn't contain that zone.
-
-% DATASRC_SQLITE_INCOMPATIBLE_VERSION database schema V%1.%2 incompatible with version (V%3.%4) expected
-The version of the SQLite3 database schema used to hold the zone data
-is incompatible with the version expected by BIND 10. As a result,
-BIND 10 is unable to run using the database file as the data source.
-
-The database should be updated using the means described in the BIND
-10 documentation.
-
-% DATASRC_SQLITE_NEWCONN SQLite3Database is being initialized
-A wrapper object to hold database connection is being initialized.
-
-% DATASRC_SQLITE_OPEN opening SQLite database '%1'
-Debug information. The SQLite data source is loading an SQLite database in
-the provided file.
-
-% DATASRC_SQLITE_PREVIOUS looking for name previous to '%1'
-This is a debug message. The name given was not found, so the program
-is searching for the next name higher up the hierarchy (e.g. if
-www.example.com were queried for and not found, the software searches
-for the "previous" name, example.com).
-
-% DATASRC_SQLITE_PREVIOUS_NO_ZONE no zone containing '%1'
-The name given was not found, so the program is searching for the next
-name higher up the hierarchy (e.g. if www.example.com were queried
-for and not found, the software searches for the "previous" name,
-example.com). However, this name is not contained in any zone in the
-data source. This is an error since it indicates a problem in the earlier
-processing of the query.
-
-% DATASRC_SQLITE_SETUP setting up new SQLite3 database in '%1'
-The database for SQLite data source was found empty. It is assumed this is the
-first run and it is being initialized with current schema. It'll still contain
-no data, but it will be ready for use. If this is indeed the first run of
-BIND 10, it is to be expected and completely harmless. If you just configured
-a data source to point to an existing file and you see this, you may have
-misspelled the file name.
-
-% DATASRC_SQLITE_SETUP_OLD_API setting up new SQLite database
-The database for SQLite data source was found empty. It is assumed this is the
-first run and it is being initialized with current schema. It'll still contain
-no data, but it will be ready for use. This is similar to DATASRC_SQLITE_SETUP
-message, but it is logged from the old API. You should never see it, since the
-API is deprecated.
-
% DATASRC_STATIC_CLASS_NOT_CH static data source can handle CH class only
An error message indicating that a query requesting a RR for a class other
that CH was sent to the static data source (which only handles CH queries).
diff --git a/src/lib/datasrc/exceptions.h b/src/lib/datasrc/exceptions.h
index 749b955..d3b25da 100644
--- a/src/lib/datasrc/exceptions.h
+++ b/src/lib/datasrc/exceptions.h
@@ -20,6 +20,42 @@
namespace isc {
namespace datasrc {
+/// \brief Top level exception related to data source.
+///
+/// This exception is the most generic form of exception for errors or
+/// unexpected events that can happen in the data source module. In general,
+/// if an application needs to catch these conditions explicitly, it should
+/// catch more specific exceptions derived from this class; the severity
+/// of the conditions will vary very much, and such an application would
+/// normally like to behave differently depending on the severity.
+class DataSourceError : public Exception {
+public:
+ DataSourceError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// \brief No such serial number when obtaining difference iterator
+///
+/// Thrown if either the zone/start serial number or zone/end serial number
+/// combination does not exist in the differences table. (Note that this
+/// includes the case where the differences table contains no records related
+/// to that zone.)
+class NoSuchSerial : public DataSourceError {
+public:
+ NoSuchSerial(const char* file, size_t line, const char* what) :
+ DataSourceError(file, line, what) {}
+};
+
+/// \brief A specified zone does not exist in the specified data source.
+///
+/// This exception is thrown from methods that take a zone name and perform
+/// some action regarding that zone on the corresponding data source.
+class NoSuchZone : public DataSourceError {
+public:
+ NoSuchZone(const char* file, size_t line, const char* what) :
+ DataSourceError(file, line, what) {}
+};
+
/// Base class for a number of exceptions that are thrown while working
/// with zones.
struct ZoneException : public Exception {
diff --git a/src/lib/datasrc/factory.cc b/src/lib/datasrc/factory.cc
index 33338db..73f7e4a 100644
--- a/src/lib/datasrc/factory.cc
+++ b/src/lib/datasrc/factory.cc
@@ -14,7 +14,7 @@
#include "factory.h"
-#include "data_source.h"
+#include "exceptions.h"
#include "database.h"
#include "sqlite3_accessor.h"
diff --git a/src/lib/datasrc/factory.h b/src/lib/datasrc/factory.h
index 45e4f9b..4e669ec 100644
--- a/src/lib/datasrc/factory.h
+++ b/src/lib/datasrc/factory.h
@@ -15,7 +15,7 @@
#ifndef DATA_SOURCE_FACTORY_H
#define DATA_SOURCE_FACTORY_H 1
-#include <datasrc/data_source.h>
+#include <datasrc/exceptions.h>
#include <datasrc/client.h>
#include <cc/data.h>
diff --git a/src/lib/datasrc/memory/Makefile.am b/src/lib/datasrc/memory/Makefile.am
index 27c2de3..434eaf2 100644
--- a/src/lib/datasrc/memory/Makefile.am
+++ b/src/lib/datasrc/memory/Makefile.am
@@ -23,11 +23,15 @@ libdatasrc_memory_la_SOURCES += zone_table.h zone_table.cc
libdatasrc_memory_la_SOURCES += zone_finder.h zone_finder.cc
libdatasrc_memory_la_SOURCES += zone_table_segment.h zone_table_segment.cc
libdatasrc_memory_la_SOURCES += zone_table_segment_local.h zone_table_segment_local.cc
+
+if USE_SHARED_MEMORY
+libdatasrc_memory_la_SOURCES += zone_table_segment_mapped.h zone_table_segment_mapped.cc
+endif
+
libdatasrc_memory_la_SOURCES += zone_data_updater.h zone_data_updater.cc
libdatasrc_memory_la_SOURCES += zone_data_loader.h zone_data_loader.cc
libdatasrc_memory_la_SOURCES += memory_client.h memory_client.cc
-libdatasrc_memory_la_SOURCES += zone_writer.h
-libdatasrc_memory_la_SOURCES += zone_writer_local.h zone_writer_local.cc
+libdatasrc_memory_la_SOURCES += zone_writer.h zone_writer.cc
libdatasrc_memory_la_SOURCES += load_action.h
libdatasrc_memory_la_SOURCES += util_internal.h
diff --git a/src/lib/datasrc/memory/domaintree.h b/src/lib/datasrc/memory/domaintree.h
index 7bd840b..d0a36e7 100644
--- a/src/lib/datasrc/memory/domaintree.h
+++ b/src/lib/datasrc/memory/domaintree.h
@@ -1690,7 +1690,7 @@ DomainTree<T>::previousNode(DomainTreeNodeChain<T>& node_path) const {
}
}
- // Exchange the node at the top of the path, as we move horizontaly
+ // Exchange the node at the top of the path, as we move horizontally
// through the domain tree
node_path.pop();
node_path.push(node);
diff --git a/src/lib/datasrc/memory/memory_client.cc b/src/lib/datasrc/memory/memory_client.cc
index 66e61a2..a6342de 100644
--- a/src/lib/datasrc/memory/memory_client.cc
+++ b/src/lib/datasrc/memory/memory_client.cc
@@ -18,15 +18,11 @@
#include <datasrc/memory/logger.h>
#include <datasrc/memory/zone_data.h>
#include <datasrc/memory/rdataset.h>
-#include <datasrc/memory/segment_object_holder.h>
#include <datasrc/memory/treenode_rrset.h>
#include <datasrc/memory/zone_finder.h>
-#include <datasrc/memory/zone_data_loader.h>
#include <datasrc/memory/zone_table_segment.h>
-#include <util/memory_segment_local.h>
-
-#include <datasrc/data_source.h>
+#include <datasrc/exceptions.h>
#include <datasrc/factory.h>
#include <datasrc/result.h>
@@ -34,12 +30,8 @@
#include <dns/rdataclass.h>
#include <dns/rrclass.h>
-#include <algorithm>
#include <utility>
-#include <cctype>
-#include <cassert>
-using namespace std;
using namespace isc::dns;
using namespace isc::dns::rdata;
using namespace isc::datasrc::memory;
@@ -49,86 +41,14 @@ namespace isc {
namespace datasrc {
namespace memory {
-using detail::SegmentObjectHolder;
using boost::shared_ptr;
-namespace { // unnamed namespace
-
-// A helper internal class used by the memory client, used for deleting
-// filenames stored in an internal tree.
-class FileNameDeleter {
-public:
- FileNameDeleter() {}
-
- void operator()(std::string* filename) const {
- delete filename;
- }
-};
-
-} // end of unnamed namespace
-
InMemoryClient::InMemoryClient(shared_ptr<ZoneTableSegment> ztable_segment,
RRClass rrclass) :
ztable_segment_(ztable_segment),
- rrclass_(rrclass),
- zone_count_(0),
- file_name_tree_(FileNameTree::create(
- ztable_segment_->getMemorySegment(), false))
+ rrclass_(rrclass)
{}
-InMemoryClient::~InMemoryClient() {
- MemorySegment& mem_sgmt = ztable_segment_->getMemorySegment();
- FileNameDeleter deleter;
- FileNameTree::destroy(mem_sgmt, file_name_tree_, deleter);
-}
-
-result::Result
-InMemoryClient::loadInternal(const isc::dns::Name& zone_name,
- const std::string& filename,
- ZoneData* zone_data)
-{
- MemorySegment& mem_sgmt = ztable_segment_->getMemorySegment();
- SegmentObjectHolder<ZoneData, RRClass> holder(
- mem_sgmt, zone_data, rrclass_);
-
- LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_MEMORY_MEM_ADD_ZONE).
- arg(zone_name).arg(rrclass_);
-
- // Set the filename in file_name_tree_ now, so that getFileName()
- // can use it (during zone reloading).
- FileNameNode* node(NULL);
- switch (file_name_tree_->insert(mem_sgmt, zone_name, &node)) {
- case FileNameTree::SUCCESS:
- case FileNameTree::ALREADYEXISTS:
- // These are OK
- break;
- default:
- // Can Not Happen
- assert(false);
- }
- // node must point to a valid node now
- assert(node != NULL);
-
- const std::string* tstr = node->setData(new std::string(filename));
- delete tstr;
-
- ZoneTable* zone_table = ztable_segment_->getHeader().getTable();
- const ZoneTable::AddResult result(zone_table->addZone(mem_sgmt, rrclass_,
- zone_name,
- holder.release()));
- if (result.code == result::SUCCESS) {
- // Only increment the zone count if the zone doesn't already
- // exist.
- ++zone_count_;
- }
- // Destroy the old instance of the zone if there was any
- if (result.zone_data != NULL) {
- ZoneData::destroy(mem_sgmt, result.zone_data, rrclass_);
- }
-
- return (result.code);
-}
-
RRClass
InMemoryClient::getClass() const {
return (rrclass_);
@@ -136,7 +56,8 @@ InMemoryClient::getClass() const {
unsigned int
InMemoryClient::getZoneCount() const {
- return (zone_count_);
+ const ZoneTable* zone_table = ztable_segment_->getHeader().getTable();
+ return (zone_table->getZoneCount());
}
isc::datasrc::DataSourceClient::FindResult
@@ -162,39 +83,6 @@ InMemoryClient::findZoneData(const isc::dns::Name& zone_name) {
return (result.zone_data);
}
-result::Result
-InMemoryClient::load(const isc::dns::Name& zone_name,
- const std::string& filename)
-{
- LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_MEMORY_MEM_LOAD).arg(zone_name).
- arg(filename);
-
- MemorySegment& mem_sgmt = ztable_segment_->getMemorySegment();
- ZoneData* zone_data = loadZoneData(mem_sgmt, rrclass_, zone_name,
- filename);
- return (loadInternal(zone_name, filename, zone_data));
-}
-
-result::Result
-InMemoryClient::load(const isc::dns::Name& zone_name, ZoneIterator& iterator) {
- MemorySegment& mem_sgmt = ztable_segment_->getMemorySegment();
- ZoneData* zone_data = loadZoneData(mem_sgmt, rrclass_, zone_name,
- iterator);
- return (loadInternal(zone_name, string(), zone_data));
-}
-
-const std::string
-InMemoryClient::getFileName(const isc::dns::Name& zone_name) const {
- const FileNameNode* node(NULL);
- const FileNameTree::Result result = file_name_tree_->find(zone_name,
- &node);
- if (result == FileNameTree::EXACTMATCH) {
- return (*node->getData());
- } else {
- return (std::string());
- }
-}
-
namespace {
class MemoryIterator : public ZoneIterator {
@@ -354,7 +242,7 @@ InMemoryClient::getIterator(const Name& name, bool separate_rrs) const {
const ZoneTable* zone_table = ztable_segment_->getHeader().getTable();
const ZoneTable::FindResult result(zone_table->findZone(name));
if (result.code != result::SUCCESS) {
- isc_throw(DataSourceError, "No such zone: " + name.toText());
+ isc_throw(NoSuchZone, "No such zone: " + name.toText());
}
return (ZoneIteratorPtr(new MemoryIterator(
@@ -369,7 +257,7 @@ InMemoryClient::getUpdater(const isc::dns::Name&, bool, bool) const {
isc_throw(isc::NotImplemented, "Update attempt on in memory data source");
}
-pair<ZoneJournalReader::Result, ZoneJournalReaderPtr>
+std::pair<ZoneJournalReader::Result, ZoneJournalReaderPtr>
InMemoryClient::getJournalReader(const isc::dns::Name&, uint32_t,
uint32_t) const
{
diff --git a/src/lib/datasrc/memory/memory_client.h b/src/lib/datasrc/memory/memory_client.h
index 10e8a81..45f0b77 100644
--- a/src/lib/datasrc/memory/memory_client.h
+++ b/src/lib/datasrc/memory/memory_client.h
@@ -55,7 +55,7 @@ class ZoneTableSegment;
class InMemoryClient : public DataSourceClient {
public:
///
- /// \name Constructors and Destructor.
+ /// \name Constructor.
///
//@{
@@ -66,9 +66,6 @@ public:
/// It never throws an exception otherwise.
InMemoryClient(boost::shared_ptr<ZoneTableSegment> ztable_segment,
isc::dns::RRClass rrclass);
-
- /// The destructor.
- ~InMemoryClient();
//@}
/// \brief Returns the class of the data source client.
@@ -81,68 +78,6 @@ public:
/// \return The number of zones stored in the client.
virtual unsigned int getZoneCount() const;
- /// \brief Load zone from masterfile.
- ///
- /// This loads data from masterfile specified by filename. It replaces
- /// current content. The masterfile parsing ability is kind of limited,
- /// see isc::dns::masterLoad.
- ///
- /// This throws isc::dns::MasterLoadError or AddError if there are
- /// problems with loading (missing file, malformed data, unexpected
- /// zone, etc. - see isc::dns::masterLoad for details).
- ///
- /// In case of internal problems, NullRRset or AssertError could
- /// be thrown, but they should not be expected. Exceptions caused by
- /// allocation may be thrown as well.
- ///
- /// If anything is thrown, the previous content is preserved (so it can
- /// be used to update the data, but if user makes a typo, the old one
- /// is kept).
- ///
- /// \param filename The master file to load.
- ///
- /// \todo We may need to split it to some kind of build and commit/abort.
- /// This will probably be needed when a better implementation of
- /// configuration reloading is written.
- result::Result load(const isc::dns::Name& zone_name,
- const std::string& filename);
-
- /// \brief Load zone from another data source.
- ///
- /// This is similar to the other version, but zone's RRsets are provided
- /// by an iterator of another data source. On successful load, the
- /// internal filename will be cleared.
- ///
- /// This implementation assumes the iterator produces combined RRsets,
- /// that is, there should exactly one RRset for the same owner name and
- /// RR type. This means the caller is expected to create the iterator
- /// with \c separate_rrs being \c false. This implementation also assumes
- /// RRsets of different names are not mixed; so if the iterator produces
- /// an RRset of a different name than that of the previous RRset, that
- /// previous name must never appear in the subsequent sequence of RRsets.
- /// Note that the iterator API does not ensure this. If the underlying
- /// implementation does not follow it, load() will fail. Note, however,
- /// that this whole interface is tentative. in-memory zone loading will
- /// have to be revisited fundamentally, and at that point this restriction
- /// probably won't matter.
- result::Result load(const isc::dns::Name& zone_name,
- ZoneIterator& iterator);
-
- /// Return the master file name of the zone
- ///
- /// This method returns the name of the zone's master file to be loaded.
- /// The returned string will be an empty unless the data source client has
- /// successfully loaded the \c zone_name zone from a file before.
- ///
- /// This method should normally not throw an exception. But the creation
- /// of the return string may involve a resource allocation, and if it
- /// fails, the corresponding standard exception will be thrown.
- ///
- /// \return The name of the zone file corresponding to the zone, or
- /// an empty string if the client hasn't loaded the \c zone_name
- /// zone from a file before.
- const std::string getFileName(const isc::dns::Name& zone_name) const;
-
/// Returns a \c ZoneFinder result that best matches the given name.
///
/// This derived version of the method never throws an exception.
@@ -180,20 +115,8 @@ public:
uint32_t end_serial) const;
private:
- // Some type aliases
- typedef DomainTree<std::string> FileNameTree;
- typedef DomainTreeNode<std::string> FileNameNode;
-
- // Common process for zone load. Registers filename internally and
- // adds the ZoneData to the ZoneTable.
- result::Result loadInternal(const isc::dns::Name& zone_name,
- const std::string& filename,
- ZoneData* zone_data);
-
boost::shared_ptr<ZoneTableSegment> ztable_segment_;
const isc::dns::RRClass rrclass_;
- unsigned int zone_count_;
- FileNameTree* file_name_tree_;
};
} // namespace memory
diff --git a/src/lib/datasrc/memory/memory_messages.mes b/src/lib/datasrc/memory/memory_messages.mes
index f8d5328..cf51706 100644
--- a/src/lib/datasrc/memory/memory_messages.mes
+++ b/src/lib/datasrc/memory/memory_messages.mes
@@ -16,6 +16,10 @@ $NAMESPACE isc::datasrc::memory
# \brief Messages for the data source memory library
+% DATASRC_MEMORY_ANY_SUCCESS ANY query for '%1' successful
+Debug information. The domain was found and an ANY type query is being answered
+by providing everything found inside the domain.
+
% DATASRC_MEMORY_BAD_NSEC3_NAME NSEC3 record has a bad owner name '%1'
The software refuses to load NSEC3 records into a wildcard domain or
the owner name has two or more labels below the zone origin.
@@ -23,6 +27,71 @@ It isn't explicitly forbidden, but no sane zone wouldn have such names
for NSEC3. BIND 9 also refuses NSEC3 at wildcard, so this behavior is
compatible with BIND 9.
+% DATASRC_MEMORY_CHECK_ERROR post-load check of zone %1/%2 failed: %3
+The zone was loaded into the data source successfully, but the content fails
+basic sanity checks. See the message if you want to know what exactly is wrong
+with the data. The data can not be used and previous version, if any, will be
+preserved.
+
+% DATASRC_MEMORY_CHECK_WARNING %1/%2: %3
+The zone was loaded into the data source successfully, but there's some problem
+with the content. The problem does not stop the new version from being used
+(though there may be other problems that do, see DATASRC_MEMORY_CHECK_ERROR),
+but it should still be checked and fixed. See the message to know what exactly
+is wrong with the data.
+
+% DATASRC_MEMORY_CNAME CNAME at the domain '%1'
+Debug information. The requested domain is an alias to a different domain,
+returning the CNAME instead.
+
+% DATASRC_MEMORY_DELEG_FOUND delegation found at '%1'
+Debug information. A delegation point was found above the requested record.
+
+% DATASRC_MEMORY_DNAME_ENCOUNTERED encountered a DNAME
+Debug information. While searching for the requested domain, a DNAME was
+encountered on the way. This may lead to redirection to a different domain and
+stop the search.
+
+% DATASRC_MEMORY_DNAME_FOUND DNAME found at '%1'
+Debug information. A DNAME was found instead of the requested information.
+
+% DATASRC_MEMORY_DOMAIN_EMPTY requested domain '%1' is empty
+Debug information. The requested domain exists in the tree of domains, but
+it is empty. Therefore it doesn't contain the requested resource type.
+
+% DATASRC_MEMORY_EXACT_DELEGATION delegation at the exact domain '%1'
+Debug information. There's a NS record at the requested domain. This means
+this zone is not authoritative for the requested domain, but a delegation
+should be followed. The requested domain is an apex of some zone.
+
+% DATASRC_MEMORY_FINDNSEC3 finding NSEC3 for %1, mode %2
+Debug information. A search in an in-memory data source for NSEC3 that
+matches or covers the given name is being started.
+
+% DATASRC_MEMORY_FINDNSEC3_COVER found a covering NSEC3 for %1: %2
+Debug information. An NSEC3 that covers the given name is found and
+being returned. The found NSEC3 RRset is also displayed.
+
+% DATASRC_MEMORY_FINDNSEC3_MATCH found a matching NSEC3 for %1 at label count %2: %3
+Debug information. An NSEC3 that matches (a possibly superdomain of)
+the given name is found and being returned. When the shown label
+count is smaller than that of the given name, the matching NSEC3 is
+for a superdomain of the given name (see DATASRC_MEMORY_FINDNSEC3_TRYHASH).
+The found NSEC3 RRset is also displayed.
+
+% DATASRC_MEMORY_FINDNSEC3_TRYHASH looking for NSEC3 for %1 at label count %2 (hash %3)
+Debug information. In an attempt of finding an NSEC3 for the give name,
+(a possibly superdomain of) the name is hashed and searched for in the
+NSEC3 name space. When the shown label count is smaller than that of the
+shown name, the search tries the superdomain name that share the shown
+(higher) label count of the shown name (e.g., for
+www.example.com. with shown label count of 3, example.com. is being
+tried).
+
+% DATASRC_MEMORY_FIND_TYPE_AT_ORIGIN origin query for type %1 in in-memory zone %2/%3 successful
+Debug information. A specific type RRset is requested at a zone origin
+of an in-memory zone and it is found.
+
% DATASRC_MEMORY_MEM_ADD_RRSET adding RRset '%1/%2' into zone '%3'
Debug information. An RRset is being added to the in-memory data source.
@@ -57,7 +126,11 @@ RRset is split into multiple locations is not supported yet.
Debug information. A zone object for this zone is being searched for in the
in-memory data source.
-% DATASRC_MEMORY_MEM_LOAD loading zone '%1' from file '%2'
+% DATASRC_MEMORY_MEM_LOAD_FROM_DATASRC loading zone '%1/%2' from other data source
+Debug information. The content of another data source is being loaded
+into the memory.
+
+% DATASRC_MEMORY_MEM_LOAD_FROM_FILE loading zone '%1/%2' from file '%3'
Debug information. The content of master file is being loaded into the memory.
% DATASRC_MEMORY_MEM_NO_NSEC3PARAM NSEC3PARAM is missing for NSEC3-signed zone %1/%2
@@ -89,33 +162,15 @@ explicitly forbidden, but the protocol is ambiguous about how this should
behave and BIND 9 refuses that as well. Please describe your intention using
different tools.
-% DATASRC_MEMORY_CHECK_ERROR post-load check of zone %1/%2 failed: %3
-The zone was loaded into the data source successfully, but the content fails
-basic sanity checks. See the message if you want to know what exactly is wrong
-with the data. The data can not be used and previous version, if any, will be
-preserved.
-
-% DATASRC_MEMORY_CHECK_WARNING %1/%2: %3
-The zone was loaded into the data source successfully, but there's some problem
-with the content. The problem does not stop the new version from being used
-(though there may be other problems that do, see DATASRC_MEMORY_CHECK_ERROR),
-but it should still be checked and fixed. See the message to know what exactly
-is wrong with the data.
-
-% DATASRC_MEMORY_DNAME_ENCOUNTERED encountered a DNAME
-Debug information. While searching for the requested domain, a DNAME was
-encountered on the way. This may lead to redirection to a different domain and
-stop the search.
+% DATASRC_MEMORY_NOT_FOUND requested domain '%1' not found
+Debug information. The requested domain does not exist.
% DATASRC_MEMORY_NS_ENCOUNTERED encountered a NS
Debug information. While searching for the requested domain, a NS was
encountered on the way (a delegation). This may lead to stop of the search.
-% DATASRC_MEMORY_DNAME_FOUND DNAME found at '%1'
-Debug information. A DNAME was found instead of the requested information.
-
-% DATASRC_MEMORY_DELEG_FOUND delegation found at '%1'
-Debug information. A delegation point was found above the requested record.
+% DATASRC_MEMORY_SUCCESS query for '%1/%2' successful
+Debug information. The requested record was found.
% DATASRC_MEMORY_SUPER_STOP stopped as '%1' is superdomain of a zone node, meaning it's empty
Debug information. The search stopped because the requested domain was
@@ -128,54 +183,3 @@ doesn't have the requested record type).
Debug information. A domain above wildcard was reached, but there's something
below the requested domain. Therefore the wildcard doesn't apply here. This
behaviour is specified by RFC 1034, section 4.3.3.
-
-% DATASRC_MEMORY_NOT_FOUND requested domain '%1' not found
-Debug information. The requested domain does not exist.
-
-% DATASRC_MEMORY_FIND_TYPE_AT_ORIGIN origin query for type %1 in in-memory zone %2/%3 successful
-Debug information. A specific type RRset is requested at a zone origin
-of an in-memory zone and it is found.
-
-% DATASRC_MEMORY_DOMAIN_EMPTY requested domain '%1' is empty
-Debug information. The requested domain exists in the tree of domains, but
-it is empty. Therefore it doesn't contain the requested resource type.
-
-% DATASRC_MEMORY_EXACT_DELEGATION delegation at the exact domain '%1'
-Debug information. There's a NS record at the requested domain. This means
-this zone is not authoritative for the requested domain, but a delegation
-should be followed. The requested domain is an apex of some zone.
-
-% DATASRC_MEMORY_ANY_SUCCESS ANY query for '%1' successful
-Debug information. The domain was found and an ANY type query is being answered
-by providing everything found inside the domain.
-
-% DATASRC_MEMORY_SUCCESS query for '%1/%2' successful
-Debug information. The requested record was found.
-
-% DATASRC_MEMORY_CNAME CNAME at the domain '%1'
-Debug information. The requested domain is an alias to a different domain,
-returning the CNAME instead.
-
-% DATASRC_MEMORY_FINDNSEC3 finding NSEC3 for %1, mode %2
-Debug information. A search in an in-memory data source for NSEC3 that
-matches or covers the given name is being started.
-
-% DATASRC_MEMORY_FINDNSEC3_COVER found a covering NSEC3 for %1: %2
-Debug information. An NSEC3 that covers the given name is found and
-being returned. The found NSEC3 RRset is also displayed.
-
-% DATASRC_MEMORY_FINDNSEC3_MATCH found a matching NSEC3 for %1 at label count %2: %3
-Debug information. An NSEC3 that matches (a possibly superdomain of)
-the given name is found and being returned. When the shown label
-count is smaller than that of the given name, the matching NSEC3 is
-for a superdomain of the given name (see DATASRC_MEMORY_FINDNSEC3_TRYHASH).
-The found NSEC3 RRset is also displayed.
-
-% DATASRC_MEMORY_FINDNSEC3_TRYHASH looking for NSEC3 for %1 at label count %2 (hash %3)
-Debug information. In an attempt of finding an NSEC3 for the give name,
-(a possibly superdomain of) the name is hashed and searched for in the
-NSEC3 name space. When the shown label count is smaller than that of the
-shown name, the search tries the superdomain name that share the shown
-(higher) label count of the shown name (e.g., for
-www.example.com. with shown label count of 3, example.com. is being
-tried).
diff --git a/src/lib/datasrc/memory/rdata_serialization.h b/src/lib/datasrc/memory/rdata_serialization.h
index a6146a5..3314406 100644
--- a/src/lib/datasrc/memory/rdata_serialization.h
+++ b/src/lib/datasrc/memory/rdata_serialization.h
@@ -364,7 +364,7 @@ struct RdataEncodeSpec;
/// from the field sequence, you'll need to build the complete
/// wire-format data, and then construct a dns::Rdata object from it.
///
-/// To use it, contstruct it with the data you got from RDataEncoder,
+/// To use it, construct it with the data you got from RDataEncoder,
/// provide it with callbacks and then iterate through the data.
/// The callbacks are called with the data fields contained in the
/// data.
diff --git a/src/lib/datasrc/memory/rdataset.cc b/src/lib/datasrc/memory/rdataset.cc
index 3841c03..7f37f51 100644
--- a/src/lib/datasrc/memory/rdataset.cc
+++ b/src/lib/datasrc/memory/rdataset.cc
@@ -28,7 +28,6 @@
#include <stdint.h>
#include <algorithm>
#include <cstring>
-#include <typeinfo> // for bad_cast
#include <new> // for the placement new
using namespace isc::dns;
@@ -41,13 +40,12 @@ namespace memory {
namespace {
RRType
getCoveredType(const Rdata& rdata) {
- try {
- const generic::RRSIG& rrsig_rdata =
- dynamic_cast<const generic::RRSIG&>(rdata);
- return (rrsig_rdata.typeCovered());
- } catch (const std::bad_cast&) {
+ const generic::RRSIG* rrsig_rdata =
+ dynamic_cast<const generic::RRSIG*>(&rdata);
+ if (!rrsig_rdata) {
isc_throw(BadValue, "Non RRSIG is given where it's expected");
}
+ return (rrsig_rdata->typeCovered());
}
// A helper for lowestTTL: restore RRTTL object from wire-format 32-bit data.
diff --git a/src/lib/datasrc/memory/segment_object_holder.h b/src/lib/datasrc/memory/segment_object_holder.h
index e1629d0..923c1e4 100644
--- a/src/lib/datasrc/memory/segment_object_holder.h
+++ b/src/lib/datasrc/memory/segment_object_holder.h
@@ -17,6 +17,7 @@
#include <util/memory_segment.h>
#include <string>
+#include <cassert>
namespace isc {
namespace datasrc {
@@ -41,23 +42,36 @@ getNextHolderName();
template <typename T, typename ARG_T>
class SegmentObjectHolder {
public:
- SegmentObjectHolder(util::MemorySegment& mem_sgmt, T* obj, ARG_T arg) :
+ SegmentObjectHolder(util::MemorySegment& mem_sgmt, ARG_T arg) :
mem_sgmt_(mem_sgmt), arg_(arg),
holder_name_(getNextHolderName()), holding_(true)
{
- mem_sgmt_.setNamedAddress(holder_name_.c_str(), obj);
+ if (mem_sgmt_.setNamedAddress(holder_name_.c_str(), NULL)) {
+ isc_throw(isc::util::MemorySegmentGrown,
+ "Segment grown when allocating holder");
+ }
}
~SegmentObjectHolder() {
if (holding_) {
// Use release, as it removes the stored address from segment
T* obj = release();
- T::destroy(mem_sgmt_, obj, arg_);
+ if (obj) { // May be NULL if set wasn't called
+ T::destroy(mem_sgmt_, obj, arg_);
+ }
}
}
+ void set(T* obj) {
+ const bool grown = mem_sgmt_.setNamedAddress(holder_name_.c_str(),
+ obj);
+ // We reserve the space in the constructor, should not grow now
+ assert(!grown);
+ }
T* get() {
if (holding_) {
- return (static_cast<T*>(
- mem_sgmt_.getNamedAddress(holder_name_.c_str())));
+ const util::MemorySegment::NamedAddressResult result =
+ mem_sgmt_.getNamedAddress(holder_name_.c_str());
+ assert(result.first);
+ return (static_cast<T*>(result.second));
} else {
return (NULL);
}
diff --git a/src/lib/datasrc/memory/zone_data.cc b/src/lib/datasrc/memory/zone_data.cc
index d32fc87..bb626c1 100644
--- a/src/lib/datasrc/memory/zone_data.cc
+++ b/src/lib/datasrc/memory/zone_data.cc
@@ -91,8 +91,8 @@ NSEC3Data::create(util::MemorySegment& mem_sgmt,
// (with an assertion check for that).
typedef boost::function<void(RdataSet*)> RdataSetDeleterType;
detail::SegmentObjectHolder<ZoneTree, RdataSetDeleterType> holder(
- mem_sgmt, ZoneTree::create(mem_sgmt, true),
- boost::bind(nullDeleter, _1));
+ mem_sgmt, boost::bind(nullDeleter, _1));
+ holder.set(ZoneTree::create(mem_sgmt, true));
ZoneTree* tree = holder.get();
const ZoneTree::Result result =
@@ -165,8 +165,8 @@ ZoneData::create(util::MemorySegment& mem_sgmt, const Name& zone_origin) {
// NSEC3Data::create().
typedef boost::function<void(RdataSet*)> RdataSetDeleterType;
detail::SegmentObjectHolder<ZoneTree, RdataSetDeleterType> holder(
- mem_sgmt, ZoneTree::create(mem_sgmt, true),
- boost::bind(nullDeleter, _1));
+ mem_sgmt, boost::bind(nullDeleter, _1));
+ holder.set(ZoneTree::create(mem_sgmt, true));
ZoneTree* tree = holder.get();
ZoneNode* origin_node = NULL;
diff --git a/src/lib/datasrc/memory/zone_data_loader.cc b/src/lib/datasrc/memory/zone_data_loader.cc
index 4dd008e..4d0c6c6 100644
--- a/src/lib/datasrc/memory/zone_data_loader.cc
+++ b/src/lib/datasrc/memory/zone_data_loader.cc
@@ -183,7 +183,8 @@ loadZoneDataInternal(util::MemorySegment& mem_sgmt,
boost::function<void(LoadCallback)> rrset_installer)
{
SegmentObjectHolder<ZoneData, RRClass> holder(
- mem_sgmt, ZoneData::create(mem_sgmt, zone_name), rrclass);
+ mem_sgmt, rrclass);
+ holder.set(ZoneData::create(mem_sgmt, zone_name));
ZoneDataLoader loader(mem_sgmt, rrclass, zone_name, *holder.get());
rrset_installer(boost::bind(&ZoneDataLoader::addFromLoad, &loader, _1));
@@ -253,6 +254,9 @@ loadZoneData(util::MemorySegment& mem_sgmt,
const isc::dns::Name& zone_name,
const std::string& zone_file)
{
+ LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_MEMORY_MEM_LOAD_FROM_FILE).
+ arg(zone_name).arg(rrclass).arg(zone_file);
+
return (loadZoneDataInternal(mem_sgmt, rrclass, zone_name,
boost::bind(masterLoaderWrapper,
zone_file.c_str(),
@@ -266,6 +270,9 @@ loadZoneData(util::MemorySegment& mem_sgmt,
const isc::dns::Name& zone_name,
ZoneIterator& iterator)
{
+ LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_MEMORY_MEM_LOAD_FROM_DATASRC).
+ arg(zone_name).arg(rrclass);
+
return (loadZoneDataInternal(mem_sgmt, rrclass, zone_name,
boost::bind(generateRRsetFromIterator,
&iterator, _1)));
diff --git a/src/lib/datasrc/memory/zone_data_updater.cc b/src/lib/datasrc/memory/zone_data_updater.cc
index 5b4f1e7..a8a88e6 100644
--- a/src/lib/datasrc/memory/zone_data_updater.cc
+++ b/src/lib/datasrc/memory/zone_data_updater.cc
@@ -427,7 +427,7 @@ ZoneDataUpdater::add(const ConstRRsetPtr& rrset,
// the data may have been remapped somewhere else in the process).
zone_data_ =
static_cast<ZoneData*>(
- mem_sgmt_.getNamedAddress("updater_zone_data"));
+ mem_sgmt_.getNamedAddress("updater_zone_data").second);
}
// Retry if it didn't add due to the growth
} while (!added);
diff --git a/src/lib/datasrc/memory/zone_data_updater.h b/src/lib/datasrc/memory/zone_data_updater.h
index c13b8d3..d802234 100644
--- a/src/lib/datasrc/memory/zone_data_updater.h
+++ b/src/lib/datasrc/memory/zone_data_updater.h
@@ -76,7 +76,7 @@ public:
hash_(NULL),
zone_data_(&zone_data)
{
- if (mem_sgmt_.getNamedAddress("updater_zone_data")) {
+ if (mem_sgmt_.getNamedAddress("updater_zone_data").first) {
isc_throw(isc::InvalidOperation, "A ZoneDataUpdater already exists"
" on this memory segment. Destroy it first.");
}
diff --git a/src/lib/datasrc/memory/zone_finder.cc b/src/lib/datasrc/memory/zone_finder.cc
index 4f9183e..3f61b89 100644
--- a/src/lib/datasrc/memory/zone_finder.cc
+++ b/src/lib/datasrc/memory/zone_finder.cc
@@ -18,7 +18,7 @@
#include <datasrc/memory/rdata_serialization.h>
#include <datasrc/zone_finder.h>
-#include <datasrc/data_source.h>
+#include <datasrc/exceptions.h>
#include <dns/labelsequence.h>
#include <dns/name.h>
#include <dns/rrset.h>
diff --git a/src/lib/datasrc/memory/zone_table.cc b/src/lib/datasrc/memory/zone_table.cc
index c0237f5..2ecf14a 100644
--- a/src/lib/datasrc/memory/zone_table.cc
+++ b/src/lib/datasrc/memory/zone_table.cc
@@ -12,14 +12,15 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-#include <util/memory_segment.h>
-
-#include <dns/name.h>
-
-#include <datasrc/memory/zone_data.h>
#include <datasrc/memory/zone_table.h>
+#include <datasrc/memory/zone_data.h>
#include <datasrc/memory/domaintree.h>
#include <datasrc/memory/segment_object_holder.h>
+#include <datasrc/memory/logger.h>
+
+#include <util/memory_segment.h>
+
+#include <dns/name.h>
#include <boost/function.hpp>
#include <boost/bind.hpp>
@@ -49,8 +50,8 @@ typedef boost::function<void(ZoneData*)> ZoneDataDeleterType;
ZoneTable*
ZoneTable::create(util::MemorySegment& mem_sgmt, const RRClass& zone_class) {
SegmentObjectHolder<ZoneTableTree, ZoneDataDeleterType> holder(
- mem_sgmt, ZoneTableTree::create(mem_sgmt),
- boost::bind(deleteZoneData, &mem_sgmt, _1, zone_class));
+ mem_sgmt, boost::bind(deleteZoneData, &mem_sgmt, _1, zone_class));
+ holder.set(ZoneTableTree::create(mem_sgmt));
void* p = mem_sgmt.allocate(sizeof(ZoneTable));
ZoneTable* zone_table = new(p) ZoneTable(zone_class, holder.get());
holder.release();
@@ -70,11 +71,14 @@ ZoneTable::AddResult
ZoneTable::addZone(util::MemorySegment& mem_sgmt, RRClass zone_class,
const Name& zone_name, ZoneData* content)
{
+ LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_MEMORY_MEM_ADD_ZONE).
+ arg(zone_name).arg(rrclass_);
+
if (content == NULL) {
isc_throw(isc::BadValue, "Zone content must not be NULL");
}
- SegmentObjectHolder<ZoneData, RRClass> holder(mem_sgmt, content,
- zone_class);
+ SegmentObjectHolder<ZoneData, RRClass> holder(mem_sgmt, zone_class);
+ holder.set(content);
// Get the node where we put the zone
ZoneTableNode* node(NULL);
switch (zones_->insert(mem_sgmt, zone_name, &node)) {
@@ -94,6 +98,7 @@ ZoneTable::addZone(util::MemorySegment& mem_sgmt, RRClass zone_class,
if (old != NULL) {
return (AddResult(result::EXIST, old));
} else {
+ ++zone_count_;
return (AddResult(result::SUCCESS, NULL));
}
}
diff --git a/src/lib/datasrc/memory/zone_table.h b/src/lib/datasrc/memory/zone_table.h
index b487855..9acbb92 100644
--- a/src/lib/datasrc/memory/zone_table.h
+++ b/src/lib/datasrc/memory/zone_table.h
@@ -104,6 +104,7 @@ private:
/// It never throws an exception otherwise.
ZoneTable(const dns::RRClass& rrclass, ZoneTableTree* zones) :
rrclass_(rrclass),
+ zone_count_(0),
zones_(zones)
{}
@@ -139,6 +140,11 @@ public:
/// is undefined if this condition isn't met).
static void destroy(util::MemorySegment& mem_sgmt, ZoneTable* ztable);
+ /// \brief Return the number of zones contained in the zone table.
+ ///
+ /// \throw None.
+ size_t getZoneCount() const { return (zone_count_); }
+
/// Add a new zone to the \c ZoneTable.
///
/// This method adds a given zone data to the internal table.
@@ -189,6 +195,7 @@ public:
private:
const dns::RRClass rrclass_;
+ size_t zone_count_;
boost::interprocess::offset_ptr<ZoneTableTree> zones_;
};
}
diff --git a/src/lib/datasrc/memory/zone_table_segment.cc b/src/lib/datasrc/memory/zone_table_segment.cc
index 50587c4..2e1a1dc 100644
--- a/src/lib/datasrc/memory/zone_table_segment.cc
+++ b/src/lib/datasrc/memory/zone_table_segment.cc
@@ -12,8 +12,16 @@
// 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_table_segment.h>
#include <datasrc/memory/zone_table_segment_local.h>
+#ifdef USE_SHARED_MEMORY
+#include <datasrc/memory/zone_table_segment_mapped.h>
+#endif
+#include <datasrc/memory/zone_writer.h>
+
+#include <string>
using namespace isc::dns;
@@ -22,13 +30,19 @@ namespace datasrc {
namespace memory {
ZoneTableSegment*
-ZoneTableSegment::create(const isc::data::Element&, const RRClass& rrclass) {
- /// FIXME: For now, we always return ZoneTableSegmentLocal. This
- /// should be updated eventually to parse the passed Element
- /// argument and construct a corresponding ZoneTableSegment
- /// implementation.
-
- return (new ZoneTableSegmentLocal(rrclass));
+ZoneTableSegment::create(const RRClass& rrclass, const std::string& type) {
+ // This will be a few sequences of if-else and hardcoded. Not really
+ // sophisticated, but we don't expect to have too many types at the moment.
+ // Until that it becomes a real issue we won't be too smart.
+ if (type == "local") {
+ return (new ZoneTableSegmentLocal(rrclass));
+#ifdef USE_SHARED_MEMORY
+ } else if (type == "mapped") {
+ return (new ZoneTableSegmentMapped(rrclass));
+#endif
+ }
+ isc_throw(UnknownSegmentType, "Zone table segment type not supported: "
+ << type);
}
void
diff --git a/src/lib/datasrc/memory/zone_table_segment.h b/src/lib/datasrc/memory/zone_table_segment.h
index 88e69f6..4761c46 100644
--- a/src/lib/datasrc/memory/zone_table_segment.h
+++ b/src/lib/datasrc/memory/zone_table_segment.h
@@ -15,15 +15,20 @@
#ifndef ZONE_TABLE_SEGMENT_H
#define ZONE_TABLE_SEGMENT_H
+#include <exceptions/exceptions.h>
+
#include <dns/rrclass.h>
+
#include <datasrc/memory/zone_table.h>
-#include "load_action.h"
+#include <datasrc/memory/load_action.h>
+
#include <cc/data.h>
#include <util/memory_segment.h>
#include <boost/interprocess/offset_ptr.hpp>
-#include <stdlib.h>
+#include <cstdlib>
+#include <string>
namespace isc {
// Some forward declarations
@@ -35,12 +40,45 @@ namespace datasrc {
namespace memory {
class ZoneWriter;
+/// \brief Exception thrown when unknown or unsupported type of
+/// ZoneTableSegment is asked to be created.
+class UnknownSegmentType : public Exception {
+public:
+ UnknownSegmentType(const char* file, size_t line, const char* what) :
+ Exception(file, line, what)
+ {}
+};
+
+/// \brief Exception thrown when a \c reset() on a \c ZoneTableSegment
+/// fails (due to various reasons). When this exception is thrown, a
+/// strong exception safety guarantee is provided, and the
+/// \c ZoneTableSegment is usable as before.
+class ResetFailed : public isc::Exception {
+public:
+ ResetFailed(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what)
+ {}
+};
+
+/// \brief Exception thrown when a \c reset() on a \c ZoneTableSegment
+/// fails (due to various reasons), and it was not able to preserve the
+/// state of the \c ZoneTableSegment. When this exception is thrown,
+/// only basic exception safety guarantee is provided and the
+/// \c ZoneTableSegment must be expected as cleared.
+class ResetFailedAndSegmentCleared : public isc::Exception {
+public:
+ ResetFailedAndSegmentCleared(const char* file, size_t line,
+ const char* what) :
+ isc::Exception(file, line, what)
+ {}
+};
+
/// \brief Memory-management independent entry point that contains a
/// pointer to a zone table in memory.
///
-/// An instance of this type lives inside a ZoneTableSegment
-/// implementation. It contains an offset pointer to the zone table (a
-/// map from domain names to zone locators) in memory.
+/// An instance of this type lives inside a \c ZoneTableSegment
+/// implementation. It contains an offset pointer to the \c ZoneTable (a
+/// map from domain names to zone locators) in the \c ZoneTableSegment.
struct ZoneTableHeader {
public:
ZoneTableHeader(ZoneTable* zone_table) :
@@ -60,14 +98,20 @@ private:
boost::interprocess::offset_ptr<ZoneTable> table_;
};
-/// \brief Manages a ZoneTableHeader, an entry point into a table of
+/// \brief Manages a \c ZoneTableHeader, an entry point into a table of
/// zones
///
/// This class specifies an interface for derived implementations which
-/// return a pointer to an object of type ZoneTableHeader, an entry
+/// return a pointer to an object of type \c ZoneTableHeader, an entry
/// point into a table of zones regardless of the underlying memory
-/// management implementation. Derived classes would implement the
-/// interface for specific memory-implementation behavior.
+/// management implementation. Derived classes implement the interface
+/// for the specific memory-implementation behavior.
+///
+/// Note: At some point in the future, methods such as \c reset(),
+/// \c clear(), \c resetHeader(), \c getHeader(), \c isWritable(),
+/// \c isUsable() may become non-virtual methods. Such a change should
+/// not affect any code that uses this class, but please be aware of
+/// such plans.
class ZoneTableSegment {
protected:
/// \brief Protected constructor
@@ -75,71 +119,227 @@ protected:
/// An instance implementing this interface is expected to be
/// created by the factory method (\c create()), so this constructor
/// is protected.
- ZoneTableSegment(isc::dns::RRClass)
+ ZoneTableSegment(const isc::dns::RRClass&)
{}
public:
/// \brief Destructor
virtual ~ZoneTableSegment() {}
- /// \brief Return the ZoneTableHeader for the zone table segment.
+ /// \brief Return a string name for the \c ZoneTableSegment
+ /// implementation.
+ ///
+ /// \throw None This method's implementations must be
+ /// exception-free.
+ virtual const std::string& getImplType() const = 0;
+
+ /// \brief Return the \c ZoneTableHeader for the zone table segment.
+ ///
+ /// As long as \c isUsable() returns true, this method must always
+ /// succeed without throwing an exception. If \c isUsable() returns
+ /// false, a derived class implementation can throw
+ /// \c isc::InvalidOperation depending on its implementation
+ /// details. Applications are generally expected to call this
+ /// method only when \c isUsable() returns true (either by making
+ /// sure explicitly or by some other indirect means).
+ ///
+ /// \throw isc::InvalidOperation may be thrown by some
+ /// implementations if this method is called without calling
+ /// \c reset() successfully first.
virtual ZoneTableHeader& getHeader() = 0;
- /// \brief const version of \c getHeader().
+ /// \brief \c const version of \c getHeader().
+ ///
+ /// See the non- \c const version for documentation.
virtual const ZoneTableHeader& getHeader() const = 0;
/// \brief Return the MemorySegment for the zone table segment.
+ ///
+ /// \throw isc::InvalidOperation may be thrown by some
+ /// implementations if this method is called without calling
+ /// \c reset() successfully first.
virtual isc::util::MemorySegment& getMemorySegment() = 0;
- /// \brief Create an instance depending on the memory segment model
+ /// \brief Return true if the segment is writable.
+ ///
+ /// The user of the zone table segment will load or update zones
+ /// into the segment only for writable ones. The precise definition
+ /// of "writability" differs in different derived classes (see
+ /// derived class documentation). In general, however, the user
+ /// should only rely on this interface rather than assume a specific
+ /// definition for a specific type of segment.
+ ///
+ /// \throw None This method's implementations must be
+ /// exception-free.
+ virtual bool isWritable() const = 0;
+
+ /// \brief Create an instance depending on the requested memory
+ /// segment implementation type.
///
- /// This is a factory method to create a derived ZoneTableSegment
+ /// This is a factory method to create a derived \c ZoneTableSegment
/// object based on the \c config passed. The method returns a
/// dynamically-allocated object. The caller is responsible for
/// destroying it with \c ZoneTableSegment::destroy().
///
- /// FIXME: For now, we always return ZoneTableSegmentLocal
- /// regardless of the passed \c config.
+ /// \throw UnknownSegmentType The memory segment type specified in
+ /// \c config is not known or not supported in this implementation.
+ ///
+ /// \param rrclass The RR class of the zones to be maintained in the table.
+ /// \param type The memory segment type to be used.
+ /// \return Returns a \c ZoneTableSegment object of the specified type.
+ static ZoneTableSegment* create(const isc::dns::RRClass& rrclass,
+ const std::string& type);
+
+ /// \brief Destroy a \c ZoneTableSegment
+ ///
+ /// This method destroys the passed \c ZoneTableSegment. It must be
+ /// passed a segment previously created by
+ /// \c ZoneTableSegment::create().
///
- /// \param config The configuration based on which a derived object
- /// is returned.
- /// \return Returns a ZoneTableSegment object
- static ZoneTableSegment* create(const isc::data::Element& config,
- const isc::dns::RRClass& rrclass);
+ /// \param segment The segment to destroy.
+ static void destroy(ZoneTableSegment* segment);
- /// \brief Temporary/Testing version of create.
+ /// \brief The mode using which to create a MemorySegment.
///
- /// This exists as a temporary solution during the migration phase
- /// towards using the ZoneTableSegment. It doesn't take a config,
- /// but a memory segment instead. If you can, you should use the
- /// other version, this one will be gone soon.
+ /// Here, a \c MemorySegment (see its class documentation) is an
+ /// interface to a storage area, and provides operations to allocate
+ /// and deallocate from that storage area, and also to look up
+ /// addresses in that area. The storage area can be a buffer in
+ /// memory, a file on disk, or some kind of shared memory depending
+ /// on the \c MemorySegment implementation being used. In every
+ /// case in the documentation below, when we mention \c
+ /// MemorySegment, we mean both the \c MemorySegment object which
+ /// interfaces to the storage area and the contents of the
+ /// associated storage area.
///
- /// \param segment The memory segment to use.
- /// \return Returns a new ZoneTableSegment object.
- /// \todo Remove this method.
- static ZoneTableSegment* create(isc::util::MemorySegment& segment);
+ /// - CREATE: If the \c MemorySegment's storage area doesn't exist,
+ /// create it. If it exists, overwrite it with a new
+ /// storage area (which does not remember old data). In
+ /// both cases, create a \c MemorySegment for it in
+ /// read+write mode.
+ ///
+ /// - READ_WRITE: If the \c MemorySegment's storage area doesn't
+ /// exist, create it. If it exists, use the existing
+ /// storage area as-is (keeping the existing data
+ /// intact). In both cases, create a \c MemorySegment
+ /// for it in read+write mode.
+ ///
+ /// - READ_ONLY: If the \c MemorySegment's storage area doesn't
+ /// exist, throw an exception. If it exists, create a
+ /// \c MemorySegment for it in read-only mode.
+ enum MemorySegmentOpenMode {
+ CREATE,
+ READ_WRITE,
+ READ_ONLY
+ };
- /// \brief Destroy a ZoneTableSegment
+ /// \brief Close the current \c MemorySegment (if open) and open the
+ /// requested one.
///
- /// This method destroys the passed ZoneTableSegment. It must be
- /// passed a segment previously created by \c ZoneTableSegment::create().
+ /// When we talk about "opening" a \c MemorySegment, it means to
+ /// construct a usable \c MemorySegment object that interfaces to
+ /// the actual memory storage area. "Closing" is the opposite
+ /// operation of opening.
///
- /// \param segment The segment to destroy.
- static void destroy(ZoneTableSegment* segment);
+ /// In case opening the new \c MemorySegment fails for some reason,
+ /// one of the following documented (further below) exceptions may
+ /// be thrown. In case failures occur, implementations of this
+ /// method must strictly provide the associated behavior as follows
+ /// and in the exception documentation below. Code that uses
+ /// \c ZoneTableSegment would depend on such assurances.
+ ///
+ /// First, in case a \c ZoneTableSegment was reset successfully
+ /// before and is currently usable (\c isUsable() returns true), and
+ /// an invalid configuration is passed in \c params to \c reset(),
+ /// the isc::InvalidParameter exception must be thrown. In this
+ /// case, a strong exception safety guarantee must be provided, and
+ /// the \c ZoneTableSegment must be usable as before.
+ ///
+ /// In case a \c ZoneTableSegment was reset successfully before and
+ /// is currently usable (\c isUsable() returns true), and the attempt
+ /// to reset to a different \c MemorySegment storage area fails,
+ /// the \c ResetFailed exception must be thrown. In this
+ /// case, a strong exception safety guarantee must be provided, and
+ /// the \c ZoneTableSegment must be usable as before.
+ ///
+ /// In case a \c ZoneTableSegment was reset successfully before and
+ /// is currently usable (\c isUsable() returns true), and the attempt
+ /// to reset to the same \c MemorySegment storage area fails, the
+ /// \c ResetFailedAndSegmentCleared exception must be thrown. In
+ /// this case, only basic exception safety guarantee is provided and
+ /// the \c ZoneTableSegment must be expected as cleared.
+ ///
+ /// In case a \c ZoneTableSegment was not reset successfully before
+ /// and is currently not usable (\c isUsable() returns false), and
+ /// the attempt to reset fails, the \c ResetFailed exception must be
+ /// thrown. In this unique case, a strong exception safety guarantee
+ /// is provided by default, as the \c ZoneTableSegment was clear
+ /// previously, and remains cleared.
+ ///
+ /// In all other cases, \c ZoneTableSegment contents can be expected
+ /// as reset.
+ ///
+ /// See \c MemorySegmentOpenMode for a definition of "storage area"
+ /// and the various modes in which a \c MemorySegment can be opened.
+ ///
+ /// \c params should contain an implementation-defined
+ /// configuration. See the specific \c ZoneTableSegment
+ /// implementation class for details of what to pass in this
+ /// argument.
+ ///
+ /// \throw isc::InvalidParameter if the configuration in \c params
+ /// has incorrect syntax, but there is a strong exception safety
+ /// guarantee and the \c ZoneTableSegment is usable or unusable as
+ /// before.
+ ///
+ /// \throw ResetFailed if there was a problem in opening the new
+ /// memory store, but there is a strong exception safety guarantee
+ /// and the \c ZoneTableSegment is usable or unusable as before.
+ ///
+ /// \throw ResetFailedAndSegmentCleared if there was a problem in
+ /// opening the new memory store, but there is only a basic
+ /// exception safety guarantee and the \c ZoneTableSegment is not
+ /// usable without a further successful \c reset().
+ ///
+ /// \throw isc::NotImplemented Some implementations may choose to
+ /// not implement this method. In this case, there must be a strong
+ /// exception safety guarantee and the \c ZoneTableSegment is usable
+ /// or unusable as before.
+ ///
+ /// \param mode The open mode (see the MemorySegmentOpenMode
+ /// documentation).
+ /// \param params An element containing implementation-specific
+ /// config (see the description).
+ virtual void reset(MemorySegmentOpenMode mode,
+ isc::data::ConstElementPtr params) = 0;
- /// \brief Create a zone write corresponding to this segment
+ /// \brief Close the currently configured \c MemorySegment (if
+ /// open).
///
- /// This creates a new write that can be used to update zones
- /// inside this zone table segment.
+ /// See the \c reset() method's documentation for a definition of
+ /// "open" and "close".
///
- /// \param loadAction Callback to provide the actual data.
- /// \param origin The origin of the zone to reload.
- /// \param rrclass The class of the zone to reload.
- /// \return New instance of a zone writer. The ownership is passed
- /// onto the caller and the caller needs to \c delete it when
- /// it's done with the writer.
- virtual ZoneWriter* getZoneWriter(const LoadAction& load_action,
- const dns::Name& origin,
- const dns::RRClass& rrclass) = 0;
+ /// Implementations of this method should close any currently
+ /// configured \c MemorySegment and clear the `ZoneTableSegment` to
+ /// a freshly constructed state.
+ ///
+ /// \throw isc::NotImplemented Some implementations may choose to
+ /// not implement this method. In this case, there must be a strong
+ /// exception safety guarantee and the \c ZoneTableSegment is usable
+ /// or unusable as before.
+ virtual void clear() = 0;
+
+ /// \brief Return true if the \c ZoneTableSegment has been
+ /// successfully \c reset().
+ ///
+ /// Note that after calling \c clear(), this method will return
+ /// false until the segment is reset successfully again.
+ virtual bool isUsable() const = 0;
+
+ /// \brief Reset the table header address.
+ ///
+ /// This method must recalculate the \c ZoneTableHeader address, so
+ /// that it is valid when requested using the \c getHeader() method.
+ virtual void resetHeader() = 0;
};
} // namespace memory
@@ -147,3 +347,7 @@ public:
} // namespace isc
#endif // ZONE_TABLE_SEGMENT_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/datasrc/memory/zone_table_segment_local.cc b/src/lib/datasrc/memory/zone_table_segment_local.cc
index fdaf678..6a3247b 100644
--- a/src/lib/datasrc/memory/zone_table_segment_local.cc
+++ b/src/lib/datasrc/memory/zone_table_segment_local.cc
@@ -13,7 +13,6 @@
// PERFORMANCE OF THIS SOFTWARE.
#include <datasrc/memory/zone_table_segment_local.h>
-#include "zone_writer_local.h"
using namespace isc::dns;
using namespace isc::util;
@@ -24,6 +23,7 @@ namespace memory {
ZoneTableSegmentLocal::ZoneTableSegmentLocal(const RRClass& rrclass) :
ZoneTableSegment(rrclass),
+ impl_type_("local"),
header_(ZoneTable::create(mem_sgmt_, rrclass))
{
}
@@ -38,6 +38,33 @@ ZoneTableSegmentLocal::~ZoneTableSegmentLocal() {
assert(mem_sgmt_.allMemoryDeallocated());
}
+const std::string&
+ZoneTableSegmentLocal::getImplType() const {
+ return (impl_type_);
+}
+
+void
+ZoneTableSegmentLocal::reset(MemorySegmentOpenMode,
+ isc::data::ConstElementPtr)
+{
+ isc_throw(isc::NotImplemented,
+ "ZoneTableSegmentLocal::reset() is not implemented and "
+ "should not be used.");
+}
+
+void
+ZoneTableSegmentLocal::clear()
+{
+ isc_throw(isc::NotImplemented,
+ "ZoneTableSegmentLocal::clear() is not implemented and "
+ "should not be used.");
+}
+
+void
+ZoneTableSegmentLocal::resetHeader() {
+ // This method does not have to do anything in this implementation.
+}
+
// After more methods' definitions are added here, it would be a good
// idea to move getHeader() and getMemorySegment() definitions to the
// header file.
@@ -56,14 +83,6 @@ ZoneTableSegmentLocal::getMemorySegment() {
return (mem_sgmt_);
}
-ZoneWriter*
-ZoneTableSegmentLocal::getZoneWriter(const LoadAction& load_action,
- const dns::Name& name,
- const dns::RRClass& rrclass)
-{
- return (new ZoneWriterLocal(this, load_action, name, rrclass));
-}
-
} // namespace memory
} // namespace datasrc
} // namespace isc
diff --git a/src/lib/datasrc/memory/zone_table_segment_local.h b/src/lib/datasrc/memory/zone_table_segment_local.h
index e08ca39..d3b61f6 100644
--- a/src/lib/datasrc/memory/zone_table_segment_local.h
+++ b/src/lib/datasrc/memory/zone_table_segment_local.h
@@ -18,19 +18,22 @@
#include <datasrc/memory/zone_table_segment.h>
#include <util/memory_segment_local.h>
+#include <string>
+
namespace isc {
namespace datasrc {
namespace memory {
-/// \brief Local implementation of ZoneTableSegment class
+/// \brief Local implementation of \c ZoneTableSegment class
///
/// This class specifies a concrete implementation for a
-/// MemorySegmentLocal based ZoneTableSegment. Please see the
-/// ZoneTableSegment class documentation for usage.
+/// \c MemorySegmentLocal -based \c ZoneTableSegment. Please see the
+/// \c ZoneTableSegment class documentation for usage.
class ZoneTableSegmentLocal : public ZoneTableSegment {
- // This is so that ZoneTableSegmentLocal can be instantiated from
- // ZoneTableSegment::create().
+ // This is so that \c ZoneTableSegmentLocal can be instantiated from
+ // \c ZoneTableSegment::create().
friend class ZoneTableSegment;
+
protected:
/// \brief Protected constructor
///
@@ -38,26 +41,64 @@ protected:
/// (\c ZoneTableSegment::create()), so this constructor is
/// protected.
ZoneTableSegmentLocal(const isc::dns::RRClass& rrclass);
+
public:
/// \brief Destructor
virtual ~ZoneTableSegmentLocal();
- /// \brief Return the ZoneTableHeader for the local zone table
- /// segment implementation.
+ /// \brief Returns "local" as the implementation type.
+ virtual const std::string& getImplType() const;
+
+ /// \brief This method does not have to do anything in this
+ /// implementation. It has an empty definition.
+ virtual void resetHeader();
+
+ /// \brief Return the \c ZoneTableHeader for this local zone table
+ /// segment.
virtual ZoneTableHeader& getHeader();
- /// \brief const version of \c getHeader().
+ /// \brief \c const version of \c getHeader().
virtual const ZoneTableHeader& getHeader() const;
- /// \brief Return the MemorySegment for the local zone table segment
- /// implementation (a MemorySegmentLocal instance).
+ /// \brief Return the \c MemorySegment for the local zone table
+ /// segment implementation (a \c MemorySegmentLocal instance).
virtual isc::util::MemorySegment& getMemorySegment();
- /// \brief Concrete implementation of ZoneTableSegment::getZoneWriter
- virtual ZoneWriter* getZoneWriter(const LoadAction& load_action,
- const dns::Name& origin,
- const dns::RRClass& rrclass);
+ /// \brief Return true if the segment is writable.
+ ///
+ /// Local segments are always writable. This implementation always
+ /// returns true.
+ virtual bool isWritable() const {
+ return (true);
+ }
+
+ /// \brief This method is not implemented.
+ ///
+ /// Resetting a local \c ZoneTableSegment is not supported at this
+ /// time.
+ ///
+ /// \throw isc::NotImplemented
+ virtual void reset(MemorySegmentOpenMode mode,
+ isc::data::ConstElementPtr params);
+
+ /// \brief This method is not implemented.
+ ///
+ /// Clearing a local \c ZoneTableSegment is not supported at this
+ /// time.
+ ///
+ /// \throw isc::NotImplemented
+ virtual void clear();
+
+ /// \brief Return true if the segment is usable.
+ ///
+ /// Local segments are always usable. This implementation always
+ /// returns true.
+ virtual bool isUsable() const {
+ return (true);
+ }
+
private:
+ std::string impl_type_;
isc::util::MemorySegmentLocal mem_sgmt_;
ZoneTableHeader header_;
};
diff --git a/src/lib/datasrc/memory/zone_table_segment_mapped.cc b/src/lib/datasrc/memory/zone_table_segment_mapped.cc
new file mode 100644
index 0000000..45bd880
--- /dev/null
+++ b/src/lib/datasrc/memory/zone_table_segment_mapped.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 <datasrc/memory/zone_table_segment_mapped.h>
+
+#include <memory>
+
+using namespace isc::data;
+using namespace isc::dns;
+using namespace isc::util;
+
+namespace isc {
+namespace datasrc {
+namespace memory {
+
+namespace { // unnamed namespace
+
+// The name with which the zone table checksum is associated in the segment.
+const char* const ZONE_TABLE_CHECKSUM_NAME = "zone_table_checksum";
+
+// The name with which the zone table header is associated in the segment.
+const char* const ZONE_TABLE_HEADER_NAME = "zone_table_header";
+
+} // end of unnamed namespace
+
+ZoneTableSegmentMapped::ZoneTableSegmentMapped(const RRClass& rrclass) :
+ ZoneTableSegment(rrclass),
+ impl_type_("mapped"),
+ rrclass_(rrclass),
+ cached_header_(NULL)
+{
+}
+
+ZoneTableSegmentMapped::~ZoneTableSegmentMapped() {
+ sync();
+}
+
+const std::string&
+ZoneTableSegmentMapped::getImplType() const {
+ return (impl_type_);
+}
+
+bool
+ZoneTableSegmentMapped::processChecksum(MemorySegmentMapped& segment,
+ bool create,
+ std::string& error_msg)
+{
+ const MemorySegment::NamedAddressResult result =
+ segment.getNamedAddress(ZONE_TABLE_CHECKSUM_NAME);
+ if (result.first) {
+ if (create) {
+ // There must be no previously saved checksum.
+ error_msg = "There is already a saved checksum in the segment "
+ "opened in create mode";
+ return (false);
+ } else {
+ // The segment was already shrunk when it was last
+ // closed. Check that its checksum is consistent.
+ assert(result.second);
+ size_t* checksum = static_cast<size_t*>(result.second);
+ const size_t saved_checksum = *checksum;
+ // First, clear the checksum so that getCheckSum() returns a
+ // consistent value.
+ *checksum = 0;
+ const size_t new_checksum = segment.getCheckSum();
+ if (saved_checksum != new_checksum) {
+ error_msg = "Saved checksum doesn't match segment data";
+ return (false);
+ }
+ }
+ } else {
+ // Allocate space for a checksum (which is saved during close).
+ void* checksum = NULL;
+ while (!checksum) {
+ try {
+ checksum = segment.allocate(sizeof(size_t));
+ } catch (const MemorySegmentGrown&) {
+ // Do nothing and try again.
+ }
+ }
+ *static_cast<size_t*>(checksum) = 0;
+ const bool grew = segment.setNamedAddress(ZONE_TABLE_CHECKSUM_NAME,
+ checksum);
+ assert(!grew);
+ }
+
+ return (true);
+}
+
+bool
+ZoneTableSegmentMapped::processHeader(MemorySegmentMapped& segment,
+ bool create,
+ std::string& error_msg)
+{
+ const MemorySegment::NamedAddressResult result =
+ segment.getNamedAddress(ZONE_TABLE_HEADER_NAME);
+ if (result.first) {
+ if (create) {
+ // There must be no previously saved checksum.
+ error_msg = "There is already a saved ZoneTableHeader in the "
+ "segment opened in create mode";
+ return (false);
+ } else {
+ assert(result.second);
+ }
+ } else {
+ void* ptr = NULL;
+ while (!ptr) {
+ try {
+ ptr = segment.allocate(sizeof(ZoneTableHeader));
+ } catch (const MemorySegmentGrown&) {
+ // Do nothing and try again.
+ }
+ }
+ try {
+ ZoneTableHeader* new_header = new(ptr)
+ ZoneTableHeader(ZoneTable::create(segment, rrclass_));
+ const bool grew = segment.setNamedAddress(ZONE_TABLE_HEADER_NAME,
+ new_header);
+ assert(!grew);
+ } catch (const MemorySegmentGrown&) {
+ // This is extremely unlikely and we just throw a fatal
+ // exception here without attempting to recover.
+
+ throw std::bad_alloc();
+ }
+ }
+
+ return (true);
+}
+
+MemorySegmentMapped*
+ZoneTableSegmentMapped::openReadWrite(const std::string& filename,
+ bool create)
+{
+ const MemorySegmentMapped::OpenMode mode = create ?
+ MemorySegmentMapped::CREATE_ONLY :
+ MemorySegmentMapped::OPEN_OR_CREATE;
+ // In case there is a problem, we throw. We want the segment to be
+ // automatically destroyed then.
+ std::auto_ptr<MemorySegmentMapped> segment
+ (new MemorySegmentMapped(filename, mode));
+
+ std::string error_msg;
+ if ((!processChecksum(*segment, create, error_msg)) ||
+ (!processHeader(*segment, create, error_msg))) {
+ if (mem_sgmt_) {
+ isc_throw(ResetFailed,
+ "Error in resetting zone table segment to use "
+ << filename << ": " << error_msg);
+ } else {
+ isc_throw(ResetFailedAndSegmentCleared,
+ "Error in resetting zone table segment to use "
+ << filename << ": " << error_msg);
+ }
+ }
+
+ return (segment.release());
+}
+
+MemorySegmentMapped*
+ZoneTableSegmentMapped::openReadOnly(const std::string& filename) {
+ // In case the checksum or table header is missing, we throw. We
+ // want the segment to be automatically destroyed then.
+ std::auto_ptr<MemorySegmentMapped> segment
+ (new MemorySegmentMapped(filename));
+ // There must be a previously saved checksum.
+ MemorySegment::NamedAddressResult result =
+ segment->getNamedAddress(ZONE_TABLE_CHECKSUM_NAME);
+ if (!result.first) {
+ const std::string error_msg =
+ "There is no previously saved checksum in a "
+ "mapped segment opened in read-only mode";
+ if (mem_sgmt_) {
+ isc_throw(ResetFailed,
+ "Error in resetting zone table segment to use "
+ << filename << ": " << error_msg);
+ } else {
+ isc_throw(ResetFailedAndSegmentCleared,
+ "Error in resetting zone table segment to use "
+ << filename << ": " << error_msg);
+ }
+ }
+
+ // We can't verify the checksum here as we can't set the checksum to
+ // 0 for checksum calculation in a read-only segment. So we continue
+ // without verifying the checksum.
+
+ // There must be a previously saved ZoneTableHeader.
+ result = segment->getNamedAddress(ZONE_TABLE_HEADER_NAME);
+ if (result.first) {
+ assert(result.second);
+ } else {
+ const std::string error_msg =
+ "There is no previously saved ZoneTableHeader in a "
+ "mapped segment opened in read-only mode.";
+ if (mem_sgmt_) {
+ isc_throw(ResetFailed,
+ "Error in resetting zone table segment to use "
+ << filename << ": " << error_msg);
+ } else {
+ isc_throw(ResetFailedAndSegmentCleared,
+ "Error in resetting zone table segment to use "
+ << filename << ": " << error_msg);
+ }
+ }
+
+ return (segment.release());
+}
+
+void
+ZoneTableSegmentMapped::reset(MemorySegmentOpenMode mode,
+ isc::data::ConstElementPtr params)
+{
+ if (!params || params->getType() != Element::map) {
+ isc_throw(isc::InvalidParameter,
+ "Configuration does not contain a map");
+ }
+
+ if (!params->contains("mapped-file")) {
+ isc_throw(isc::InvalidParameter,
+ "Configuration does not contain a \"mapped-file\" key");
+ }
+
+ ConstElementPtr mapped_file = params->get("mapped-file");
+ if ((!mapped_file) || (mapped_file->getType() != Element::string)) {
+ isc_throw(isc::InvalidParameter,
+ "Value of \"mapped-file\" is not a string");
+ }
+
+ const std::string filename = mapped_file->stringValue();
+
+ if (mem_sgmt_ && (filename == current_filename_)) {
+ // This reset() is an attempt to re-open the currently open
+ // mapped file. We cannot do this in many mode combinations
+ // unless we close the existing mapped file. So just close it.
+ clear();
+ } else {
+ sync();
+ }
+
+ // In case current_filename_ below fails, we want the segment to be
+ // automatically destroyed.
+ std::auto_ptr<MemorySegmentMapped> segment;
+
+ switch (mode) {
+ case CREATE:
+ segment.reset(openReadWrite(filename, true));
+ break;
+
+ case READ_WRITE:
+ segment.reset(openReadWrite(filename, false));
+ break;
+
+ case READ_ONLY:
+ segment.reset(openReadOnly(filename));
+ break;
+
+ default:
+ isc_throw(isc::InvalidOperation,
+ "Invalid MemorySegmentOpenMode passed to reset()");
+ }
+
+ current_filename_ = filename;
+ current_mode_ = mode;
+ mem_sgmt_.reset(segment.release());
+
+ // Given what we setup above, resetHeader() must not throw at this
+ // point. If it does, all bets are off.
+ resetHeader();
+}
+
+void
+ZoneTableSegmentMapped::sync() {
+ // Synchronize checksum, etc.
+ if (mem_sgmt_ && isWritable()) {
+ // If there is a previously opened segment, and it was opened in
+ // read-write mode, update its checksum.
+ mem_sgmt_->shrinkToFit();
+ const MemorySegment::NamedAddressResult result =
+ mem_sgmt_->getNamedAddress(ZONE_TABLE_CHECKSUM_NAME);
+ assert(result.first);
+ assert(result.second);
+ size_t* checksum = static_cast<size_t*>(result.second);
+ // First, clear the checksum so that getCheckSum() returns a
+ // consistent value.
+ *checksum = 0;
+ const size_t new_checksum = mem_sgmt_->getCheckSum();
+ // Now, update it into place.
+ *checksum = new_checksum;
+ }
+}
+
+void
+ZoneTableSegmentMapped::clear() {
+ if (mem_sgmt_) {
+ sync();
+ mem_sgmt_.reset();
+ }
+}
+
+void
+ZoneTableSegmentMapped::resetHeader() {
+ // We cannot perform resetHeader() lazily during getHeader() as
+ // getHeader() has to work on const objects too. So we do it here
+ // now.
+
+ if (!isUsable()) {
+ isc_throw(isc::InvalidOperation,
+ "resetHeader() called without calling reset() first");
+ }
+
+ const MemorySegment::NamedAddressResult result =
+ mem_sgmt_->getNamedAddress(ZONE_TABLE_HEADER_NAME);
+ if (!result.first) {
+ isc_throw(isc::Unexpected,
+ "Unable to look up the address of the table header in "
+ "getHeader()");
+ }
+
+ cached_header_ = static_cast<ZoneTableHeader*>(result.second);
+}
+
+template<typename T>
+T*
+ZoneTableSegmentMapped::getHeaderHelper() const {
+ if (!isUsable()) {
+ isc_throw(isc::InvalidOperation,
+ "getHeader() called without calling reset() first");
+ }
+
+ assert(cached_header_);
+ return (cached_header_);
+}
+
+ZoneTableHeader&
+ZoneTableSegmentMapped::getHeader() {
+ return (*getHeaderHelper<ZoneTableHeader>());
+}
+
+const ZoneTableHeader&
+ZoneTableSegmentMapped::getHeader() const {
+ return (*getHeaderHelper<const ZoneTableHeader>());
+}
+
+MemorySegment&
+ZoneTableSegmentMapped::getMemorySegment() {
+ if (!isUsable()) {
+ isc_throw(isc::InvalidOperation,
+ "getMemorySegment() called without calling reset() first");
+ }
+ return (*mem_sgmt_);
+}
+
+bool
+ZoneTableSegmentMapped::isUsable() const {
+ // If mem_sgmt_ is not empty, then it is usable.
+ return (mem_sgmt_);
+}
+
+bool
+ZoneTableSegmentMapped::isWritable() const {
+ if (!isUsable()) {
+ // If reset() was never performed for this segment, or if the
+ // most recent reset() had failed, then the segment is not
+ // writable.
+ return (false);
+ }
+
+ return ((current_mode_ == CREATE) || (current_mode_ == READ_WRITE));
+}
+
+} // namespace memory
+} // namespace datasrc
+} // namespace isc
diff --git a/src/lib/datasrc/memory/zone_table_segment_mapped.h b/src/lib/datasrc/memory/zone_table_segment_mapped.h
new file mode 100644
index 0000000..93bd7fa
--- /dev/null
+++ b/src/lib/datasrc/memory/zone_table_segment_mapped.h
@@ -0,0 +1,150 @@
+// 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 ZONE_TABLE_SEGMENT_MAPPED_H
+#define ZONE_TABLE_SEGMENT_MAPPED_H
+
+#include <datasrc/memory/zone_table_segment.h>
+#include <util/memory_segment_mapped.h>
+
+#include <boost/scoped_ptr.hpp>
+#include <string>
+
+namespace isc {
+namespace datasrc {
+namespace memory {
+
+/// \brief Mapped-file based implementation of \c ZoneTableSegment class
+///
+/// This class specifies a concrete implementation for a memory-mapped
+/// \c ZoneTableSegment. Please see the \c ZoneTableSegment class
+/// documentation for usage.
+class ZoneTableSegmentMapped : public ZoneTableSegment {
+ // This is so that \c ZoneTableSegmentMapped can be instantiated
+ // from \c ZoneTableSegment::create().
+ friend class ZoneTableSegment;
+
+protected:
+ /// \brief Protected constructor
+ ///
+ /// Instances are expected to be created by the factory method
+ /// (\c ZoneTableSegment::create()), so this constructor is
+ /// protected.
+ ZoneTableSegmentMapped(const isc::dns::RRClass& rrclass);
+
+public:
+ /// \brief Destructor
+ virtual ~ZoneTableSegmentMapped();
+
+ /// \brief Returns "mapped" as the implementation type.
+ virtual const std::string& getImplType() const;
+
+ /// \brief Resets the \c ZoneTableHeader address from the named
+ /// address in the mapped file. This method should be called once
+ /// before calls to \c getHeader() if the mapped \c MemorySegment
+ /// has grown.
+ virtual void resetHeader();
+
+ /// \brief Return the \c ZoneTableHeader for this mapped zone table
+ /// segment.
+ ///
+ /// \throws isc::InvalidOperation if this method is called without a
+ /// successful \c reset() call first.
+ virtual ZoneTableHeader& getHeader();
+
+ /// \brief const version of \c getHeader().
+ virtual const ZoneTableHeader& getHeader() const;
+
+ /// \brief Return the \c MemorySegment for the memory-mapped zone
+ /// table segment implementation (a \c MemorySegmentMapped
+ /// instance).
+ ///
+ /// \throws isc::InvalidOperation if this method is called without a
+ /// successful \c reset() call first.
+ virtual isc::util::MemorySegment& getMemorySegment();
+
+ /// \brief Returns if the segment is writable.
+ ///
+ /// Segments successfully opened in CREATE or READ_WRITE modes are
+ /// writable. Segments opened in READ_ONLY mode are not writable.
+ /// If the \c ZoneTableSegment was cleared for some reason, it is
+ /// not writable until it is reset successfully.
+ virtual bool isWritable() const;
+
+ /// \brief Close the current \c MemorySegment (if open) and open the
+ /// requested one.
+ ///
+ /// See \c MemorySegmentOpenMode for a definition of "storage area"
+ /// and the various modes in which a \c MemorySegment can be opened.
+ ///
+ /// \c params should be a map containing a "mapped-file" key that
+ /// points to a string value containing the filename of a mapped
+ /// file. E.g.,
+ ///
+ /// {"mapped-file": "/var/bind10/mapped-files/zone-sqlite3.mapped.0"}
+ ///
+ /// Please see the \c ZoneTableSegment API documentation for the
+ /// behavior in case of exceptions.
+ ///
+ /// \throws isc::Unexpected when it's unable to lookup a named
+ /// address that it expected to be present. This is extremely
+ /// unlikely, and it points to corruption.
+ ///
+ /// \param mode The open mode (see the \c MemorySegmentOpenMode
+ /// documentation in \c ZoneTableSegment class).
+ /// \param params An element containing config for the mapped file
+ /// (see the description).
+ virtual void reset(MemorySegmentOpenMode mode,
+ isc::data::ConstElementPtr params);
+
+ /// \brief Close the currently configured \c MemorySegment (if
+ /// open). See the base class for a definition of "open" and
+ /// "close".
+ virtual void clear();
+
+ /// \brief Return true if the segment is usable.
+ ///
+ /// See the base class for the description.
+ virtual bool isUsable() const;
+
+private:
+ void sync();
+
+ bool processChecksum(isc::util::MemorySegmentMapped& segment, bool create,
+ std::string& error_msg);
+ bool processHeader(isc::util::MemorySegmentMapped& segment, bool create,
+ std::string& error_msg);
+
+ isc::util::MemorySegmentMapped* openReadWrite(const std::string& filename,
+ bool create);
+ isc::util::MemorySegmentMapped* openReadOnly(const std::string& filename);
+
+ template<typename T> T* getHeaderHelper() const;
+
+private:
+ std::string impl_type_;
+ isc::dns::RRClass rrclass_;
+ MemorySegmentOpenMode current_mode_;
+ std::string current_filename_;
+ // Internally holds a MemorySegmentMapped. This is NULL on
+ // construction, and is set by the \c reset() method.
+ boost::scoped_ptr<isc::util::MemorySegmentMapped> mem_sgmt_;
+ ZoneTableHeader* cached_header_;
+};
+
+} // namespace memory
+} // namespace datasrc
+} // namespace isc
+
+#endif // ZONE_TABLE_SEGMENT_MAPPED_H
diff --git a/src/lib/datasrc/memory/zone_writer.cc b/src/lib/datasrc/memory/zone_writer.cc
new file mode 100644
index 0000000..e3c9039
--- /dev/null
+++ b/src/lib/datasrc/memory/zone_writer.cc
@@ -0,0 +1,109 @@
+// Copyright (C) 2012 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 <datasrc/memory/zone_writer.h>
+#include <datasrc/memory/zone_data.h>
+#include <datasrc/memory/zone_table_segment.h>
+
+#include <memory>
+
+using std::auto_ptr;
+
+namespace isc {
+namespace datasrc {
+namespace memory {
+
+ZoneWriter::ZoneWriter(ZoneTableSegment& segment,
+ const LoadAction& load_action,
+ const dns::Name& origin,
+ const dns::RRClass& rrclass) :
+ segment_(segment),
+ load_action_(load_action),
+ origin_(origin),
+ rrclass_(rrclass),
+ zone_data_(NULL),
+ state_(ZW_UNUSED)
+{
+ if (!segment.isWritable()) {
+ isc_throw(isc::InvalidOperation,
+ "Attempt to construct ZoneWriter for a read-only segment");
+ }
+}
+
+ZoneWriter::~ZoneWriter() {
+ // Clean up everything there might be left if someone forgot, just
+ // in case.
+ cleanup();
+}
+
+void
+ZoneWriter::load() {
+ if (state_ != ZW_UNUSED) {
+ isc_throw(isc::InvalidOperation, "Trying to load twice");
+ }
+
+ zone_data_ = load_action_(segment_.getMemorySegment());
+
+ if (!zone_data_) {
+ // Bug inside load_action_.
+ isc_throw(isc::InvalidOperation, "No data returned from load action");
+ }
+
+ segment_.resetHeader();
+
+ state_ = ZW_LOADED;
+}
+
+void
+ZoneWriter::install() {
+ if (state_ != ZW_LOADED) {
+ isc_throw(isc::InvalidOperation, "No data to install");
+ }
+
+ // 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 (state_ != ZW_INSTALLED) {
+ try {
+ ZoneTable* table(segment_.getHeader().getTable());
+ if (table == NULL) {
+ isc_throw(isc::Unexpected, "No zone table present");
+ }
+ const ZoneTable::AddResult result(table->addZone(
+ segment_.getMemorySegment(),
+ rrclass_, origin_,
+ zone_data_));
+ state_ = ZW_INSTALLED;
+ zone_data_ = result.zone_data;
+ } catch (const isc::util::MemorySegmentGrown&) {
+ }
+ }
+
+ segment_.resetHeader();
+}
+
+void
+ZoneWriter::cleanup() {
+ // We eat the data (if any) now.
+
+ if (zone_data_ != NULL) {
+ ZoneData::destroy(segment_.getMemorySegment(), zone_data_, rrclass_);
+ zone_data_ = NULL;
+ state_ = ZW_CLEANED;
+ }
+}
+
+}
+}
+}
diff --git a/src/lib/datasrc/memory/zone_writer.h b/src/lib/datasrc/memory/zone_writer.h
index 0e8f285..1c4e944 100644
--- a/src/lib/datasrc/memory/zone_writer.h
+++ b/src/lib/datasrc/memory/zone_writer.h
@@ -15,7 +15,11 @@
#ifndef MEM_ZONE_WRITER_H
#define MEM_ZONE_WRITER_H
-#include "load_action.h"
+#include <datasrc/memory/zone_table_segment.h>
+#include <datasrc/memory/load_action.h>
+
+#include <dns/rrclass.h>
+#include <dns/name.h>
namespace isc {
namespace datasrc {
@@ -23,22 +27,33 @@ namespace memory {
/// \brief Does an update to a zone.
///
-/// This abstract base class represents the work of a reload of a zone.
-/// The work is divided into three stages -- load(), install() and cleanup().
-/// They should be called in this order for the effect to take place.
+/// This represents the work of a (re)load of a zone. The work is divided
+/// into three stages -- load(), install() and cleanup(). They should
+/// be called in this order for the effect to take place.
///
/// We divide them so the update of zone data can be done asynchronously,
/// in a different thread. The install() operation is the only one that needs
/// to be done in a critical section.
///
-/// Each derived class implementation must provide the strong exception
-/// guarantee for each public method. That is, when any of the methods
-/// throws, the entire state should stay the same as before the call
-/// (how to achieve that may be implementation dependant).
+/// This class provides strong exception guarantee for each public
+/// method. That is, when any of the methods throws, the entire state
+/// stays the same as before the call.
class ZoneWriter {
public:
- /// \brief Virtual destructor.
- virtual ~ZoneWriter() {};
+ /// \brief Constructor
+ ///
+ /// \throw isc::InvalidOperation if \c segment is read-only.
+ ///
+ /// \param segment The zone table segment to store the zone into.
+ /// \param load_action The callback used to load data.
+ /// \param name The name of the zone.
+ /// \param rrclass The class of the zone.
+ ZoneWriter(ZoneTableSegment& segment,
+ const LoadAction& load_action, const dns::Name& name,
+ const dns::RRClass& rrclass);
+
+ /// \brief Destructor.
+ ~ZoneWriter();
/// \brief Get the zone data into memory.
///
@@ -56,7 +71,7 @@ public:
/// \note After successful load(), you have to call cleanup() some time
/// later.
/// \throw isc::InvalidOperation if called second time.
- virtual void load() = 0;
+ void load();
/// \brief Put the changes to effect.
///
@@ -68,12 +83,12 @@ public:
/// The operation is expected to be fast and is meant to be used inside
/// a critical section.
///
- /// This may throw in rare cases, depending on the concrete implementation.
- /// If it throws, you still need to call cleanup().
+ /// This may throw in rare cases. If it throws, you still need to
+ /// call cleanup().
///
/// \throw isc::InvalidOperation if called without previous load() or for
/// the second time or cleanup() was called already.
- virtual void install() = 0;
+ void install();
/// \brief Clean up resources.
///
@@ -81,8 +96,22 @@ public:
/// one loaded by load() in case install() was not called or was not
/// successful, or the one replaced in install().
///
- /// Generally, this should never throw.
- virtual void cleanup() = 0;
+ /// \throw none
+ void cleanup();
+
+private:
+ ZoneTableSegment& segment_;
+ const LoadAction load_action_;
+ const dns::Name origin_;
+ const dns::RRClass rrclass_;
+ ZoneData* zone_data_;
+ enum State {
+ ZW_UNUSED,
+ ZW_LOADED,
+ ZW_INSTALLED,
+ ZW_CLEANED
+ };
+ State state_;
};
}
diff --git a/src/lib/datasrc/memory/zone_writer_local.cc b/src/lib/datasrc/memory/zone_writer_local.cc
deleted file mode 100644
index 01827a3..0000000
--- a/src/lib/datasrc/memory/zone_writer_local.cc
+++ /dev/null
@@ -1,101 +0,0 @@
-// Copyright (C) 2012 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 "zone_writer_local.h"
-#include "zone_data.h"
-#include "zone_table_segment_local.h"
-
-#include <memory>
-
-using std::auto_ptr;
-
-namespace isc {
-namespace datasrc {
-namespace memory {
-
-ZoneWriterLocal::ZoneWriterLocal(ZoneTableSegmentLocal* segment,
- const LoadAction& load_action,
- const dns::Name& origin,
- const dns::RRClass& rrclass) :
- segment_(segment),
- load_action_(load_action),
- origin_(origin),
- rrclass_(rrclass),
- zone_data_(NULL),
- state_(ZW_UNUSED)
-{}
-
-ZoneWriterLocal::~ZoneWriterLocal() {
- // Clean up everything there might be left if someone forgot, just
- // in case.
- cleanup();
-}
-
-void
-ZoneWriterLocal::load() {
- if (state_ != ZW_UNUSED) {
- isc_throw(isc::InvalidOperation, "Trying to load twice");
- }
-
- zone_data_ = load_action_(segment_->getMemorySegment());
-
- if (zone_data_ == NULL) {
- // Bug inside load_action_.
- isc_throw(isc::InvalidOperation, "No data returned from load action");
- }
-
- state_ = ZW_LOADED;
-}
-
-void
-ZoneWriterLocal::install() {
- if (state_ != ZW_LOADED) {
- isc_throw(isc::InvalidOperation, "No data to install");
- }
-
- // 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 (state_ != ZW_INSTALLED) {
- try {
- ZoneTable* table(segment_->getHeader().getTable());
- if (table == NULL) {
- isc_throw(isc::Unexpected, "No zone table present");
- }
- const ZoneTable::AddResult result(table->addZone(
- segment_->getMemorySegment(),
- rrclass_, origin_,
- zone_data_));
- state_ = ZW_INSTALLED;
- zone_data_ = result.zone_data;
- } catch (const isc::util::MemorySegmentGrown&) {
- }
- }
-
-}
-
-void
-ZoneWriterLocal::cleanup() {
- // We eat the data (if any) now.
-
- if (zone_data_ != NULL) {
- ZoneData::destroy(segment_->getMemorySegment(), zone_data_, rrclass_);
- zone_data_ = NULL;
- state_ = ZW_CLEANED;
- }
-}
-
-}
-}
-}
diff --git a/src/lib/datasrc/memory/zone_writer_local.h b/src/lib/datasrc/memory/zone_writer_local.h
deleted file mode 100644
index 7231a57..0000000
--- a/src/lib/datasrc/memory/zone_writer_local.h
+++ /dev/null
@@ -1,95 +0,0 @@
-// Copyright (C) 2012 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 MEM_ZONE_WRITER_LOCAL_H
-#define MEM_ZONE_WRITER_LOCAL_H
-
-#include "zone_writer.h"
-
-#include <dns/rrclass.h>
-#include <dns/name.h>
-
-namespace isc {
-namespace datasrc {
-namespace memory {
-
-class ZoneData;
-class ZoneTableSegmentLocal;
-
-/// \brief Writer implementation which loads data locally.
-///
-/// This implementation prepares a clean zone data and lets one callback
-/// to fill it and another to install it somewhere. The class does mostly
-/// nothing (and delegates the work to the callbacks), just stores little bit
-/// of state between the calls.
-class ZoneWriterLocal : public ZoneWriter {
-public:
- /// \brief Constructor
- ///
- /// \param segment The zone table segment to store the zone into.
- /// \param load_action The callback used to load data.
- /// \param install_action The callback used to install the loaded zone.
- /// \param rrclass The class of the zone.
- ZoneWriterLocal(ZoneTableSegmentLocal* segment,
- const LoadAction& load_action, const dns::Name& name,
- const dns::RRClass& rrclass);
-
- /// \brief Destructor
- ~ZoneWriterLocal();
-
- /// \brief Loads the data.
- ///
- /// This calls the load_action (passed to constructor) and stores the
- /// data for future use.
- ///
- /// \throw isc::InvalidOperation if it is called the second time in
- /// lifetime of the object.
- /// \throw Whatever the load_action throws, it is propagated up.
- virtual void load();
-
- /// \brief Installs the zone.
- ///
- /// It modifies the zone table accessible through the segment (passed to
- /// constructor).
- ///
- /// \throw isc::InvalidOperation if it is called the second time in
- /// lifetime of the object or if load() was not called previously or if
- /// cleanup() was already called.
- virtual void install();
-
- /// \brief Clean up memory.
- ///
- /// Cleans up the memory used by load()ed zone if not yet installed, or
- /// the old zone replaced by install().
- virtual void cleanup();
-private:
- ZoneTableSegmentLocal* segment_;
- LoadAction load_action_;
- dns::Name origin_;
- dns::RRClass rrclass_;
- ZoneData* zone_data_;
- enum State {
- ZW_UNUSED,
- ZW_LOADED,
- ZW_INSTALLED,
- ZW_CLEANED
- };
- State state_;
-};
-
-}
-}
-}
-
-#endif
diff --git a/src/lib/datasrc/sqlite3_accessor.cc b/src/lib/datasrc/sqlite3_accessor.cc
index b7c063d..93f468c 100644
--- a/src/lib/datasrc/sqlite3_accessor.cc
+++ b/src/lib/datasrc/sqlite3_accessor.cc
@@ -23,8 +23,9 @@
#include <dns/name.h>
#include <datasrc/sqlite3_accessor.h>
+#include <datasrc/sqlite3_datasrc_messages.h>
#include <datasrc/logger.h>
-#include <datasrc/data_source.h>
+#include <datasrc/exceptions.h>
#include <datasrc/factory.h>
#include <datasrc/database.h>
#include <util/filename.h>
@@ -103,7 +104,9 @@ const char* const text_statements[NUM_STATEMENTS] = {
"INSERT INTO records " // ADD_RECORD
"(zone_id, name, rname, ttl, rdtype, sigtype, rdata) "
"VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)",
- "DELETE FROM records WHERE zone_id=?1 AND name=?2 " // DEL_RECORD
+ // DEL_RECORD:
+ // Delete based on the reverse name, as that one has an index.
+ "DELETE FROM records WHERE zone_id=?1 AND rname=?2 " // DEL_RECORD
"AND rdtype=?3 AND rdata=?4",
// ITERATE_RECORDS:
@@ -1294,19 +1297,27 @@ SQLite3Accessor::deleteRecordInZone(const string (¶ms)[DEL_PARAM_COUNT]) {
isc_throw(DataSourceError, "deleting record in SQLite3 "
"data source without transaction");
}
- doUpdate<const string (&)[DEL_PARAM_COUNT]>(
- *dbparameters_, DEL_RECORD, params, "delete record from zone");
+ // We don't pass all the parameters to the query, one name (reserve one
+ // in this case) is sufficient. Pass only the needed ones.
+ const size_t SQLITE3_DEL_PARAM_COUNT = DEL_PARAM_COUNT - 1;
+ const string sqlite3_params[SQLITE3_DEL_PARAM_COUNT] = {
+ params[DEL_RNAME],
+ params[DEL_TYPE],
+ params[DEL_RDATA]
+ };
+ doUpdate<const string (&)[SQLITE3_DEL_PARAM_COUNT]>(
+ *dbparameters_, DEL_RECORD, sqlite3_params, "delete record from zone");
}
void
SQLite3Accessor::deleteNSEC3RecordInZone(
- const string (¶ms)[DEL_PARAM_COUNT])
+ const string (¶ms)[DEL_NSEC3_PARAM_COUNT])
{
if (!dbparameters_->updating_zone) {
isc_throw(DataSourceError, "deleting NSEC3-related record in SQLite3 "
"data source without transaction");
}
- doUpdate<const string (&)[DEL_PARAM_COUNT]>(
+ doUpdate<const string (&)[DEL_NSEC3_PARAM_COUNT]>(
*dbparameters_, DEL_NSEC3_RECORD, params,
"delete NSEC3 record from zone");
}
diff --git a/src/lib/datasrc/sqlite3_accessor.h b/src/lib/datasrc/sqlite3_accessor.h
index d014193..f8c4138 100644
--- a/src/lib/datasrc/sqlite3_accessor.h
+++ b/src/lib/datasrc/sqlite3_accessor.h
@@ -17,7 +17,7 @@
#define DATASRC_SQLITE3_ACCESSOR_H
#include <datasrc/database.h>
-#include <datasrc/data_source.h>
+#include <datasrc/exceptions.h>
#include <exceptions/exceptions.h>
@@ -230,7 +230,7 @@ public:
const std::string (¶ms)[DEL_PARAM_COUNT]);
virtual void deleteNSEC3RecordInZone(
- const std::string (¶ms)[DEL_PARAM_COUNT]);
+ const std::string (¶ms)[DEL_NSEC3_PARAM_COUNT]);
/// This derived version of the method prepares an SQLite3 statement
/// for adding the diff first time it's called, and if it fails throws
@@ -250,7 +250,7 @@ public:
virtual std::string findPreviousName(int zone_id, const std::string& rname)
const;
- /// \brief Conrete implemantion of the pure virtual method of
+ /// \brief Concrete implementation of the pure virtual method of
/// DatabaseAccessor
virtual std::string findPreviousNSEC3Hash(int zone_id,
const std::string& hash) const;
diff --git a/src/lib/datasrc/sqlite3_accessor_link.cc b/src/lib/datasrc/sqlite3_accessor_link.cc
index 56e0c2f..6f38de4 100644
--- a/src/lib/datasrc/sqlite3_accessor_link.cc
+++ b/src/lib/datasrc/sqlite3_accessor_link.cc
@@ -19,6 +19,8 @@
#include <datasrc/sqlite3_accessor.h>
#include <datasrc/database.h>
+#include <log/message_initializer.h>
+
#include <string>
using namespace std;
@@ -77,6 +79,9 @@ checkConfig(ConstElementPtr config, ElementPtr errors) {
DataSourceClient *
createInstance(isc::data::ConstElementPtr config, std::string& error) {
+ // Initialize the logging dictionary
+ isc::log::MessageInitializer::loadDictionary(true);
+
ElementPtr errors(Element::createList());
if (!checkConfig(config, errors)) {
error = "Configuration error: " + errors->str();
diff --git a/src/lib/datasrc/sqlite3_datasrc_messages.mes b/src/lib/datasrc/sqlite3_datasrc_messages.mes
new file mode 100644
index 0000000..a462d43
--- /dev/null
+++ b/src/lib/datasrc/sqlite3_datasrc_messages.mes
@@ -0,0 +1,142 @@
+# Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+$NAMESPACE isc::datasrc
+
+# \brief Messages for the SQLITE3 data source backend
+
+% DATASRC_SQLITE_CLOSE closing SQLite database
+Debug information. The SQLite data source is closing the database file.
+
+% DATASRC_SQLITE_COMPATIBLE_VERSION database schema V%1.%2 not up to date (expecting V%3.%4) but is compatible
+The version of the SQLite3 database schema used to hold the zone data
+is not the latest one - the current version of BIND 10 was written
+with a later schema version in mind. However, the database is
+compatible with the current version of BIND 10, and BIND 10 will run
+without any problems.
+
+Consult the release notes for your version of BIND 10. Depending on
+the changes made to the database schema, it is possible that improved
+performance could result if the database were upgraded.
+
+% DATASRC_SQLITE_CONNCLOSE Closing sqlite database
+The database file is no longer needed and is being closed.
+
+% DATASRC_SQLITE_CONNOPEN Opening sqlite database file '%1'
+The database file is being opened so it can start providing data.
+
+% DATASRC_SQLITE_CREATE SQLite data source created
+Debug information. An instance of SQLite data source is being created.
+
+% DATASRC_SQLITE_DESTROY SQLite data source destroyed
+Debug information. An instance of SQLite data source is being destroyed.
+
+% DATASRC_SQLITE_DROPCONN SQLite3Database is being deinitialized
+The object around a database connection is being destroyed.
+
+% DATASRC_SQLITE_ENCLOSURE looking for zone containing '%1'
+Debug information. The SQLite data source is trying to identify which zone
+should hold this domain.
+
+% DATASRC_SQLITE_ENCLOSURE_NOT_FOUND no zone contains '%1'
+Debug information. The last SQLITE_ENCLOSURE query was unsuccessful; there's
+no such zone in our data.
+
+% DATASRC_SQLITE_FIND looking for RRset '%1/%2'
+Debug information. The SQLite data source is looking up a resource record
+set.
+
+% DATASRC_SQLITE_FINDADDRS looking for A/AAAA addresses for '%1'
+Debug information. The data source is looking up the addresses for given
+domain name.
+
+% DATASRC_SQLITE_FINDADDRS_BAD_CLASS class mismatch looking for addresses ('%1' and '%2')
+The SQLite data source was looking up A/AAAA addresses, but the data source
+contains different class than the query was for.
+
+% DATASRC_SQLITE_FINDEXACT looking for exact RRset '%1/%2'
+Debug information. The SQLite data source is looking up an exact resource
+record.
+
+% DATASRC_SQLITE_FINDEXACT_BAD_CLASS class mismatch looking for an RRset ('%1' and '%2')
+The SQLite data source was looking up an exact RRset, but the data source
+contains different class than the query was for.
+
+% DATASRC_SQLITE_FINDREC looking for record '%1/%2'
+Debug information. The SQLite data source is looking up records of given name
+and type in the database.
+
+% DATASRC_SQLITE_FINDREF looking for referral at '%1'
+Debug information. The SQLite data source is identifying if this domain is
+a referral and where it goes.
+
+% DATASRC_SQLITE_FINDREF_BAD_CLASS class mismatch looking for referral ('%1' and '%2')
+The SQLite data source was trying to identify if there's a referral. But
+it contains different class than the query was for.
+
+% DATASRC_SQLITE_FIND_BAD_CLASS class mismatch looking for an RRset ('%1' and '%2')
+The SQLite data source was looking up an RRset, but the data source contains
+different class than the query was for.
+
+% DATASRC_SQLITE_FIND_NSEC3 looking for NSEC3 in zone '%1' for hash '%2'
+Debug information. We're trying to look up a NSEC3 record in the SQLite data
+source.
+
+% DATASRC_SQLITE_FIND_NSEC3_NO_ZONE no such zone '%1'
+The SQLite data source was asked to provide a NSEC3 record for given zone.
+But it doesn't contain that zone.
+
+% DATASRC_SQLITE_INCOMPATIBLE_VERSION database schema V%1.%2 incompatible with version (V%3.%4) expected
+The version of the SQLite3 database schema used to hold the zone data
+is incompatible with the version expected by BIND 10. As a result,
+BIND 10 is unable to run using the database file as the data source.
+
+The database should be updated using the means described in the BIND
+10 documentation.
+
+% DATASRC_SQLITE_NEWCONN SQLite3Database is being initialized
+A wrapper object to hold database connection is being initialized.
+
+% DATASRC_SQLITE_OPEN opening SQLite database '%1'
+Debug information. The SQLite data source is loading an SQLite database in
+the provided file.
+
+% DATASRC_SQLITE_PREVIOUS looking for name previous to '%1'
+This is a debug message. The name given was not found, so the program
+is searching for the next name higher up the hierarchy (e.g. if
+www.example.com were queried for and not found, the software searches
+for the "previous" name, example.com).
+
+% DATASRC_SQLITE_PREVIOUS_NO_ZONE no zone containing '%1'
+The name given was not found, so the program is searching for the next
+name higher up the hierarchy (e.g. if www.example.com were queried
+for and not found, the software searches for the "previous" name,
+example.com). However, this name is not contained in any zone in the
+data source. This is an error since it indicates a problem in the earlier
+processing of the query.
+
+% DATASRC_SQLITE_SETUP setting up new SQLite3 database in '%1'
+The database for SQLite data source was found empty. It is assumed this is the
+first run and it is being initialized with current schema. It'll still contain
+no data, but it will be ready for use. If this is indeed the first run of
+BIND 10, it is to be expected and completely harmless. If you just configured
+a data source to point to an existing file and you see this, you may have
+misspelled the file name.
+
+% DATASRC_SQLITE_SETUP_OLD_API setting up new SQLite database
+The database for SQLite data source was found empty. It is assumed this is the
+first run and it is being initialized with current schema. It'll still contain
+no data, but it will be ready for use. This is similar to DATASRC_SQLITE_SETUP
+message, but it is logged from the old API. You should never see it, since the
+API is deprecated.
diff --git a/src/lib/datasrc/static_datasrc.h b/src/lib/datasrc/static_datasrc.h
deleted file mode 100644
index d5d8875..0000000
--- a/src/lib/datasrc/static_datasrc.h
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-
-#ifndef DATASRC_STATIC_H
-#define DATASRC_STATIC_H
-
-#include <datasrc/database.h>
-#include <cc/data.h>
-
-#include <string>
-
-namespace isc {
-namespace datasrc {
-
-/// \brief Creates an instance of the static datasource client
-///
-/// Currently the configuration passed here must be a StringElement,
-/// containing the path to a zone file for the BIND./CH zone.
-///
-/// \param config The configuration for the datasource instance (see above)
-/// \param error This string will be set to an error message if an error occurs
-/// during initialization
-/// \return An instance of the static datasource client, or NULL if there was
-/// an error
-extern "C" DataSourceClient* createInstance(isc::data::ConstElementPtr config,
- std::string& error);
-
-/// \brief Destroy the instance created by createInstance()
-extern "C" void destroyInstance(DataSourceClient* instance);
-
-}
-}
-
-#endif // DATASRC_STATIC_H
-
-// Local Variables:
-// mode: c++
-// End:
diff --git a/src/lib/datasrc/static_datasrc_link.cc b/src/lib/datasrc/static_datasrc_link.cc
deleted file mode 100644
index 1b767a4..0000000
--- a/src/lib/datasrc/static_datasrc_link.cc
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright (C) 2012 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 "client.h"
-#include "static_datasrc.h"
-#include <datasrc/memory/memory_client.h>
-#include <datasrc/memory/zone_table_segment.h>
-
-#include <cc/data.h>
-#include <dns/rrclass.h>
-
-#include <memory>
-#include <exception>
-
-using namespace isc::data;
-using namespace isc::dns;
-using namespace boost;
-using namespace std;
-
-namespace isc {
-namespace datasrc {
-
-DataSourceClient*
-createInstance(ConstElementPtr config, string& error) {
- try {
- // FIXME: Fix the config that should be passed to
- // ZoneTableSegment::create() when it actually uses the config
- // to do something.
- shared_ptr<memory::ZoneTableSegment> ztable_segment(
- memory::ZoneTableSegment::create(isc::data::NullElement(),
- RRClass::CH()));
- // Create the data source
- auto_ptr<memory::InMemoryClient> client
- (new memory::InMemoryClient(ztable_segment, RRClass::CH()));
-
- // Fill it with data
- const string path(config->stringValue());
- client->load(Name("BIND"), path);
-
- return (client.release());
- }
- catch (const std::exception& e) {
- error = e.what();
- }
- catch (...) {
- error = "Unknown exception";
- }
- return (NULL);
-}
-
-void
-destroyInstance(DataSourceClient* instance) {
- delete instance;
-}
-
-}
-}
diff --git a/src/lib/datasrc/tests/Makefile.am b/src/lib/datasrc/tests/Makefile.am
index 7960963..f9380d8 100644
--- a/src/lib/datasrc/tests/Makefile.am
+++ b/src/lib/datasrc/tests/Makefile.am
@@ -48,6 +48,7 @@ common_ldadd += $(GTEST_LDADD) $(SQLITE_LIBS)
run_unittests_SOURCES = $(common_sources)
run_unittests_SOURCES += test_client.h test_client.cc
+run_unittests_SOURCES += mock_client.h mock_client.cc
run_unittests_SOURCES += logger_unittest.cc
run_unittests_SOURCES += client_unittest.cc
run_unittests_SOURCES += database_unittest.h database_unittest.cc
@@ -58,10 +59,14 @@ run_unittests_SOURCES += faked_nsec3.h faked_nsec3.cc
run_unittests_SOURCES += client_list_unittest.cc
run_unittests_SOURCES += master_loader_callbacks_test.cc
run_unittests_SOURCES += zone_loader_unittest.cc
+run_unittests_SOURCES += cache_config_unittest.cc
+run_unittests_SOURCES += zone_table_accessor_unittest.cc
# We need the actual module implementation in the tests (they are not part
# of libdatasrc)
run_unittests_SOURCES += $(top_srcdir)/src/lib/datasrc/sqlite3_accessor.cc
+# Also, as of #2746, sqlite3-specific log messages are in a separate file
+nodist_run_unittests_SOURCES = $(abs_top_builddir)/src/lib/datasrc/sqlite3_datasrc_messages.cc
run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
@@ -71,7 +76,7 @@ run_unittests_LDADD = $(common_ldadd)
noinst_PROGRAMS+= $(TESTS)
# For the factory unit tests, we need to specify that we want
-# the loadable backend libraries from the build tree, and not from
+# the loadable backend libraries from the build tree, and not from
# the installation directory. Therefore we build it into a separate
# binary, and call that from check-local with B10_FROM_BUILD set.
# Also, we only want to do this when static building is not used,
diff --git a/src/lib/datasrc/tests/cache_config_unittest.cc b/src/lib/datasrc/tests/cache_config_unittest.cc
new file mode 100644
index 0000000..34dd5d1
--- /dev/null
+++ b/src/lib/datasrc/tests/cache_config_unittest.cc
@@ -0,0 +1,312 @@
+// 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 <datasrc/cache_config.h>
+#include <datasrc/exceptions.h>
+#include <datasrc/memory/load_action.h>
+#include <datasrc/memory/zone_data.h>
+#include <datasrc/tests/mock_client.h>
+
+#include <cc/data.h>
+#include <util/memory_segment_local.h>
+#include <dns/name.h>
+#include <dns/rrclass.h>
+
+#include <gtest/gtest.h>
+
+#include <iterator> // for std::distance
+
+using namespace isc::datasrc;
+using namespace isc::data;
+using namespace isc::dns;
+using isc::datasrc::unittest::MockDataSourceClient;
+using isc::datasrc::internal::CacheConfig;
+using isc::datasrc::internal::CacheConfigError;
+using isc::datasrc::memory::LoadAction;
+using isc::datasrc::memory::ZoneData;
+
+namespace {
+
+const char* zones[] = {
+ "example.org.",
+ "example.com.",
+ "null.org", // test for bad iterator case
+ NULL
+};
+
+class CacheConfigTest : public ::testing::Test {
+protected:
+ CacheConfigTest() :
+ mock_client_(zones),
+ master_config_(Element::fromJSON(
+ "{\"cache-enable\": true,"
+ " \"params\": "
+ " {\".\": \"" TEST_DATA_DIR "/root.zone\"}"
+ "}")),
+ mock_config_(Element::fromJSON("{\"cache-enable\": true,"
+ " \"cache-zones\": [\".\"]}"))
+ {}
+
+ virtual void TearDown() {
+ EXPECT_TRUE(msgmt_.allMemoryDeallocated());
+ }
+
+ MockDataSourceClient mock_client_;
+ const ConstElementPtr master_config_; // valid config for MasterFiles
+ const ConstElementPtr mock_config_; // valid config for MasterFiles
+ isc::util::MemorySegmentLocal msgmt_;
+};
+
+size_t
+countZones(const CacheConfig& cache_config) {
+ return (std::distance(cache_config.begin(), cache_config.end()));
+}
+
+TEST_F(CacheConfigTest, constructMasterFiles) {
+ // A simple case: configuring a MasterFiles table with a single zone
+ const CacheConfig cache_conf("MasterFiles", 0, *master_config_, true);
+ EXPECT_EQ(1, countZones(cache_conf));
+
+ // With multiple zones. Note that the constructor doesn't check if the
+ // file exists, so they can be anything.
+ const ConstElementPtr config_elem_multi(
+ Element::fromJSON("{\"cache-enable\": true,"
+ " \"params\": "
+ "{\"example.com\": \"file1\","
+ " \"example.org\": \"file2\","
+ " \"example.info\": \"file3\"}"
+ "}"));
+ const CacheConfig cache_conf2("MasterFiles", 0, *config_elem_multi, true);
+ EXPECT_EQ(3, countZones(cache_conf2));
+
+ // A bit unusual, but acceptable case: empty parameters, so no zones.
+ const CacheConfig cache_conf3("MasterFiles", 0,
+ *Element::fromJSON("{\"cache-enable\": true,"
+ " \"params\": {}}"),
+ true);
+ EXPECT_EQ(0, countZones(cache_conf3));
+}
+
+TEST_F(CacheConfigTest, badConstructMasterFiles) {
+ // no "params"
+ EXPECT_THROW(CacheConfig("MasterFiles", 0,
+ *Element::fromJSON("{\"cache-enable\": true}"),
+ true),
+ isc::data::TypeError);
+
+ // no "cache-enable"
+ EXPECT_THROW(CacheConfig("MasterFiles", 0,
+ *Element::fromJSON("{\"params\": {}}"), true),
+ CacheConfigError);
+ // cache disabled for MasterFiles
+ EXPECT_THROW(CacheConfig("MasterFiles", 0,
+ *Element::fromJSON("{\"cache-enable\": false,"
+ " \"params\": {}}"), true),
+ CacheConfigError);
+ // cache enabled but not "allowed"
+ EXPECT_THROW(CacheConfig("MasterFiles", 0,
+ *Element::fromJSON("{\"cache-enable\": false,"
+ " \"params\": {}}"), false),
+ CacheConfigError);
+ // type error for cache-enable
+ EXPECT_THROW(CacheConfig("MasterFiles", 0,
+ *Element::fromJSON("{\"cache-enable\": 1,"
+ " \"params\": {}}"), true),
+ isc::data::TypeError);
+
+ // "params" is not a map
+ EXPECT_THROW(CacheConfig("MasterFiles", 0,
+ *Element::fromJSON("{\"cache-enable\": true,"
+ " \"params\": []}"), true),
+ isc::data::TypeError);
+
+ // bogus zone name
+ const ConstElementPtr bad_config(Element::fromJSON(
+ "{\"cache-enable\": true,"
+ " \"params\": "
+ "{\"bad..name\": \"file1\"}}"));
+ EXPECT_THROW(CacheConfig("MasterFiles", 0, *bad_config, true),
+ isc::dns::EmptyLabel);
+
+ // file name is not a string
+ const ConstElementPtr bad_config2(Element::fromJSON(
+ "{\"cache-enable\": true,"
+ " \"params\": {\".\": 1}}"));
+ EXPECT_THROW(CacheConfig("MasterFiles", 0, *bad_config2, true),
+ isc::data::TypeError);
+
+ // Specify data source client (must be null for MasterFiles)
+ EXPECT_THROW(CacheConfig("MasterFiles", &mock_client_,
+ *Element::fromJSON("{\"cache-enable\": true,"
+ " \"params\": {}}"), true),
+ isc::InvalidParameter);
+}
+
+TEST_F(CacheConfigTest, getLoadActionWithMasterFiles) {
+ uint8_t labels_buf[LabelSequence::MAX_SERIALIZED_LENGTH];
+
+ const CacheConfig cache_conf("MasterFiles", 0, *master_config_, true);
+
+ // Check getLoadAction. Since it returns a mere functor, we can only
+ // check the behavior by actually calling it. For the purpose of this
+ // test, it should suffice if we confirm the call succeeds and shows
+ // some reasonably valid behavior (we'll check the origin name for that).
+ LoadAction action = cache_conf.getLoadAction(RRClass::IN(),
+ Name::ROOT_NAME());
+ ZoneData* zone_data = action(msgmt_);
+ ASSERT_TRUE(zone_data);
+ EXPECT_EQ(".", zone_data->getOriginNode()->
+ getAbsoluteLabels(labels_buf).toText());
+ ZoneData::destroy(msgmt_, zone_data, RRClass::IN());
+
+ // If the specified zone name is not configured to be cached,
+ // getLoadAction returns empty (false) functor.
+ EXPECT_FALSE(cache_conf.getLoadAction(RRClass::IN(), Name("example.com")));
+}
+
+TEST_F(CacheConfigTest, constructWithMock) {
+ // Performing equivalent set of tests as constructMasterFiles
+
+ // Configure with a single zone.
+ const CacheConfig cache_conf("mock", &mock_client_, *mock_config_, true);
+ EXPECT_EQ(1, countZones(cache_conf));
+ EXPECT_TRUE(cache_conf.isEnabled());
+
+ // Configure with multiple zones.
+ const ConstElementPtr config_elem_multi(
+ Element::fromJSON("{\"cache-enable\": true,"
+ " \"cache-zones\": "
+ "[\"example.com\", \"example.org\",\"example.info\"]"
+ "}"));
+ const CacheConfig cache_conf2("mock", &mock_client_, *config_elem_multi,
+ true);
+ EXPECT_EQ(3, countZones(cache_conf2));
+
+ // Empty
+ const CacheConfig cache_conf3(
+ "mock", &mock_client_,
+ *Element::fromJSON("{\"cache-enable\": true,"
+ " \"cache-zones\": []}"), true);
+ EXPECT_EQ(0, countZones(cache_conf3));
+
+ // disabled. value of cache-zones are ignored.
+ const ConstElementPtr config_elem_disabled(
+ Element::fromJSON("{\"cache-enable\": false,"
+ " \"cache-zones\": [\"example.com\"]}"));
+ EXPECT_FALSE(CacheConfig("mock", &mock_client_, *config_elem_disabled,
+ true).isEnabled());
+ // enabled but not "allowed". same effect.
+ EXPECT_FALSE(CacheConfig("mock", &mock_client_,
+ *Element::fromJSON("{\"cache-enable\": true,"
+ " \"cache-zones\": []}"),
+ false).isEnabled());
+}
+
+TEST_F(CacheConfigTest, badConstructWithMock) {
+ // no "cache-zones" (may become valid in future, but for now "notimp")
+ EXPECT_THROW(CacheConfig("mock", &mock_client_,
+ *Element::fromJSON("{\"cache-enable\": true}"),
+ true),
+ isc::NotImplemented);
+
+ // "cache-zones" is not a list
+ EXPECT_THROW(CacheConfig("mock", &mock_client_,
+ *Element::fromJSON("{\"cache-enable\": true,"
+ " \"cache-zones\": {}}"),
+ true),
+ isc::data::TypeError);
+
+ // "cache-zone" entry is not a string
+ EXPECT_THROW(CacheConfig("mock", &mock_client_,
+ *Element::fromJSON("{\"cache-enable\": true,"
+ " \"cache-zones\": [1]}"),
+ true),
+ isc::data::TypeError);
+
+ // bogus zone name
+ const ConstElementPtr bad_config(Element::fromJSON(
+ "{\"cache-enable\": true,"
+ " \"cache-zones\": [\"bad..\"]}"));
+ EXPECT_THROW(CacheConfig("mock", &mock_client_, *bad_config, true),
+ isc::dns::EmptyLabel);
+
+ // duplicate zone name (note that comparison is case insensitive)
+ const ConstElementPtr dup_config(Element::fromJSON(
+ "{\"cache-enable\": true,"
+ " \"cache-zones\": "
+ " [\"example\", \"EXAMPLE\"]}"));
+ EXPECT_THROW(CacheConfig("mock", &mock_client_, *dup_config, true),
+ CacheConfigError);
+
+ // datasrc is null
+ EXPECT_THROW(CacheConfig("mock", 0, *mock_config_, true),
+ isc::InvalidParameter);
+}
+
+TEST_F(CacheConfigTest, getLoadActionWithMock) {
+ uint8_t labels_buf[LabelSequence::MAX_SERIALIZED_LENGTH];
+
+ // Similar to MasterFiles counterpart, but using underlying source
+ // data source.
+
+ // Note: there's a mismatch between this configuration and the actual
+ // mock data source content: example.net doesn't exist in the data source.
+ const ConstElementPtr config(Element::fromJSON(
+ "{\"cache-enable\": true,"
+ " \"cache-zones\": [\"example.org\","
+ " \"example.net\", \"null.org\"]}"));
+ const CacheConfig cache_conf("mock", &mock_client_, *config, true);
+ LoadAction action = cache_conf.getLoadAction(RRClass::IN(),
+ Name("example.org"));
+ ZoneData* zone_data = action(msgmt_);
+ ASSERT_TRUE(zone_data);
+ EXPECT_EQ("example.org.", zone_data->getOriginNode()->
+ getAbsoluteLabels(labels_buf).toText());
+ ZoneData::destroy(msgmt_, zone_data, RRClass::IN());
+
+ // Zone not configured for the cache
+ EXPECT_FALSE(cache_conf.getLoadAction(RRClass::IN(), Name("example.com")));
+
+ // Zone configured for the cache but doesn't exist in the underling data
+ // source.
+ EXPECT_THROW(cache_conf.getLoadAction(RRClass::IN(), Name("example.net")),
+ NoSuchZone);
+
+ // buggy data source client: it returns a null pointer from getIterator.
+ EXPECT_THROW(cache_conf.getLoadAction(RRClass::IN(), Name("null.org")),
+ isc::Unexpected);
+}
+
+TEST_F(CacheConfigTest, getSegmentType) {
+ // Default type
+ EXPECT_EQ("local",
+ CacheConfig("MasterFiles", 0,
+ *master_config_, true).getSegmentType());
+
+ // If we explicitly configure it, that value should be used.
+ ConstElementPtr config(Element::fromJSON("{\"cache-enable\": true,"
+ " \"cache-type\": \"mapped\","
+ " \"params\": {}}" ));
+ EXPECT_EQ("mapped",
+ CacheConfig("MasterFiles", 0, *config, true).getSegmentType());
+
+ // Wrong types: should be rejected at construction time
+ ConstElementPtr badconfig(Element::fromJSON("{\"cache-enable\": true,"
+ " \"cache-type\": 1,"
+ " \"params\": {}}"));
+ EXPECT_THROW(CacheConfig("MasterFiles", 0, *badconfig, true),
+ isc::data::TypeError);
+}
+
+}
diff --git a/src/lib/datasrc/tests/client_list_unittest.cc b/src/lib/datasrc/tests/client_list_unittest.cc
index 0838fb6..745654c 100644
--- a/src/lib/datasrc/tests/client_list_unittest.cc
+++ b/src/lib/datasrc/tests/client_list_unittest.cc
@@ -14,13 +14,16 @@
#include <datasrc/client_list.h>
#include <datasrc/client.h>
+#include <datasrc/cache_config.h>
#include <datasrc/zone_iterator.h>
-#include <datasrc/data_source.h>
+#include <datasrc/exceptions.h>
#include <datasrc/memory/memory_client.h>
#include <datasrc/memory/zone_table_segment.h>
#include <datasrc/memory/zone_finder.h>
#include <datasrc/memory/zone_writer.h>
+#include <datasrc/tests/mock_client.h>
+
#include <dns/rrclass.h>
#include <dns/rrttl.h>
#include <dns/rdataclass.h>
@@ -33,6 +36,7 @@
#include <fstream>
using namespace isc::datasrc;
+using isc::datasrc::unittest::MockDataSourceClient;
using isc::datasrc::memory::InMemoryClient;
using isc::datasrc::memory::ZoneTableSegment;
using isc::datasrc::memory::InMemoryZoneFinder;
@@ -45,162 +49,6 @@ using namespace std;
namespace {
-// A test data source. It pretends it has some zones.
-class MockDataSourceClient : public DataSourceClient {
-public:
- class Finder : public ZoneFinder {
- public:
- Finder(const Name& origin) :
- origin_(origin)
- {}
- Name getOrigin() const { return (origin_); }
- // The rest is not to be called, so just have them
- RRClass getClass() const {
- isc_throw(isc::NotImplemented, "Not implemented");
- }
- shared_ptr<Context> find(const Name&, const RRType&,
- const FindOptions)
- {
- isc_throw(isc::NotImplemented, "Not implemented");
- }
- shared_ptr<Context> findAll(const Name&,
- vector<ConstRRsetPtr>&,
- const FindOptions)
- {
- isc_throw(isc::NotImplemented, "Not implemented");
- }
- FindNSEC3Result findNSEC3(const Name&, bool) {
- isc_throw(isc::NotImplemented, "Not implemented");
- }
- private:
- Name origin_;
- };
- class Iterator : public ZoneIterator {
- public:
- Iterator(const Name& origin, bool include_a) :
- origin_(origin),
- soa_(new RRset(origin_, RRClass::IN(), RRType::SOA(),
- RRTTL(3600)))
- {
- // The RData here is bogus, but it is not used to anything. There
- // just needs to be some.
- soa_->addRdata(rdata::generic::SOA(Name::ROOT_NAME(),
- Name::ROOT_NAME(),
- 0, 0, 0, 0, 0));
- rrsets_.push_back(soa_);
-
- RRsetPtr rrset(new RRset(origin_, RRClass::IN(), RRType::NS(),
- RRTTL(3600)));
- rrset->addRdata(rdata::generic::NS(Name::ROOT_NAME()));
- rrsets_.push_back(rrset);
-
- if (include_a) {
- // Dummy A rrset. This is used for checking zone data
- // after reload.
- rrset.reset(new RRset(Name("tstzonedata").concatenate(origin_),
- RRClass::IN(), RRType::A(),
- RRTTL(3600)));
- rrset->addRdata(rdata::in::A("192.0.2.1"));
- rrsets_.push_back(rrset);
- }
-
- rrsets_.push_back(ConstRRsetPtr());
-
- it_ = rrsets_.begin();
- }
- virtual isc::dns::ConstRRsetPtr getNextRRset() {
- ConstRRsetPtr result = *it_;
- ++it_;
- return (result);
- }
- virtual isc::dns::ConstRRsetPtr getSOA() const {
- return (soa_);
- }
- private:
- const Name origin_;
- const RRsetPtr soa_;
- std::vector<ConstRRsetPtr> rrsets_;
- std::vector<ConstRRsetPtr>::const_iterator it_;
- };
- // Constructor from a list of zones.
- MockDataSourceClient(const char* zone_names[]) :
- have_a_(true), use_baditerator_(true)
- {
- for (const char** zone(zone_names); *zone; ++zone) {
- zones.insert(Name(*zone));
- }
- }
- // Constructor from configuration. The list of zones will be empty, but
- // it will keep the configuration inside for further inspection.
- MockDataSourceClient(const string& type,
- const ConstElementPtr& configuration) :
- type_(type),
- configuration_(configuration),
- have_a_(true), use_baditerator_(true)
- {
- EXPECT_NE("MasterFiles", type) << "MasterFiles is a special case "
- "and it never should be created as a data source client";
- if (configuration_->getType() == Element::list) {
- for (size_t i(0); i < configuration_->size(); ++i) {
- zones.insert(Name(configuration_->get(i)->stringValue()));
- }
- }
- }
- virtual FindResult findZone(const Name& name) const {
- if (zones.empty()) {
- return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
- }
- set<Name>::const_iterator it(zones.upper_bound(name));
- if (it == zones.begin()) {
- return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
- }
- --it;
- NameComparisonResult compar(it->compare(name));
- const ZoneFinderPtr finder(new Finder(*it));
- switch (compar.getRelation()) {
- case NameComparisonResult::EQUAL:
- return (FindResult(result::SUCCESS, finder));
- case NameComparisonResult::SUPERDOMAIN:
- return (FindResult(result::PARTIALMATCH, finder));
- default:
- return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
- }
- }
- // These methods are not used. They just need to be there to have
- // complete vtable.
- virtual ZoneUpdaterPtr getUpdater(const Name&, bool, bool) const {
- isc_throw(isc::NotImplemented, "Not implemented");
- }
- virtual pair<ZoneJournalReader::Result, ZoneJournalReaderPtr>
- getJournalReader(const Name&, uint32_t, uint32_t) const
- {
- isc_throw(isc::NotImplemented, "Not implemented");
- }
- virtual ZoneIteratorPtr getIterator(const Name& name, bool) const {
- if (use_baditerator_ && name == Name("noiter.org")) {
- isc_throw(isc::NotImplemented, "Asked not to be implemented");
- } else if (use_baditerator_ && name == Name("null.org")) {
- return (ZoneIteratorPtr());
- } else {
- FindResult result(findZone(name));
- if (result.code == isc::datasrc::result::SUCCESS) {
- return (ZoneIteratorPtr(new Iterator(name, have_a_)));
- } else {
- isc_throw(DataSourceError, "No such zone");
- }
- }
- }
- void disableA() { have_a_ = false; }
- void disableBadIterator() { use_baditerator_ = false; }
- const string type_;
- const ConstElementPtr configuration_;
-private:
- set<Name> zones;
- bool have_a_; // control the iterator behavior whether to include A record
- bool use_baditerator_; // whether to use bogus zone iterators for tests
-};
-
-
// The test version is the same as the normal version. We, however, add
// some methods to dig directly in the internals, for the tests.
class TestedList : public ConfigurableClientList {
@@ -219,6 +67,9 @@ public:
if (type == "error") {
isc_throw(DataSourceError, "The error data source type");
}
+ if (type == "MasterFiles") {
+ return (DataSourcePair(0, DataSourceClientContainerPtr()));
+ }
shared_ptr<MockDataSourceClient>
ds(new MockDataSourceClient(type, configuration));
// Make sure it is deleted when the test list is deleted.
@@ -269,8 +120,7 @@ public:
" \"params\": [\"example.org\", \"example.com\", "
" \"noiter.org\", \"null.org\"]"
"}]")),
- config_(Element::fromJSON("{}")),
- ztable_segment_(ZoneTableSegment::create(*config_, rrclass_))
+ ztable_segment_(ZoneTableSegment::create(rrclass_, "local"))
{
for (size_t i(0); i < ds_count; ++ i) {
shared_ptr<MockDataSourceClient>
@@ -278,34 +128,57 @@ public:
ds_.push_back(ds);
ds_info_.push_back(ConfigurableClientList::DataSourceInfo(
ds.get(), DataSourceClientContainerPtr(),
- false, rrclass_, ztable_segment_));
+ boost::shared_ptr<internal::CacheConfig>(),
+ rrclass_, ""));
}
}
// Install a "fake" cached zone using a temporary underlying data source
- // client.
- void prepareCache(size_t index, const Name& zone) {
- // Prepare the temporary data source client
- const char* zones[2];
- const std::string zonename_txt = zone.toText();
- zones[0] = zonename_txt.c_str();
- zones[1] = NULL;
- MockDataSourceClient mock_client(zones);
+ // client. If 'enabled' is set to false, emulate a disabled cache, in
+ // which case there will be no data in memory.
+ void prepareCache(size_t index, const Name& zone, bool enabled = true) {
+ ConfigurableClientList::DataSourceInfo& dsrc_info =
+ list_->getDataSources()[index];
+ MockDataSourceClient* mock_client =
+ static_cast<MockDataSourceClient*>(dsrc_info.data_src_client_);
+
// Disable some default features of the mock to distinguish the
// temporary case from normal case.
- mock_client.disableA();
- mock_client.disableBadIterator();
-
- // Create cache from the temporary data source, and push it to the
- // client list.
- const shared_ptr<InMemoryClient> cache(
- new InMemoryClient(ztable_segment_, rrclass_));
- cache->load(zone, *mock_client.getIterator(zone, false));
+ mock_client->disableA();
+ mock_client->disableBadIterator();
+
+ // Build new cache config to load the specified zone, and replace
+ // the data source info with the new config.
+ ConstElementPtr cache_conf_elem =
+ Element::fromJSON("{\"type\": \"mock\","
+ " \"cache-enable\": " +
+ string(enabled ? "true," : "false,") +
+ " \"cache-zones\": "
+ " [\"" + zone.toText() + "\"]}");
+ boost::shared_ptr<internal::CacheConfig> cache_conf(
+ new internal::CacheConfig("mock", mock_client, *cache_conf_elem,
+ true));
+ dsrc_info = ConfigurableClientList::DataSourceInfo(
+ dsrc_info.data_src_client_,
+ dsrc_info.container_,
+ cache_conf, rrclass_, dsrc_info.name_);
+
+ // Load the data into the zone table.
+ if (enabled) {
+ boost::scoped_ptr<memory::ZoneWriter> writer(
+ new memory::ZoneWriter(
+ *dsrc_info.ztable_segment_,
+ cache_conf->getLoadAction(rrclass_, zone),
+ zone, rrclass_));
+ writer->load();
+ writer->install();
+ writer->cleanup(); // not absolutely necessary, but just in case
+ }
- ConfigurableClientList::DataSourceInfo& dsrc_info =
- list_->getDataSources()[index];
- dsrc_info.cache_ = cache;
- dsrc_info.ztable_segment_ = ztable_segment_;
+ // On completion of load revert to the previous state of underlying
+ // data source.
+ mock_client->enableA();
+ mock_client->enableBadIterator();
}
// Check the positive result is as we expect it.
void positiveResult(const ClientList::FindResult& result,
@@ -377,12 +250,15 @@ public:
EXPECT_EQ(cache, list_->getDataSources()[index].cache_ !=
shared_ptr<InMemoryClient>());
}
+ ConfigurableClientList::CacheStatus doReload(
+ const Name& origin, const string& datasrc_name = "");
+
const RRClass rrclass_;
shared_ptr<TestedList> list_;
const ClientList::FindResult negative_result_;
vector<shared_ptr<MockDataSourceClient> > ds_;
vector<ConfigurableClientList::DataSourceInfo> ds_info_;
- const ConstElementPtr config_elem_, config_elem_zones_, config_;
+ const ConstElementPtr config_elem_, config_elem_zones_;
shared_ptr<ZoneTableSegment> ztable_segment_;
};
@@ -512,12 +388,12 @@ TEST_F(ListTest, configureMulti) {
const ConstElementPtr elem(Element::fromJSON("["
"{"
" \"type\": \"type1\","
- " \"cache\": \"off\","
+ " \"cache-enable\": false,"
" \"params\": {}"
"},"
"{"
" \"type\": \"type2\","
- " \"cache\": \"off\","
+ " \"cache-enable\": false,"
" \"params\": {}"
"}]"
));
@@ -546,7 +422,7 @@ TEST_F(ListTest, configureParams) {
ConstElementPtr elem(Element::fromJSON(string("["
"{"
" \"type\": \"t\","
- " \"cache\": \"off\","
+ " \"cache-enable\": false,"
" \"params\": ") + *param +
"}]"));
list_->configure(elem, true);
@@ -555,6 +431,33 @@ TEST_F(ListTest, configureParams) {
}
}
+TEST_F(ListTest, status) {
+ EXPECT_TRUE(list_->getStatus().empty());
+ const ConstElementPtr elem(Element::fromJSON("["
+ "{"
+ " \"type\": \"type1\","
+ " \"cache-enable\": false,"
+ " \"params\": {}"
+ "},"
+ "{"
+ " \"type\": \"type2\","
+ " \"cache-enable\": true,"
+ " \"cache-zones\": [],"
+ " \"name\": \"Test name\","
+ " \"params\": {}"
+ "}]"
+ ));
+ list_->configure(elem, true);
+ const vector<DataSourceStatus> statuses(list_->getStatus());
+ ASSERT_EQ(2, statuses.size());
+ EXPECT_EQ("type1", statuses[0].getName());
+ EXPECT_EQ(SEGMENT_UNUSED, statuses[0].getSegmentState());
+ EXPECT_THROW(statuses[0].getSegmentType(), isc::InvalidOperation);
+ EXPECT_EQ("Test name", statuses[1].getName());
+ EXPECT_EQ(SEGMENT_INUSE, statuses[1].getSegmentState());
+ EXPECT_EQ("local", statuses[1].getSegmentType());
+}
+
TEST_F(ListTest, wrongConfig) {
const char* configs[] = {
// A lot of stuff missing from there
@@ -768,17 +671,20 @@ TEST_F(ListTest, cacheZones) {
TEST_F(ListTest, badCache) {
list_->configure(config_elem_, true);
checkDS(0, "test_type", "{}", false);
- // First, the zone is not in the data source
+ // First, the zone is not in the data source. configure() should still
+ // succeed, and the existence zone should be cached.
const ConstElementPtr elem1(Element::fromJSON("["
"{"
- " \"type\": \"type1\","
+ " \"type\": \"test_type\","
" \"cache-enable\": true,"
- " \"cache-zones\": [\"example.org\"],"
- " \"params\": []"
+ " \"cache-zones\": [\"example.org\", \"example.com\"],"
+ " \"params\": [\"example.org\"]"
"}]"));
- EXPECT_THROW(list_->configure(elem1, true),
- ConfigurableClientList::ConfigurationError);
- checkDS(0, "test_type", "{}", false);
+ list_->configure(elem1, true); // shouldn't cause disruption
+ checkDS(0, "test_type", "[\"example.org\"]", true);
+ const shared_ptr<InMemoryClient> cache(list_->getDataSources()[0].cache_);
+ EXPECT_EQ(1, cache->getZoneCount());
+ EXPECT_EQ(result::SUCCESS, cache->findZone(Name("example.org")).code);
// Now, the zone doesn't give an iterator
const ConstElementPtr elem2(Element::fromJSON("["
"{"
@@ -788,7 +694,7 @@ TEST_F(ListTest, badCache) {
" \"params\": [\"noiter.org\"]"
"}]"));
EXPECT_THROW(list_->configure(elem2, true), isc::NotImplemented);
- checkDS(0, "test_type", "{}", false);
+ checkDS(0, "test_type", "[\"example.org\"]", true);
// Now, the zone returns NULL iterator
const ConstElementPtr elem3(Element::fromJSON("["
"{"
@@ -798,7 +704,7 @@ TEST_F(ListTest, badCache) {
" \"params\": [\"null.org\"]"
"}]"));
EXPECT_THROW(list_->configure(elem3, true), isc::Unexpected);
- checkDS(0, "test_type", "{}", false);
+ checkDS(0, "test_type", "[\"example.org\"]", true);
// The autodetection of zones is not enabled
const ConstElementPtr elem4(Element::fromJSON("["
"{"
@@ -807,7 +713,35 @@ TEST_F(ListTest, badCache) {
" \"params\": [\"example.org\"]"
"}]"));
EXPECT_THROW(list_->configure(elem4, true), isc::NotImplemented);
- checkDS(0, "test_type", "{}", false);
+ checkDS(0, "test_type", "[\"example.org\"]", true);
+}
+
+// This test relies on the property of mapped type of cache.
+TEST_F(ListTest,
+#ifdef USE_SHARED_MEMORY
+ cacheInNonWritableSegment
+#else
+ DISABLED_cacheInNonWritableSegment
+#endif
+ )
+{
+ // Initializing data source with non writable zone table memory segment
+ // is possible. Loading is just postponed
+ const ConstElementPtr elem(Element::fromJSON("["
+ "{"
+ " \"type\": \"test_type\","
+ " \"cache-enable\": true,"
+ " \"cache-type\": \"mapped\","
+ " \"cache-zones\": [\"example.org\"],"
+ " \"params\": [\"example.org\"]"
+ "}]"));
+ list_->configure(elem, true); // no disruption
+ checkDS(0, "test_type", "[\"example.org\"]", true);
+ const shared_ptr<InMemoryClient> cache(list_->getDataSources()[0].cache_);
+
+ // Likewise, reload attempt will fail.
+ EXPECT_EQ(ConfigurableClientList::CACHE_NOT_WRITABLE,
+ doReload(Name("example.org")));
}
TEST_F(ListTest, masterFiles) {
@@ -834,6 +768,54 @@ TEST_F(ListTest, masterFiles) {
EXPECT_EQ(0, list_->getDataSources().size());
}
+// Test the names are set correctly and collission is detected.
+TEST_F(ListTest, names) {
+ // Explicit name
+ const ConstElementPtr elem1(Element::fromJSON("["
+ "{"
+ " \"type\": \"MasterFiles\","
+ " \"cache-enable\": true,"
+ " \"params\": {"
+ " \".\": \"" TEST_DATA_DIR "/root.zone\""
+ " },"
+ " \"name\": \"Whatever\""
+ "}]"));
+ list_->configure(elem1, true);
+ EXPECT_EQ("Whatever", list_->getDataSources()[0].name_);
+
+ // Default name
+ const ConstElementPtr elem2(Element::fromJSON("["
+ "{"
+ " \"type\": \"MasterFiles\","
+ " \"cache-enable\": true,"
+ " \"params\": {"
+ " \".\": \"" TEST_DATA_DIR "/root.zone\""
+ " }"
+ "}]"));
+ list_->configure(elem2, true);
+ EXPECT_EQ("MasterFiles", list_->getDataSources()[0].name_);
+
+ // Collission
+ const ConstElementPtr elem3(Element::fromJSON("["
+ "{"
+ " \"type\": \"MasterFiles\","
+ " \"cache-enable\": true,"
+ " \"params\": {"
+ " \".\": \"" TEST_DATA_DIR "/root.zone\""
+ " }"
+ "},"
+ "{"
+ " \"type\": \"MasterFiles\","
+ " \"cache-enable\": true,"
+ " \"params\": {"
+ " \".\": \"" TEST_DATA_DIR "/root.zone\""
+ " },"
+ " \"name\": \"MasterFiles\""
+ "}]"));
+ EXPECT_THROW(list_->configure(elem3, true),
+ ConfigurableClientList::ConfigurationError);
+}
+
TEST_F(ListTest, BadMasterFile) {
// Configuration should succeed, and the good zones in the list
// below should be loaded. No bad zones should be loaded.
@@ -881,31 +863,10 @@ TEST_F(ListTest, BadMasterFile) {
true);
}
-// This allows us to test two versions of the reloading code
-// (One by calling reload(), one by obtaining a ZoneWriter and
-// playing with that). Once we deprecate reload(), we should revert this
-// change and not use typed tests any more.
-template<class UpdateType>
-class ReloadTest : public ListTest {
-public:
- ConfigurableClientList::ReloadResult doReload(const Name& origin);
-};
-
-// Version with calling reload()
-class ReloadUpdateType {};
-template<>
-ConfigurableClientList::ReloadResult
-ReloadTest<ReloadUpdateType>::doReload(const Name& origin) {
- return (list_->reload(origin));
-};
-
-// Version with the ZoneWriter
-class WriterUpdateType {};
-template<>
-ConfigurableClientList::ReloadResult
-ReloadTest<WriterUpdateType>::doReload(const Name& origin) {
+ConfigurableClientList::CacheStatus
+ListTest::doReload(const Name& origin, const string& datasrc_name) {
ConfigurableClientList::ZoneWriterPair
- result(list_->getCachedZoneWriter(origin));
+ result(list_->getCachedZoneWriter(origin, 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.
@@ -918,142 +879,172 @@ ReloadTest<WriterUpdateType>::doReload(const Name& origin) {
"but the writer is NULL";
}
} else {
- EXPECT_EQ(static_cast<memory::ZoneWriter*>(NULL),
- result.second.get());
+ EXPECT_EQ(static_cast<memory::ZoneWriter*>(NULL), result.second.get());
}
return (result.first);
}
-// Typedefs for the GTEST guts to make it work
-typedef ::testing::Types<ReloadUpdateType, WriterUpdateType> UpdateTypes;
-TYPED_TEST_CASE(ReloadTest, UpdateTypes);
-
// Test we can reload a zone
-TYPED_TEST(ReloadTest, reloadSuccess) {
- this->list_->configure(this->config_elem_zones_, true);
+TEST_F(ListTest, reloadSuccess) {
+ list_->configure(config_elem_zones_, true);
const Name name("example.org");
- this->prepareCache(0, name);
+ prepareCache(0, name);
// The cache currently contains a tweaked version of zone, which
// doesn't have "tstzonedata" A record. So the lookup should result
// in NXDOMAIN.
EXPECT_EQ(ZoneFinder::NXDOMAIN,
- this->list_->find(name).finder_->
+ list_->find(name).finder_->
find(Name("tstzonedata").concatenate(name),
RRType::A())->code);
// Now reload the full zone. It should be there now.
- EXPECT_EQ(ConfigurableClientList::ZONE_SUCCESS, this->doReload(name));
+ EXPECT_EQ(ConfigurableClientList::ZONE_SUCCESS, doReload(name));
EXPECT_EQ(ZoneFinder::SUCCESS,
- this->list_->find(name).finder_->
+ list_->find(name).finder_->
find(Name("tstzonedata").concatenate(name),
RRType::A())->code);
}
// The cache is not enabled. The load should be rejected.
-TYPED_TEST(ReloadTest, reloadNotEnabled) {
- this->list_->configure(this->config_elem_zones_, false);
+TEST_F(ListTest, 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.
- this->prepareCache(0, name);
+ prepareCache(0, name);
// See the reloadSuccess test. This should result in NXDOMAIN.
EXPECT_EQ(ZoneFinder::NXDOMAIN,
- this->list_->find(name).finder_->
+ list_->find(name).finder_->
find(Name("tstzonedata").concatenate(name),
RRType::A())->code);
// Now reload. It should reject it.
- EXPECT_EQ(ConfigurableClientList::CACHE_DISABLED, this->doReload(name));
+ EXPECT_EQ(ConfigurableClientList::CACHE_DISABLED, doReload(name));
// Nothing changed here
EXPECT_EQ(ZoneFinder::NXDOMAIN,
- this->list_->find(name).finder_->
+ list_->find(name).finder_->
find(Name("tstzonedata").concatenate(name),
RRType::A())->code);
}
+// Similar to the previous case, but the cache is disabled in config.
+TEST_F(ListTest, reloadNotEnabled) {
+ list_->configure(config_elem_zones_, true);
+ const Name name("example.org");
+ // We put the cache, actually disabling it.
+ prepareCache(0, name, false);
+ // In this case we cannot really look up due to the limitation of
+ // the mock implementation. We only check reload fails.
+ EXPECT_EQ(ConfigurableClientList::ZONE_NOT_CACHED, doReload(name));
+}
+
// Test several cases when the zone does not exist
-TYPED_TEST(ReloadTest, reloadNoSuchZone) {
- this->list_->configure(this->config_elem_zones_, true);
+TEST_F(ListTest, reloadNoSuchZone) {
+ list_->configure(config_elem_zones_, true);
const Name name("example.org");
// We put the cache in even when not enabled. This won't confuse the
// reload method, as that one looks at the real state of things, not
// at the configuration.
- this->prepareCache(0, Name("example.com"));
+ prepareCache(0, Name("example.com"));
// Not in the data sources
EXPECT_EQ(ConfigurableClientList::ZONE_NOT_FOUND,
- this->doReload(Name("exmaple.cz")));
- // Not cached
- EXPECT_EQ(ConfigurableClientList::ZONE_NOT_FOUND, this->doReload(name));
+ doReload(Name("exmaple.cz")));
+ // If it's not configured to be cached, it won't be reloaded.
+ EXPECT_EQ(ConfigurableClientList::ZONE_NOT_CACHED, doReload(name));
// Partial match
EXPECT_EQ(ConfigurableClientList::ZONE_NOT_FOUND,
- this->doReload(Name("sub.example.com")));
+ doReload(Name("sub.example.com")));
// Nothing changed here - these zones don't exist
EXPECT_EQ(static_cast<isc::datasrc::DataSourceClient*>(NULL),
- this->list_->find(name).dsrc_client_);
+ list_->find(name).dsrc_client_);
EXPECT_EQ(static_cast<isc::datasrc::DataSourceClient*>(NULL),
- this->list_->find(Name("example.cz")).dsrc_client_);
+ list_->find(Name("example.cz")).dsrc_client_);
EXPECT_EQ(static_cast<isc::datasrc::DataSourceClient*>(NULL),
- this->list_->find(Name("sub.example.com"), true).dsrc_client_);
+ list_->find(Name("sub.example.com"), true).dsrc_client_);
// Not reloaded, so A record shouldn't be visible yet.
EXPECT_EQ(ZoneFinder::NXDOMAIN,
- this->list_->find(Name("example.com")).finder_->
+ list_->find(Name("example.com")).finder_->
find(Name("tstzonedata.example.com"),
RRType::A())->code);
}
-// Check we gracefuly throw an exception when a zone disappeared in
-// the underlying data source when we want to reload it
-TYPED_TEST(ReloadTest, reloadZoneGone) {
- this->list_->configure(this->config_elem_, true);
+// Check we gracefully reject reloading (i.e. no exception) when a zone
+// disappeared in the underlying data source when we want to reload it
+TEST_F(ListTest, reloadZoneGone) {
+ list_->configure(config_elem_zones_, true);
const Name name("example.org");
- // We put in a cache for non-existant zone. This emulates being loaded
+ // We put in a cache for non-existent zone. This emulates being loaded
// and then the zone disappearing. We prefill the cache, so we can check
// it.
- this->prepareCache(0, name);
+ prepareCache(0, name);
// The (cached) zone contains zone's SOA
EXPECT_EQ(ZoneFinder::SUCCESS,
- this->list_->find(name).finder_->find(name,
- RRType::SOA())->code);
- // The zone is not there, so abort the reload.
- EXPECT_THROW(this->doReload(name), DataSourceError);
+ list_->find(name).finder_->find(name, RRType::SOA())->code);
+ // Remove the zone from the data source.
+ static_cast<MockDataSourceClient*>(
+ list_->getDataSources()[0].data_src_client_)->eraseZone(name);
+
+ // The zone is not there, so reload doesn't take place.
+ EXPECT_EQ(ConfigurableClientList::ZONE_NOT_FOUND, doReload(name));
// The (cached) zone is not hurt.
EXPECT_EQ(ZoneFinder::SUCCESS,
- this->list_->find(name).finder_->find(name,
- RRType::SOA())->code);
+ list_->find(name).finder_->find(name, RRType::SOA())->code);
+}
+
+TEST_F(ListTest, reloadNewZone) {
+ // Test the case where a zone to be cached originally doesn't exist
+ // in the underlying data source and is added later. reload() will
+ // succeed once it's available in the data source.
+ const ConstElementPtr elem(Element::fromJSON("["
+ "{"
+ " \"type\": \"test_type\","
+ " \"cache-enable\": true,"
+ " \"cache-zones\": [\"example.org\", \"example.com\"],"
+ " \"params\": [\"example.org\"]"
+ "}]"));
+ list_->configure(elem, true);
+ checkDS(0, "test_type", "[\"example.org\"]", true); // no example.com
+
+ // We can't reload it either
+ EXPECT_EQ(ConfigurableClientList::ZONE_NOT_FOUND,
+ doReload(Name("example.com")));
+
+ // If we add the zone, we can now reload it
+ EXPECT_TRUE(static_cast<MockDataSourceClient*>(
+ list_->getDataSources()[0].data_src_client_)->
+ insertZone(Name("example.com")));
+ EXPECT_EQ(ConfigurableClientList::ZONE_SUCCESS,
+ doReload(Name("example.com")));
}
// The underlying data source throws. Check we don't modify the state.
-TYPED_TEST(ReloadTest, reloadZoneThrow) {
- this->list_->configure(this->config_elem_zones_, true);
+TEST_F(ListTest, reloadZoneThrow) {
+ list_->configure(config_elem_zones_, true);
const Name name("noiter.org");
- this->prepareCache(0, name);
+ prepareCache(0, name);
// The zone contains stuff now
EXPECT_EQ(ZoneFinder::SUCCESS,
- this->list_->find(name).finder_->find(name,
- RRType::SOA())->code);
+ list_->find(name).finder_->find(name, RRType::SOA())->code);
// The iterator throws, so abort the reload.
- EXPECT_THROW(this->doReload(name), isc::NotImplemented);
+ EXPECT_THROW(doReload(name), isc::NotImplemented);
// The zone is not hurt.
EXPECT_EQ(ZoneFinder::SUCCESS,
- this->list_->find(name).finder_->find(name,
- RRType::SOA())->code);
+ list_->find(name).finder_->find(name, RRType::SOA())->code);
}
-TYPED_TEST(ReloadTest, reloadNullIterator) {
- this->list_->configure(this->config_elem_zones_, true);
+TEST_F(ListTest, reloadNullIterator) {
+ list_->configure(config_elem_zones_, true);
const Name name("null.org");
- this->prepareCache(0, name);
+ prepareCache(0, name);
// The zone contains stuff now
EXPECT_EQ(ZoneFinder::SUCCESS,
- this->list_->find(name).finder_->find(name,
- RRType::SOA())->code);
+ list_->find(name).finder_->find(name, RRType::SOA())->code);
// The iterator throws, so abort the reload.
- EXPECT_THROW(this->doReload(name), isc::Unexpected);
+ EXPECT_THROW(doReload(name), isc::Unexpected);
// The zone is not hurt.
EXPECT_EQ(ZoneFinder::SUCCESS,
- this->list_->find(name).finder_->find(name,
- RRType::SOA())->code);
+ list_->find(name).finder_->find(name, RRType::SOA())->code);
}
// Test we can reload the master files too (special-cased)
-TYPED_TEST(ReloadTest, reloadMasterFile) {
+TEST_F(ListTest, reloadMasterFile) {
const char* const install_cmd = INSTALL_PROG " -c " TEST_DATA_DIR
"/root.zone " TEST_DATA_BUILDDIR "/root.zone.copied";
if (system(install_cmd) != 0) {
@@ -1071,21 +1062,80 @@ TYPED_TEST(ReloadTest, reloadMasterFile) {
" \".\": \"" TEST_DATA_BUILDDIR "/root.zone.copied\""
" }"
"}]"));
- this->list_->configure(elem, true);
+ list_->configure(elem, true);
// Add a record that is not in the zone
EXPECT_EQ(ZoneFinder::NXDOMAIN,
- this->list_->find(Name(".")).finder_->find(Name("nosuchdomain"),
- RRType::TXT())->code);
+ list_->find(Name(".")).finder_->find(Name("nosuchdomain"),
+ RRType::TXT())->code);
ofstream f;
f.open(TEST_DATA_BUILDDIR "/root.zone.copied", ios::out | ios::app);
f << "nosuchdomain.\t\t3600\tIN\tTXT\ttest" << std::endl;
f.close();
// Do the reload.
- EXPECT_EQ(ConfigurableClientList::ZONE_SUCCESS, this->doReload(Name(".")));
+ EXPECT_EQ(ConfigurableClientList::ZONE_SUCCESS, doReload(Name(".")));
// It is here now.
EXPECT_EQ(ZoneFinder::SUCCESS,
- this->list_->find(Name(".")).finder_->find(Name("nosuchdomain"),
- RRType::TXT())->code);
+ list_->find(Name(".")).finder_->find(Name("nosuchdomain"),
+ RRType::TXT())->code);
+}
+
+TEST_F(ListTest, reloadByDataSourceName) {
+ // We use three data sources (and their clients). 2nd and 3rd have
+ // the same name of the zones.
+ const ConstElementPtr config_elem = Element::fromJSON(
+ "[{\"type\": \"test_type1\", \"params\": [\"example.org\"]},"
+ " {\"type\": \"test_type2\", \"params\": [\"example.com\"]},"
+ " {\"type\": \"test_type3\", \"params\": [\"example.com\"]}]");
+ list_->configure(config_elem, true);
+ // Prepare in-memory cache for the 1st and 2nd data sources.
+ prepareCache(0, Name("example.org"));
+ prepareCache(1, Name("example.com"));
+
+ // Normal case: both zone name and data source name matches.
+ // See the reloadSuccess test about the NXDOMAIN/SUCCESS checks.
+ EXPECT_EQ(ZoneFinder::NXDOMAIN,
+ list_->find(Name("tstzonedata.example.com")).finder_->
+ find(Name("tstzonedata.example.com"), RRType::A())->code);
+ EXPECT_EQ(ConfigurableClientList::ZONE_SUCCESS,
+ doReload(Name("example.com"), "test_type2"));
+ EXPECT_EQ(ZoneFinder::SUCCESS,
+ list_->find(Name("tstzonedata.example.com")).finder_->
+ find(Name("tstzonedata.example.com"), RRType::A())->code);
+
+ // The specified zone exists in the first entry of the list, but a
+ // different data source name is specified (in which the specified zone
+ // doesn't exist), so reloading should fail, and the cache status of the
+ // first data source shouldn't change.
+ EXPECT_EQ(ZoneFinder::NXDOMAIN,
+ list_->find(Name("tstzonedata.example.org")).finder_->
+ find(Name("tstzonedata.example.org"), RRType::A())->code);
+ EXPECT_EQ(ConfigurableClientList::ZONE_NOT_FOUND,
+ doReload(Name("example.org"), "test_type2"));
+ EXPECT_EQ(ZoneFinder::NXDOMAIN,
+ list_->find(Name("tstzonedata.example.org")).finder_->
+ find(Name("tstzonedata.example.org"), RRType::A())->code);
+
+ // Likewise, if a specific data source is given, normal name matching
+ // isn't suppressed and the 3rd data source will be used. There cache
+ // is disabled, so reload should fail due to "not cached".
+ EXPECT_EQ(ConfigurableClientList::ZONE_NOT_CACHED,
+ doReload(Name("example.com"), "test_type3"));
+
+ // specified name of data source doesn't exist.
+ EXPECT_EQ(ConfigurableClientList::DATASRC_NOT_FOUND,
+ doReload(Name("example.org"), "test_type4"));
+}
+
+// Check the status holds data
+TEST(DataSourceStatus, status) {
+ const DataSourceStatus status("Test", SEGMENT_INUSE, "local");
+ EXPECT_EQ("Test", status.getName());
+ EXPECT_EQ(SEGMENT_INUSE, status.getSegmentState());
+ EXPECT_EQ("local", status.getSegmentType());
+ const DataSourceStatus status_unused("Unused", SEGMENT_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 83ff004..79cc2c3 100644
--- a/src/lib/datasrc/tests/database_unittest.cc
+++ b/src/lib/datasrc/tests/database_unittest.cc
@@ -25,7 +25,7 @@
#include <datasrc/database.h>
#include <datasrc/zone.h>
#include <datasrc/zone_finder.h>
-#include <datasrc/data_source.h>
+#include <datasrc/exceptions.h>
#include <datasrc/zone_iterator.h>
#include <testutils/dnsmessage_test.h>
@@ -167,7 +167,8 @@ public:
virtual void addNSEC3RecordToZone(const string (&)[ADD_NSEC3_COLUMN_COUNT])
{}
virtual void deleteRecordInZone(const string (&)[DEL_PARAM_COUNT]) {}
- virtual void deleteNSEC3RecordInZone(const string (&)[DEL_PARAM_COUNT]) {}
+ virtual void deleteNSEC3RecordInZone(const string
+ (&)[DEL_NSEC3_PARAM_COUNT]) {}
virtual void addRecordDiff(int, uint32_t, DiffOperation,
const std::string (&)[DIFF_PARAM_COUNT]) {}
@@ -634,9 +635,8 @@ private:
};
// Common subroutine for deleteRecordinZone and deleteNSEC3RecordInZone.
- void deleteRecord(Domains& domains,
- const string (¶ms)[DEL_PARAM_COUNT])
- {
+ template<size_t param_count>
+ void deleteRecord(Domains& domains, const string (¶ms)[param_count]) {
vector<vector<string> >& records =
domains[params[DatabaseAccessor::DEL_NAME]];
records.erase(remove_if(records.begin(), records.end(),
@@ -655,7 +655,7 @@ public:
}
virtual void deleteNSEC3RecordInZone(
- const string (¶ms)[DEL_PARAM_COUNT])
+ const string (¶ms)[DEL_NSEC3_PARAM_COUNT])
{
deleteRecord(*update_nsec3_namespace_, params);
}
@@ -1417,7 +1417,7 @@ TEST(GenericDatabaseClientTest, noAccessorException) {
// If the zone doesn't exist, exception is thrown
TEST_P(DatabaseClientTest, noZoneIterator) {
- EXPECT_THROW(client_->getIterator(Name("example.com")), DataSourceError);
+ EXPECT_THROW(client_->getIterator(Name("example.com")), NoSuchZone);
}
// If the zone doesn't exist and iteration is not implemented, it still throws
@@ -1427,7 +1427,7 @@ TEST(GenericDatabaseClientTest, noZoneNotImplementedIterator) {
boost::shared_ptr<DatabaseAccessor>(
new NopAccessor())).getIterator(
Name("example.com")),
- DataSourceError);
+ NoSuchZone);
}
TEST(GenericDatabaseClientTest, notImplementedIterator) {
@@ -2566,7 +2566,7 @@ TEST_P(DatabaseClientTest, wildcard) {
doFindTest(*finder, isc::dns::Name("a.*.wild.example.org"),
qtype_, qtype_, rrttl_, ZoneFinder::NXDOMAIN,
expected_rdatas_, expected_sig_rdatas_);
- // These should be canceled, since it is below a domain which exitsts
+ // These should be canceled, since it is below a domain which exists
doFindTest(*finder, isc::dns::Name("nothing.here.wild.example.org"),
qtype_, qtype_, rrttl_, ZoneFinder::NXDOMAIN,
expected_rdatas_, expected_sig_rdatas_);
diff --git a/src/lib/datasrc/tests/factory_unittest.cc b/src/lib/datasrc/tests/factory_unittest.cc
index cc03ce3..5a01a27 100644
--- a/src/lib/datasrc/tests/factory_unittest.cc
+++ b/src/lib/datasrc/tests/factory_unittest.cc
@@ -16,7 +16,7 @@
#include <datasrc/datasrc_config.h>
#include <datasrc/factory.h>
-#include <datasrc/data_source.h>
+#include <datasrc/exceptions.h>
#include <datasrc/sqlite3_accessor.h>
#include <dns/rrclass.h>
@@ -28,8 +28,6 @@ using namespace isc::datasrc;
using namespace isc::data;
std::string SQLITE_DBFILE_EXAMPLE_ORG = TEST_DATA_DIR "/example.org.sqlite3";
-const std::string STATIC_DS_FILE = TEST_DATA_DIR "/static.zone";
-const std::string STATIC_BAD_DS_FILE = TEST_DATA_DIR "/static-bad.zone";
const std::string ROOT_ZONE_FILE = TEST_DATA_DIR "/root.zone";
namespace {
@@ -166,55 +164,5 @@ TEST(FactoryTest, badType) {
DataSourceError);
}
-// Check the static data source can be loaded.
-TEST(FactoryTest, staticDS) {
- // The only configuration is the file to load.
- const ConstElementPtr config(new StringElement(STATIC_DS_FILE));
- // Get the data source
- DataSourceClientContainer dsc("static", config);
- // And try getting something out to see if it really works.
- DataSourceClient::FindResult
- result(dsc.getInstance().findZone(isc::dns::Name("BIND")));
- ASSERT_EQ(result::SUCCESS, result.code);
- EXPECT_EQ(isc::dns::Name("BIND"), result.zone_finder->getOrigin());
- EXPECT_EQ(isc::dns::RRClass::CH(), result.zone_finder->getClass());
- const isc::dns::ConstRRsetPtr
- version(result.zone_finder->find(isc::dns::Name("VERSION.BIND"),
- isc::dns::RRType::TXT())->rrset);
- ASSERT_NE(isc::dns::ConstRRsetPtr(), version);
- EXPECT_EQ(isc::dns::Name("VERSION.BIND"), version->getName());
- EXPECT_EQ(isc::dns::RRClass::CH(), version->getClass());
- EXPECT_EQ(isc::dns::RRType::TXT(), version->getType());
-}
-
-// Check that file not containing BIND./CH is rejected
-TEST(FactoryTest, staticDSBadFile) {
- // The only configuration is the file to load.
- const ConstElementPtr config(new StringElement(STATIC_BAD_DS_FILE));
- // See it does not want the file
- EXPECT_THROW(DataSourceClientContainer("static", config), DataSourceError);
-}
-
-// Check that some bad configs are rejected
-TEST(FactoryTest, staticDSBadConfig) {
- const char* configs[] = {
- // The file does not exist
- "\"/does/not/exist\"",
- // Bad types
- "null",
- "42",
- "{}",
- "[]",
- "true",
- NULL
- };
- for (const char** config(configs); *config; ++config) {
- SCOPED_TRACE(*config);
- EXPECT_THROW(DataSourceClientContainer("static",
- Element::fromJSON(*config)),
- DataSourceError);
- }
-}
-
} // end anonymous namespace
diff --git a/src/lib/datasrc/tests/memory/Makefile.am b/src/lib/datasrc/tests/memory/Makefile.am
index e0b05b6..c3d6a40 100644
--- a/src/lib/datasrc/tests/memory/Makefile.am
+++ b/src/lib/datasrc/tests/memory/Makefile.am
@@ -6,7 +6,6 @@ AM_CPPFLAGS += $(BOOST_INCLUDES)
AM_CPPFLAGS += -DTEST_DATA_DIR=\"$(abs_srcdir)/testdata\"
AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_builddir)\"
-
AM_CXXFLAGS = $(B10_CXXFLAGS)
if USE_STATIC_LINK
@@ -23,6 +22,7 @@ if HAVE_GTEST
TESTS += run_unittests
run_unittests_SOURCES = run_unittests.cc
+run_unittests_SOURCES += zone_loader_util.h zone_loader_util.cc
run_unittests_SOURCES += rdata_serialization_unittest.cc
run_unittests_SOURCES += rdataset_unittest.cc
run_unittests_SOURCES += domaintree_unittest.cc
@@ -31,14 +31,19 @@ run_unittests_SOURCES += zone_table_unittest.cc
run_unittests_SOURCES += zone_data_unittest.cc
run_unittests_SOURCES += zone_finder_unittest.cc
run_unittests_SOURCES += ../../tests/faked_nsec3.h ../../tests/faked_nsec3.cc
-run_unittests_SOURCES += memory_segment_test.h
+run_unittests_SOURCES += memory_segment_mock.h
run_unittests_SOURCES += segment_object_holder_unittest.cc
run_unittests_SOURCES += memory_client_unittest.cc
run_unittests_SOURCES += rrset_collection_unittest.cc
run_unittests_SOURCES += zone_data_loader_unittest.cc
run_unittests_SOURCES += zone_data_updater_unittest.cc
-run_unittests_SOURCES += zone_table_segment_test.h
+run_unittests_SOURCES += zone_table_segment_mock.h
run_unittests_SOURCES += zone_table_segment_unittest.cc
+
+if USE_SHARED_MEMORY
+run_unittests_SOURCES += zone_table_segment_mapped_unittest.cc
+endif
+
run_unittests_SOURCES += zone_writer_unittest.cc
run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
diff --git a/src/lib/datasrc/tests/memory/memory_client_unittest.cc b/src/lib/datasrc/tests/memory/memory_client_unittest.cc
index 5984de5..5f52a33 100644
--- a/src/lib/datasrc/tests/memory/memory_client_unittest.cc
+++ b/src/lib/datasrc/tests/memory/memory_client_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 <datasrc/tests/memory/zone_loader_util.h>
+
#include <exceptions/exceptions.h>
#include <util/memory_segment_local.h>
@@ -26,7 +28,7 @@
#include <dns/masterload.h>
#include <datasrc/result.h>
-#include <datasrc/data_source.h>
+#include <datasrc/exceptions.h>
#include <datasrc/memory/zone_data.h>
#include <datasrc/memory/zone_table.h>
#include <datasrc/memory/zone_data_updater.h>
@@ -35,13 +37,14 @@
#include <testutils/dnsmessage_test.h>
-#include "memory_segment_test.h"
-#include "zone_table_segment_test.h"
+#include <datasrc/tests/memory/memory_segment_mock.h>
+#include <datasrc/tests/memory/zone_table_segment_mock.h>
#include <gtest/gtest.h>
#include <boost/lexical_cast.hpp>
#include <boost/shared_ptr.hpp>
+#include <boost/scoped_ptr.hpp>
#include <new> // for bad_alloc
@@ -53,6 +56,7 @@ using namespace isc::datasrc::memory;
using namespace isc::testutils;
using boost::shared_ptr;
using std::vector;
+using isc::datasrc::memory::test::loadZoneIntoTable;
namespace {
@@ -165,30 +169,26 @@ public:
class MemoryClientTest : public ::testing::Test {
protected:
MemoryClientTest() : zclass_(RRClass::IN()),
- ztable_segment_(new test::ZoneTableSegmentTest(
+ ztable_segment_(new test::ZoneTableSegmentMock(
zclass_, mem_sgmt_)),
client_(new InMemoryClient(ztable_segment_, zclass_))
{}
- ~MemoryClientTest() {
- delete client_;
- }
void TearDown() {
- delete client_;
- client_ = NULL;
+ client_.reset();
ztable_segment_.reset();
EXPECT_TRUE(mem_sgmt_.allMemoryDeallocated()); // catch any leak here.
}
const RRClass zclass_;
- test::MemorySegmentTest mem_sgmt_;
+ test::MemorySegmentMock mem_sgmt_;
shared_ptr<ZoneTableSegment> ztable_segment_;
- InMemoryClient* client_;
+ boost::scoped_ptr<InMemoryClient> client_;
};
TEST_F(MemoryClientTest, loadRRsetDoesntMatchOrigin) {
// Attempting to load example.org to example.com zone should result
// in an exception.
- EXPECT_THROW(client_->load(Name("example.com"),
- TEST_DATA_DIR "/example.org-empty.zone"),
+ EXPECT_THROW(loadZoneData(mem_sgmt_, zclass_, Name("example.com"),
+ TEST_DATA_DIR "/example.org-empty.zone"),
ZoneLoaderException);
}
@@ -196,8 +196,8 @@ TEST_F(MemoryClientTest, loadErrorsInParsingZoneMustNotLeak1) {
// Attempting to load broken example.org zone should result in an
// exception. This should not leak ZoneData and other such
// allocations.
- EXPECT_THROW(client_->load(Name("example.org"),
- TEST_DATA_DIR "/example.org-broken1.zone"),
+ EXPECT_THROW(loadZoneData(mem_sgmt_, zclass_, Name("example.org"),
+ TEST_DATA_DIR "/example.org-broken1.zone"),
ZoneLoaderException);
// Teardown checks for memory segment leaks
}
@@ -206,50 +206,45 @@ TEST_F(MemoryClientTest, loadErrorsInParsingZoneMustNotLeak2) {
// Attempting to load broken example.org zone should result in an
// exception. This should not leak ZoneData and other such
// allocations.
- EXPECT_THROW(client_->load(Name("example.org"),
- TEST_DATA_DIR "/example.org-broken2.zone"),
+ EXPECT_THROW(loadZoneData(mem_sgmt_, zclass_, Name("example.org"),
+ TEST_DATA_DIR "/example.org-broken2.zone"),
ZoneLoaderException);
// Teardown checks for memory segment leaks
}
TEST_F(MemoryClientTest, loadNonExistentZoneFile) {
- EXPECT_THROW(client_->load(Name("example.org"),
- TEST_DATA_DIR "/somerandomfilename"),
+ EXPECT_THROW(loadZoneData(mem_sgmt_, zclass_, Name("example.org"),
+ TEST_DATA_DIR "/somerandomfilename"),
ZoneLoaderException);
// Teardown checks for memory segment leaks
}
TEST_F(MemoryClientTest, loadEmptyZoneFileThrows) {
// When an empty zone file is loaded, the origin doesn't even have
- // an SOA RR. This condition should be avoided, and hence load()
- // should throw when an empty zone is loaded.
-
- EXPECT_EQ(0, client_->getZoneCount());
-
- EXPECT_THROW(client_->load(Name("."),
- TEST_DATA_DIR "/empty.zone"),
+ // an SOA RR. This condition should be avoided, and hence it results in
+ // an exception.
+ EXPECT_THROW(loadZoneData(mem_sgmt_, zclass_, Name("."),
+ TEST_DATA_DIR "/empty.zone"),
ZoneValidationError);
-
- EXPECT_EQ(0, client_->getZoneCount());
-
// Teardown checks for memory segment leaks
}
TEST_F(MemoryClientTest, load) {
// This is a simple load check for a "full" and correct zone that
// should not result in any exceptions.
- client_->load(Name("example.org"),
- TEST_DATA_DIR "/example.org.zone");
- const ZoneData* zone_data =
- client_->findZoneData(Name("example.org"));
+ ZoneData* zone_data = loadZoneData(mem_sgmt_, zclass_,
+ Name("example.org"),
+ TEST_DATA_DIR
+ "/example.org.zone");
ASSERT_NE(static_cast<const ZoneData*>(NULL), zone_data);
EXPECT_FALSE(zone_data->isSigned());
EXPECT_FALSE(zone_data->isNSEC3Signed());
+ ZoneData::destroy(mem_sgmt_, zone_data, zclass_);
}
TEST_F(MemoryClientTest, loadFromIterator) {
- client_->load(Name("example.org"),
- *MockIterator::makeIterator(rrset_data));
+ loadZoneIntoTable(*ztable_segment_, Name("example.org"), zclass_,
+ *MockIterator::makeIterator(rrset_data));
ZoneIteratorPtr iterator(client_->getIterator(Name("example.org")));
@@ -281,21 +276,23 @@ TEST_F(MemoryClientTest, loadFromIterator) {
// Iterating past the end should result in an exception
EXPECT_THROW(iterator->getNextRRset(), isc::Unexpected);
+ // NOTE: The rest of the tests is not actually about InMemoryClient
+
// Loading the zone with an iterator separating RRs of the same
// RRset should not fail. It is acceptable to load RRs of the same
// type again.
- client_->load(Name("example.org"),
- *MockIterator::makeIterator(
- rrset_data_separated));
+ loadZoneIntoTable(*ztable_segment_, Name("example.org"), zclass_,
+ *MockIterator::makeIterator(rrset_data_separated));
// Similar to the previous case, but with separated RRSIGs.
- client_->load(Name("example.org"),
- *MockIterator::makeIterator(
- rrset_data_sigseparated));
+ loadZoneIntoTable(*ztable_segment_, Name("example.org"), zclass_,
+ *MockIterator::makeIterator(rrset_data_sigseparated));
// Emulating bogus iterator implementation that passes empty RRSIGs.
- EXPECT_THROW(client_->load(Name("example.org"),
- *MockIterator::makeIterator(rrset_data, true)),
+ EXPECT_THROW(loadZoneIntoTable(*ztable_segment_, Name("example.org"),
+ zclass_,
+ *MockIterator::makeIterator(rrset_data,
+ true)),
isc::Unexpected);
}
@@ -308,24 +305,24 @@ TEST_F(MemoryClientTest, loadMemoryAllocationFailures) {
mem_sgmt_.setThrowCount(i);
EXPECT_THROW({
shared_ptr<ZoneTableSegment> ztable_segment(
- new test::ZoneTableSegmentTest(
+ new test::ZoneTableSegmentMock(
zclass_, mem_sgmt_));
// Include the InMemoryClient construction too here. Now,
// even allocations done from InMemoryClient constructor
- // fail (due to MemorySegmentTest throwing) and we check for
+ // fail (due to MemorySegmentMock throwing) and we check for
// leaks when this happens.
InMemoryClient client2(ztable_segment, zclass_);
- client2.load(Name("example.org"),
- TEST_DATA_DIR "/example.org.zone");
+ loadZoneIntoTable(*ztable_segment, Name("example.org"), zclass_,
+ TEST_DATA_DIR "/example.org.zone");
}, std::bad_alloc);
}
// Teardown checks for memory segment leaks
}
TEST_F(MemoryClientTest, loadNSEC3Signed) {
- client_->load(Name("example.org"),
- TEST_DATA_DIR "/example.org-nsec3-signed.zone");
+ loadZoneIntoTable(*ztable_segment_, Name("example.org"), zclass_,
+ TEST_DATA_DIR "/example.org-nsec3-signed.zone");
const ZoneData* zone_data =
client_->findZoneData(Name("example.org"));
ASSERT_NE(static_cast<const ZoneData*>(NULL), zone_data);
@@ -336,8 +333,8 @@ TEST_F(MemoryClientTest, loadNSEC3Signed) {
TEST_F(MemoryClientTest, loadNSEC3EmptySalt) {
// Load NSEC3 with empty ("-") salt. This should not throw or crash
// or anything.
- client_->load(Name("example.org"),
- TEST_DATA_DIR "/example.org-nsec3-empty-salt.zone");
+ loadZoneIntoTable(*ztable_segment_, Name("example.org"), zclass_,
+ TEST_DATA_DIR "/example.org-nsec3-empty-salt.zone");
const ZoneData* zone_data =
client_->findZoneData(Name("example.org"));
ASSERT_NE(static_cast<const ZoneData*>(NULL), zone_data);
@@ -346,8 +343,8 @@ TEST_F(MemoryClientTest, loadNSEC3EmptySalt) {
}
TEST_F(MemoryClientTest, loadNSEC3SignedNoParam) {
- client_->load(Name("example.org"),
- TEST_DATA_DIR "/example.org-nsec3-signed-no-param.zone");
+ loadZoneIntoTable(*ztable_segment_, Name("example.org"), zclass_,
+ TEST_DATA_DIR "/example.org-nsec3-signed-no-param.zone");
const ZoneData* zone_data =
client_->findZoneData(Name("example.org"));
ASSERT_NE(static_cast<const ZoneData*>(NULL), zone_data);
@@ -360,14 +357,14 @@ TEST_F(MemoryClientTest, loadReloadZone) {
// doesn't increase.
EXPECT_EQ(0, client_->getZoneCount());
- client_->load(Name("example.org"),
- TEST_DATA_DIR "/example.org-empty.zone");
+ loadZoneIntoTable(*ztable_segment_, Name("example.org"), zclass_,
+ TEST_DATA_DIR "/example.org-empty.zone");
EXPECT_EQ(1, client_->getZoneCount());
// Reload zone with same data
- client_->load(Name("example.org"),
- client_->getFileName(Name("example.org")));
+ loadZoneIntoTable(*ztable_segment_, Name("example.org"), zclass_,
+ TEST_DATA_DIR "/example.org-empty.zone");
EXPECT_EQ(1, client_->getZoneCount());
const ZoneData* zone_data =
@@ -396,8 +393,8 @@ TEST_F(MemoryClientTest, loadReloadZone) {
// Reload zone with different data
- client_->load(Name("example.org"),
- TEST_DATA_DIR "/example.org-rrsigs.zone");
+ loadZoneIntoTable(*ztable_segment_, Name("example.org"), zclass_,
+ TEST_DATA_DIR "/example.org-rrsigs.zone");
EXPECT_EQ(1, client_->getZoneCount());
zone_data = client_->findZoneData(Name("example.org"));
@@ -441,15 +438,14 @@ TEST_F(MemoryClientTest, loadReloadZone) {
TEST_F(MemoryClientTest, loadDuplicateType) {
// This should not result in any exceptions (multiple records of the
// same name, type are present, one after another in sequence).
- client_->load(Name("example.org"),
- TEST_DATA_DIR "/example.org-duplicate-type.zone");
+ loadZoneIntoTable(*ztable_segment_, Name("example.org"), zclass_,
+ TEST_DATA_DIR "/example.org-duplicate-type.zone");
// This should not result in any exceptions (multiple records of the
// same name, type are present, but not one after another in
// sequence).
- client_->load(Name("example.org"),
- TEST_DATA_DIR
- "/example.org-duplicate-type-bad.zone");
+ loadZoneIntoTable(*ztable_segment_, Name("example.org"), zclass_,
+ TEST_DATA_DIR "/example.org-duplicate-type-bad.zone");
const ZoneData* zone_data =
client_->findZoneData(Name("example.org"));
@@ -479,104 +475,116 @@ TEST_F(MemoryClientTest, loadDuplicateType) {
TEST_F(MemoryClientTest, loadMultipleCNAMEThrows) {
// Multiple CNAME RRs should throw.
- EXPECT_THROW(client_->load(Name("example.org"),
- TEST_DATA_DIR
- "/example.org-multiple-cname.zone"),
+ EXPECT_THROW(loadZoneIntoTable(*ztable_segment_, Name("example.org"),
+ zclass_,
+ TEST_DATA_DIR
+ "/example.org-multiple-cname.zone"),
ZoneDataUpdater::AddError);
// Teardown checks for memory segment leaks
}
TEST_F(MemoryClientTest, loadMultipleDNAMEThrows) {
// Multiple DNAME RRs should throw.
- EXPECT_THROW(client_->load(Name("example.org"),
- TEST_DATA_DIR
- "/example.org-multiple-dname.zone"),
+ EXPECT_THROW(loadZoneIntoTable(*ztable_segment_, Name("example.org"),
+ zclass_,
+ TEST_DATA_DIR
+ "/example.org-multiple-dname.zone"),
ZoneDataUpdater::AddError);
// Teardown checks for memory segment leaks
}
TEST_F(MemoryClientTest, loadMultipleNSEC3Throws) {
// Multiple NSEC3 RRs should throw.
- EXPECT_THROW(client_->load(Name("example.org"),
- TEST_DATA_DIR
- "/example.org-multiple-nsec3.zone"),
+ EXPECT_THROW(loadZoneIntoTable(*ztable_segment_, Name("example.org"),
+ zclass_,
+ TEST_DATA_DIR
+ "/example.org-multiple-nsec3.zone"),
ZoneDataUpdater::AddError);
// Teardown checks for memory segment leaks
}
TEST_F(MemoryClientTest, loadMultipleNSEC3PARAMThrows) {
// Multiple NSEC3PARAM RRs should throw.
- EXPECT_THROW(client_->load(Name("example.org"),
- TEST_DATA_DIR
- "/example.org-multiple-nsec3param.zone"),
+ EXPECT_THROW(loadZoneIntoTable(*ztable_segment_, Name("example.org"),
+ zclass_,
+ TEST_DATA_DIR
+ "/example.org-multiple-nsec3param.zone"),
ZoneDataUpdater::AddError);
// Teardown checks for memory segment leaks
}
TEST_F(MemoryClientTest, loadOutOfZoneThrows) {
// Out of zone names should throw.
- EXPECT_THROW(client_->load(Name("example.org"),
- TEST_DATA_DIR
- "/example.org-out-of-zone.zone"),
+ EXPECT_THROW(loadZoneIntoTable(*ztable_segment_, Name("example.org"),
+ zclass_,
+ TEST_DATA_DIR
+ "/example.org-out-of-zone.zone"),
ZoneLoaderException);
// Teardown checks for memory segment leaks
}
TEST_F(MemoryClientTest, loadWildcardNSThrows) {
// Wildcard NS names should throw
- EXPECT_THROW(client_->load(Name("example.org"),
- TEST_DATA_DIR
- "/example.org-wildcard-ns.zone"),
+ EXPECT_THROW(loadZoneIntoTable(*ztable_segment_, Name("example.org"),
+ zclass_,
+ TEST_DATA_DIR
+ "/example.org-wildcard-ns.zone"),
ZoneDataUpdater::AddError);
// Teardown checks for memory segment leaks
}
TEST_F(MemoryClientTest, loadWildcardDNAMEThrows) {
// Wildcard DNAME names should throw
- EXPECT_THROW(client_->load(Name("example.org"),
- TEST_DATA_DIR
- "/example.org-wildcard-dname.zone"),
+ EXPECT_THROW(loadZoneIntoTable(*ztable_segment_, Name("example.org"),
+ zclass_,
+ TEST_DATA_DIR
+ "/example.org-wildcard-dname.zone"),
ZoneDataUpdater::AddError);
// Teardown checks for memory segment leaks
}
TEST_F(MemoryClientTest, loadWildcardNSEC3Throws) {
// Wildcard NSEC3 names should throw
- EXPECT_THROW(client_->load(Name("example.org"),
- TEST_DATA_DIR
- "/example.org-wildcard-nsec3.zone"),
+ EXPECT_THROW(loadZoneIntoTable(*ztable_segment_, Name("example.org"),
+ zclass_,
+ TEST_DATA_DIR
+ "/example.org-wildcard-nsec3.zone"),
ZoneDataUpdater::AddError);
// Teardown checks for memory segment leaks
}
TEST_F(MemoryClientTest, loadNSEC3WithFewerLabelsThrows) {
// NSEC3 names with labels != (origin_labels + 1) should throw
- EXPECT_THROW(client_->load(Name("example.org"),
- TEST_DATA_DIR
- "/example.org-nsec3-fewer-labels.zone"),
+ EXPECT_THROW(loadZoneIntoTable(*ztable_segment_, Name("example.org"),
+ zclass_,
+ TEST_DATA_DIR
+ "/example.org-nsec3-fewer-labels.zone"),
ZoneDataUpdater::AddError);
// Teardown checks for memory segment leaks
}
TEST_F(MemoryClientTest, loadNSEC3WithMoreLabelsThrows) {
// NSEC3 names with labels != (origin_labels + 1) should throw
- EXPECT_THROW(client_->load(Name("example.org"),
- TEST_DATA_DIR
- "/example.org-nsec3-more-labels.zone"),
+ EXPECT_THROW(loadZoneIntoTable(*ztable_segment_, Name("example.org"),
+ zclass_,
+ TEST_DATA_DIR
+ "/example.org-nsec3-more-labels.zone"),
ZoneDataUpdater::AddError);
// Teardown checks for memory segment leaks
}
TEST_F(MemoryClientTest, loadCNAMEAndNotNSECThrows) {
// CNAME and not NSEC should throw
- EXPECT_THROW(client_->load(Name("example.org"),
- TEST_DATA_DIR
- "/example.org-cname-and-not-nsec-1.zone"),
+ EXPECT_THROW(loadZoneIntoTable(*ztable_segment_, Name("example.org"),
+ zclass_,
+ TEST_DATA_DIR
+ "/example.org-cname-and-not-nsec-1.zone"),
ZoneDataUpdater::AddError);
- EXPECT_THROW(client_->load(Name("example.org"),
- TEST_DATA_DIR
- "/example.org-cname-and-not-nsec-2.zone"),
+ EXPECT_THROW(loadZoneIntoTable(*ztable_segment_, Name("example.org"),
+ zclass_,
+ TEST_DATA_DIR
+ "/example.org-cname-and-not-nsec-2.zone"),
ZoneDataUpdater::AddError);
// Teardown checks for memory segment leaks
@@ -584,41 +592,41 @@ TEST_F(MemoryClientTest, loadCNAMEAndNotNSECThrows) {
TEST_F(MemoryClientTest, loadDNAMEAndNSApex1) {
// DNAME + NS (apex) is OK
- client_->load(Name("example.org"),
- TEST_DATA_DIR
- "/example.org-dname-ns-apex-1.zone");
+ loadZoneIntoTable(*ztable_segment_, Name("example.org"), zclass_,
+ TEST_DATA_DIR "/example.org-dname-ns-apex-1.zone");
// Teardown checks for memory segment leaks
}
TEST_F(MemoryClientTest, loadDNAMEAndNSApex2) {
// DNAME + NS (apex) is OK (reverse order)
- client_->load(Name("example.org"),
- TEST_DATA_DIR
- "/example.org-dname-ns-apex-2.zone");
+ loadZoneIntoTable(*ztable_segment_, Name("example.org"), zclass_,
+ TEST_DATA_DIR "/example.org-dname-ns-apex-2.zone");
// Teardown checks for memory segment leaks
}
TEST_F(MemoryClientTest, loadDNAMEAndNSNonApex1) {
// DNAME + NS (non-apex) must throw
- EXPECT_THROW(client_->load(Name("example.org"),
- TEST_DATA_DIR
- "/example.org-dname-ns-nonapex-1.zone"),
+ EXPECT_THROW(loadZoneIntoTable(*ztable_segment_, Name("example.org"),
+ zclass_,
+ TEST_DATA_DIR
+ "/example.org-dname-ns-nonapex-1.zone"),
ZoneDataUpdater::AddError);
// Teardown checks for memory segment leaks
}
TEST_F(MemoryClientTest, loadDNAMEAndNSNonApex2) {
// DNAME + NS (non-apex) must throw (reverse order)
- EXPECT_THROW(client_->load(Name("example.org"),
- TEST_DATA_DIR
- "/example.org-dname-ns-nonapex-2.zone"),
+ EXPECT_THROW(loadZoneIntoTable(*ztable_segment_, Name("example.org"),
+ zclass_,
+ TEST_DATA_DIR
+ "/example.org-dname-ns-nonapex-2.zone"),
ZoneDataUpdater::AddError);
// Teardown checks for memory segment leaks
}
TEST_F(MemoryClientTest, loadRRSIGs) {
- client_->load(Name("example.org"),
- TEST_DATA_DIR "/example.org-rrsigs.zone");
+ loadZoneIntoTable(*ztable_segment_, Name("example.org"), zclass_,
+ TEST_DATA_DIR "/example.org-rrsigs.zone");
EXPECT_EQ(1, client_->getZoneCount());
}
@@ -642,37 +650,31 @@ TEST_F(MemoryClientTest, loadRRSIGsRdataMixedCoveredTypes) {
rrsets_vec.push_back(rrset);
- EXPECT_THROW(
- client_->load(Name("example.org"),
- *MockVectorIterator::makeIterator(rrsets_vec)),
- ZoneDataUpdater::AddError);
+ EXPECT_THROW(loadZoneIntoTable(*ztable_segment_, Name("example.org"),
+ zclass_,
+ *MockVectorIterator::makeIterator(
+ rrsets_vec)),
+ ZoneDataUpdater::AddError);
// Teardown checks for memory segment leaks
}
TEST_F(MemoryClientTest, getZoneCount) {
EXPECT_EQ(0, client_->getZoneCount());
- client_->load(Name("example.org"), TEST_DATA_DIR "/example.org-empty.zone");
+ loadZoneIntoTable(*ztable_segment_, Name("example.org"), zclass_,
+ TEST_DATA_DIR "/example.org-empty.zone");
+ // We've updated the zone table already in the client, so the count
+ // should also be incremented indirectly.
EXPECT_EQ(1, client_->getZoneCount());
}
-TEST_F(MemoryClientTest, getFileNameForNonExistentZone) {
- // Zone "example.org." doesn't exist
- EXPECT_TRUE(client_->getFileName(Name("example.org.")).empty());
-}
-
-TEST_F(MemoryClientTest, getFileName) {
- client_->load(Name("example.org"), TEST_DATA_DIR "/example.org-empty.zone");
- EXPECT_EQ(TEST_DATA_DIR "/example.org-empty.zone",
- client_->getFileName(Name("example.org")));
-}
-
TEST_F(MemoryClientTest, getIteratorForNonExistentZone) {
// Zone "." doesn't exist
- EXPECT_THROW(client_->getIterator(Name(".")), DataSourceError);
+ EXPECT_THROW(client_->getIterator(Name(".")), NoSuchZone);
}
TEST_F(MemoryClientTest, getIterator) {
- client_->load(Name("example.org"), TEST_DATA_DIR "/example.org-empty.zone");
+ loadZoneIntoTable(*ztable_segment_, Name("example.org"), zclass_,
+ TEST_DATA_DIR "/example.org-empty.zone");
ZoneIteratorPtr iterator(client_->getIterator(Name("example.org")));
// First we have the SOA
@@ -693,8 +695,8 @@ TEST_F(MemoryClientTest, getIterator) {
}
TEST_F(MemoryClientTest, getIteratorSeparateRRs) {
- client_->load(Name("example.org"),
- TEST_DATA_DIR "/example.org-multiple.zone");
+ loadZoneIntoTable(*ztable_segment_, Name("example.org"), zclass_,
+ TEST_DATA_DIR "/example.org-multiple.zone");
// separate_rrs = false
ZoneIteratorPtr iterator(client_->getIterator(Name("example.org")));
@@ -746,8 +748,8 @@ TEST_F(MemoryClientTest, getIteratorSeparateRRs) {
// Test we get RRSIGs and NSEC3s too for iterating with separate RRs
TEST_F(MemoryClientTest, getIteratorSeparateSigned) {
- client_->load(Name("example.org"),
- TEST_DATA_DIR "/example.org-nsec3-signed.zone");
+ loadZoneIntoTable(*ztable_segment_, Name("example.org"), zclass_,
+ TEST_DATA_DIR "/example.org-nsec3-signed.zone");
ZoneIteratorPtr iterator(client_->getIterator(Name("example.org"), true));
bool seen_rrsig = false, seen_nsec3 = false;
for (ConstRRsetPtr rrset = iterator->getNextRRset();
@@ -764,7 +766,8 @@ TEST_F(MemoryClientTest, getIteratorSeparateSigned) {
}
TEST_F(MemoryClientTest, getIteratorGetSOAThrowsNotImplemented) {
- client_->load(Name("example.org"), TEST_DATA_DIR "/example.org-empty.zone");
+ loadZoneIntoTable(*ztable_segment_, Name("example.org"), zclass_,
+ TEST_DATA_DIR "/example.org-empty.zone");
ZoneIteratorPtr iterator(client_->getIterator(Name("example.org")));
// This method is not implemented.
@@ -780,16 +783,17 @@ TEST_F(MemoryClientTest, addEmptyRRsetThrows) {
rrsets_vec.push_back(RRsetPtr(new RRset(Name("example.org"), zclass_,
RRType::A(), RRTTL(3600))));
- EXPECT_THROW(
- client_->load(Name("example.org"),
- *MockVectorIterator::makeIterator(rrsets_vec)),
- ZoneDataUpdater::AddError);
+ EXPECT_THROW(loadZoneIntoTable(*ztable_segment_, Name("example.org"),
+ zclass_,
+ *MockVectorIterator::makeIterator(
+ rrsets_vec)),
+ ZoneDataUpdater::AddError);
// Teardown checks for memory segment leaks
}
TEST_F(MemoryClientTest, findZoneData) {
- client_->load(Name("example.org"),
- TEST_DATA_DIR "/example.org-rrsigs.zone");
+ loadZoneIntoTable(*ztable_segment_, Name("example.org"), zclass_,
+ TEST_DATA_DIR "/example.org-rrsigs.zone");
const ZoneData* zone_data = client_->findZoneData(Name("example.com"));
EXPECT_EQ(static_cast<const ZoneData*>(NULL), zone_data);
diff --git a/src/lib/datasrc/tests/memory/memory_segment_mock.h b/src/lib/datasrc/tests/memory/memory_segment_mock.h
new file mode 100644
index 0000000..92c291d
--- /dev/null
+++ b/src/lib/datasrc/tests/memory/memory_segment_mock.h
@@ -0,0 +1,62 @@
+// Copyright (C) 2012 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 DATASRC_MEMORY_SEGMENT_TEST_H
+#define DATASRC_MEMORY_SEGMENT_TEST_H 1
+
+#include <util/memory_segment_local.h>
+
+#include <cstddef> // for size_t
+#include <new> // for bad_alloc
+
+namespace isc {
+namespace datasrc {
+namespace memory {
+namespace test {
+
+// A special memory segment that can be used for tests. It normally behaves
+// like a "local" memory segment. If "throw count" is set to non 0 via
+// setThrowCount(), it continues the normal behavior until the specified
+// number of calls to allocate(), exclusive, and throws an exception at the
+// next call. For example, if count is set to 3, the next two calls to
+// allocate() will succeed, and the 3rd call will fail with an exception.
+// This segment object can be used after the exception is thrown, and the
+// count is internally reset to 0.
+class MemorySegmentMock : public isc::util::MemorySegmentLocal {
+public:
+ MemorySegmentMock() : throw_count_(0) {}
+ virtual void* allocate(std::size_t size) {
+ if (throw_count_ > 0) {
+ if (--throw_count_ == 0) {
+ throw std::bad_alloc();
+ }
+ }
+ return (isc::util::MemorySegmentLocal::allocate(size));
+ }
+ void setThrowCount(std::size_t count) { throw_count_ = count; }
+
+private:
+ std::size_t throw_count_;
+};
+
+} // namespace test
+} // namespace memory
+} // namespace datasrc
+} // namespace isc
+
+#endif // DATASRC_MEMORY_SEGMENT_TEST_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/datasrc/tests/memory/memory_segment_test.h b/src/lib/datasrc/tests/memory/memory_segment_test.h
deleted file mode 100644
index 3195a9b..0000000
--- a/src/lib/datasrc/tests/memory/memory_segment_test.h
+++ /dev/null
@@ -1,62 +0,0 @@
-// Copyright (C) 2012 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 DATASRC_MEMORY_SEGMENT_TEST_H
-#define DATASRC_MEMORY_SEGMENT_TEST_H 1
-
-#include <util/memory_segment_local.h>
-
-#include <cstddef> // for size_t
-#include <new> // for bad_alloc
-
-namespace isc {
-namespace datasrc {
-namespace memory {
-namespace test {
-
-// A special memory segment that can be used for tests. It normally behaves
-// like a "local" memory segment. If "throw count" is set to non 0 via
-// setThrowCount(), it continues the normal behavior until the specified
-// number of calls to allocate(), exclusive, and throws an exception at the
-// next call. For example, if count is set to 3, the next two calls to
-// allocate() will succeed, and the 3rd call will fail with an exception.
-// This segment object can be used after the exception is thrown, and the
-// count is internally reset to 0.
-class MemorySegmentTest : public isc::util::MemorySegmentLocal {
-public:
- MemorySegmentTest() : throw_count_(0) {}
- virtual void* allocate(std::size_t size) {
- if (throw_count_ > 0) {
- if (--throw_count_ == 0) {
- throw std::bad_alloc();
- }
- }
- return (isc::util::MemorySegmentLocal::allocate(size));
- }
- void setThrowCount(std::size_t count) { throw_count_ = count; }
-
-private:
- std::size_t throw_count_;
-};
-
-} // namespace test
-} // namespace memory
-} // namespace datasrc
-} // namespace isc
-
-#endif // DATASRC_MEMORY_SEGMENT_TEST_H
-
-// Local Variables:
-// mode: c++
-// End:
diff --git a/src/lib/datasrc/tests/memory/rdataset_unittest.cc b/src/lib/datasrc/tests/memory/rdataset_unittest.cc
index 1a964ef..c8e0ff9 100644
--- a/src/lib/datasrc/tests/memory/rdataset_unittest.cc
+++ b/src/lib/datasrc/tests/memory/rdataset_unittest.cc
@@ -193,18 +193,18 @@ TEST_F(RdataSetTest, mergeCreate) {
// Create old rdataset
SegmentObjectHolder<RdataSet, RRClass> holder1(
mem_sgmt_,
- RdataSet::create(mem_sgmt_, encoder_,
- (i & 1) != 0 ? a_rrsets[0] : null_rrset,
- (i & 2) != 0 ? rrsig_rrsets[0] : null_rrset),
rrclass);
+ holder1.set(RdataSet::create(mem_sgmt_, encoder_,
+ (i & 1) != 0 ? a_rrsets[0] : null_rrset,
+ (i & 2) != 0 ? rrsig_rrsets[0] : null_rrset));
// Create merged rdataset, based on the old one and RRsets
SegmentObjectHolder<RdataSet, RRClass> holder2(
mem_sgmt_,
- RdataSet::create(mem_sgmt_, encoder_,
+ rrclass);
+ holder2.set(RdataSet::create(mem_sgmt_, encoder_,
(j & 1) != 0 ? a_rrsets[1] : null_rrset,
(j & 2) != 0 ? rrsig_rrsets[1] : null_rrset,
- holder1.get()),
- rrclass);
+ holder1.get()));
// Set up the expected data for the case.
vector<string> expected_rdata;
@@ -242,15 +242,15 @@ TEST_F(RdataSetTest, duplicate) {
// After suppressing duplicates, it should be the same as the default
// RdataSet. Check that.
SegmentObjectHolder<RdataSet, RRClass> holder1(
- mem_sgmt_,
- RdataSet::create(mem_sgmt_, encoder_, dup_rrset, dup_rrsig), rrclass);
+ mem_sgmt_, rrclass);
+ holder1.set(RdataSet::create(mem_sgmt_, encoder_, dup_rrset, dup_rrsig));
checkRdataSet(*holder1.get(), def_rdata_txt_, def_rrsig_txt_);
// Confirm the same thing for the merge mode.
SegmentObjectHolder<RdataSet, RRClass> holder2(
- mem_sgmt_,
- RdataSet::create(mem_sgmt_, encoder_, a_rrset_, rrsig_rrset_,
- holder1.get()), rrclass);
+ mem_sgmt_, rrclass);
+ holder2.set(RdataSet::create(mem_sgmt_, encoder_, a_rrset_, rrsig_rrset_,
+ holder1.get()));
checkRdataSet(*holder2.get(), def_rdata_txt_, def_rrsig_txt_);
}
@@ -276,23 +276,24 @@ TEST_F(RdataSetTest, getNext) {
TEST_F(RdataSetTest, find) {
// Create some RdataSets and make a chain of them.
SegmentObjectHolder<RdataSet, RRClass> holder1(
- mem_sgmt_,
- RdataSet::create(mem_sgmt_, encoder_, a_rrset_, ConstRRsetPtr()),
- RRClass::IN());
+ mem_sgmt_, RRClass::IN());
+ holder1.set(RdataSet::create(mem_sgmt_, encoder_, a_rrset_,
+ ConstRRsetPtr()));
ConstRRsetPtr aaaa_rrset =
textToRRset("www.example.com. 1076895760 IN AAAA 2001:db8::1");
SegmentObjectHolder<RdataSet, RRClass> holder2(
mem_sgmt_,
- RdataSet::create(mem_sgmt_, encoder_, aaaa_rrset, ConstRRsetPtr()),
RRClass::IN());
+ holder2.set(RdataSet::create(mem_sgmt_, encoder_, aaaa_rrset,
+ ConstRRsetPtr()));
ConstRRsetPtr sigonly_rrset =
textToRRset("www.example.com. 1076895760 IN RRSIG "
"TXT 5 2 3600 20120814220826 20120715220826 "
"1234 example.com. FAKE");
SegmentObjectHolder<RdataSet, RRClass> holder3(
- mem_sgmt_,
- RdataSet::create(mem_sgmt_, encoder_, ConstRRsetPtr(), sigonly_rrset),
- RRClass::IN());
+ mem_sgmt_, RRClass::IN());
+ holder3.set(RdataSet::create(mem_sgmt_, encoder_, ConstRRsetPtr(),
+ sigonly_rrset));
RdataSet* rdataset_a = holder1.get();
RdataSet* rdataset_aaaa = holder2.get();
@@ -377,9 +378,8 @@ TEST_F(RdataSetTest, createManyRRs) {
TEST_F(RdataSetTest, mergeCreateManyRRs) {
ConstRRsetPtr rrset = textToRRset("example.com. 3600 IN TXT some-text");
SegmentObjectHolder<RdataSet, RRClass> holder(
- mem_sgmt_,
- RdataSet::create(mem_sgmt_, encoder_, rrset, ConstRRsetPtr()),
- RRClass::IN());
+ mem_sgmt_, RRClass::IN());
+ holder.set(RdataSet::create(mem_sgmt_, encoder_, rrset, ConstRRsetPtr()));
checkCreateManyRRs(boost::bind(&RdataSet::create, _1, _2, _3, _4,
holder.get()), rrset->getRdataCount());
@@ -475,9 +475,8 @@ TEST_F(RdataSetTest, mergeCreateManyRRSIGs) {
"example.com. 3600 IN RRSIG A 5 2 3600 20120814220826 20120715220826 "
"1234 example.com. FAKEFAKE");
SegmentObjectHolder<RdataSet, RRClass> holder(
- mem_sgmt_,
- RdataSet::create(mem_sgmt_, encoder_, ConstRRsetPtr(), rrsig),
- rrclass);
+ mem_sgmt_, rrclass);
+ holder.set(RdataSet::create(mem_sgmt_, encoder_, ConstRRsetPtr(), rrsig));
checkCreateManyRRSIGs(boost::bind(&RdataSet::create, _1, _2, _3, _4,
holder.get()), rrsig->getRdataCount());
@@ -544,11 +543,10 @@ TEST_F(RdataSetTest, badMergeCreate) {
// The 'old RdataSet' for merge. Its content doesn't matter much; the test
// should trigger exception before examining it except for the last checks.
SegmentObjectHolder<RdataSet, RRClass> holder(
- mem_sgmt_,
- RdataSet::create(mem_sgmt_, encoder_,
+ mem_sgmt_, RRClass::IN());
+ holder.set(RdataSet::create(mem_sgmt_, encoder_,
textToRRset("www.example.com. 0 IN AAAA 2001:db8::1"),
- ConstRRsetPtr()),
- RRClass::IN());
+ ConstRRsetPtr()));
checkBadCreate(boost::bind(&RdataSet::create, _1, _2, _3, _4,
holder.get()));
@@ -586,8 +584,8 @@ TEST_F(RdataSetTest, varyingTTL) {
// RRSIG's TTL is smaller
SegmentObjectHolder<RdataSet, RRClass> holder1(
- mem_sgmt_,
- RdataSet::create(mem_sgmt_, encoder_, aaaa_large, sig_small), rrclass);
+ mem_sgmt_, rrclass);
+ holder1.set(RdataSet::create(mem_sgmt_, encoder_, aaaa_large, sig_small));
EXPECT_EQ(RRTTL(10), restoreTTL(holder1.get()->getTTLData()));
// Merging another RRset (w/o sig) that has larger TTL
diff --git a/src/lib/datasrc/tests/memory/rrset_collection_unittest.cc b/src/lib/datasrc/tests/memory/rrset_collection_unittest.cc
index 04e285b..f3d3934 100644
--- a/src/lib/datasrc/tests/memory/rrset_collection_unittest.cc
+++ b/src/lib/datasrc/tests/memory/rrset_collection_unittest.cc
@@ -15,15 +15,17 @@
#include <datasrc/memory/rrset_collection.h>
-#include "memory_segment_test.h"
-
#include <datasrc/memory/zone_data_loader.h>
#include <datasrc/memory/segment_object_holder.h>
#include <dns/rrttl.h>
#include <dns/rdataclass.h>
+#include <datasrc/tests/memory/memory_segment_mock.h>
+
#include <gtest/gtest.h>
+#include <boost/scoped_ptr.hpp>
+
using namespace isc::dns;
using namespace isc::dns::rdata;
using namespace std;
@@ -43,22 +45,24 @@ public:
rrclass("IN"),
origin("example.org"),
zone_file(TEST_DATA_DIR "/rrset-collection.zone"),
- zone_data_holder(mem_sgmt,
- loadZoneData(mem_sgmt, rrclass, origin, zone_file),
- rrclass),
- collection(*zone_data_holder.get(), rrclass)
- {}
+ zone_data_holder(mem_sgmt, rrclass)
+ {
+ zone_data_holder.set(loadZoneData(mem_sgmt, rrclass, origin,
+ zone_file));
+ collection.reset(new RRsetCollection(*zone_data_holder.get(),
+ rrclass));
+ }
const RRClass rrclass;
const Name origin;
std::string zone_file;
- test::MemorySegmentTest mem_sgmt;
+ test::MemorySegmentMock mem_sgmt;
SegmentObjectHolder<ZoneData, RRClass> zone_data_holder;
- RRsetCollection collection;
+ boost::scoped_ptr<RRsetCollection> collection;
};
TEST_F(RRsetCollectionTest, find) {
- const RRsetCollection& ccln = collection;
+ const RRsetCollection& ccln = *collection;
ConstRRsetPtr rrset = ccln.find(Name("www.example.org"), rrclass,
RRType::A());
EXPECT_TRUE(rrset);
diff --git a/src/lib/datasrc/tests/memory/segment_object_holder_unittest.cc b/src/lib/datasrc/tests/memory/segment_object_holder_unittest.cc
index 70d331e..2d36952 100644
--- a/src/lib/datasrc/tests/memory/segment_object_holder_unittest.cc
+++ b/src/lib/datasrc/tests/memory/segment_object_holder_unittest.cc
@@ -34,6 +34,7 @@ public:
static void destroy(MemorySegment& sgmt, TestObject* obj, int arg) {
sgmt.deallocate(obj, sizeof(*obj));
EXPECT_EQ(TEST_ARG_VAL, arg);
+ EXPECT_TRUE(obj);
}
};
@@ -46,7 +47,8 @@ useHolder(MemorySegment& sgmt, TestObject* obj, bool release) {
// deallocate().
typedef SegmentObjectHolder<TestObject, int> HolderType;
- HolderType holder(sgmt, obj, TEST_ARG_VAL);
+ HolderType holder(sgmt, TEST_ARG_VAL);
+ holder.set(obj);
EXPECT_EQ(obj, holder.get());
if (release) {
EXPECT_EQ(obj, holder.release());
@@ -69,6 +71,16 @@ TEST(SegmentObjectHolderTest, foo) {
EXPECT_TRUE(sgmt.allMemoryDeallocated());
}
+// Test nothing bad happens if the holder is not set before it is destroyed
+TEST(SegmentObjectHolderTest, destroyNotSet) {
+ MemorySegmentLocal sgmt;
+ {
+ typedef SegmentObjectHolder<TestObject, int> HolderType;
+ HolderType holder(sgmt, TEST_ARG_VAL);
+ }
+ EXPECT_TRUE(sgmt.allMemoryDeallocated());
+}
+
// Keep allocating bigger and bigger chunks of data until the allocation
// fails with growing the segment.
void
@@ -78,7 +90,8 @@ allocateUntilGrows(MemorySegment& segment, size_t& current_size) {
// the position moved.
void *object_memory = segment.allocate(sizeof(TestObject));
TestObject* object = new(object_memory) TestObject;
- SegmentObjectHolder<TestObject, int> holder(segment, object, TEST_ARG_VAL);
+ SegmentObjectHolder<TestObject, int> holder(segment, TEST_ARG_VAL);
+ holder.set(object);
while (true) {
void* data = segment.allocate(current_size);
segment.deallocate(data, current_size);
@@ -113,11 +126,11 @@ TEST(SegmentObjectHolderTest, DISABLED_grow) {
size_t alloc_size = 1024;
EXPECT_THROW(allocateUntilGrows(segment, alloc_size), MemorySegmentGrown);
// Confirm it's now mapped at a different address.
- EXPECT_NE(mark, segment.getNamedAddress("mark"))
+ EXPECT_NE(mark, segment.getNamedAddress("mark").second)
<< "portability assumption for the test doesn't hold; "
"disable the test by setting env variable GTEST_FILTER to "
"'-SegmentObjectHolderTest.grow'";
- mark = segment.getNamedAddress("mark");
+ mark = segment.getNamedAddress("mark").second;
segment.clearNamedAddress("mark");
segment.deallocate(mark, 1);
EXPECT_TRUE(segment.allMemoryDeallocated());
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 a9aaea3..bfd79a5 100644
--- a/src/lib/datasrc/tests/memory/zone_data_loader_unittest.cc
+++ b/src/lib/datasrc/tests/memory/zone_data_loader_unittest.cc
@@ -27,7 +27,7 @@
#include <util/memory_segment_mapped.h>
#include <util/memory_segment_local.h>
-#include "memory_segment_test.h"
+#include <datasrc/tests/memory/memory_segment_mock.h>
#include <gtest/gtest.h>
@@ -50,7 +50,7 @@ protected:
EXPECT_TRUE(mem_sgmt_.allMemoryDeallocated()); // catch any leak here.
}
const RRClass zclass_;
- test::MemorySegmentTest mem_sgmt_;
+ test::MemorySegmentMock mem_sgmt_;
ZoneData* zone_data_;
};
@@ -99,8 +99,8 @@ TEST(ZoneDataLoaterTest, relocate) {
TEST_DATA_DIR
"/example.org-nsec3-signed.zone");
// Store it, so it is cleaned up later
- zones.push_back(HolderPtr(new Holder(segment, data,
- RRClass::IN())));
+ zones.push_back(HolderPtr(new Holder(segment, RRClass::IN())));
+ zones.back()->set(data);
}
// Deallocate all the zones now.
diff --git a/src/lib/datasrc/tests/memory/zone_data_unittest.cc b/src/lib/datasrc/tests/memory/zone_data_unittest.cc
index ffbd0f6..983e588 100644
--- a/src/lib/datasrc/tests/memory/zone_data_unittest.cc
+++ b/src/lib/datasrc/tests/memory/zone_data_unittest.cc
@@ -16,8 +16,6 @@
#include <datasrc/memory/rdata_serialization.h>
#include <datasrc/memory/rdataset.h>
-#include "memory_segment_test.h"
-
#include <dns/rdataclass.h>
#include <exceptions/exceptions.h>
@@ -30,6 +28,7 @@
#include <dns/rrttl.h>
#include <testutils/dnsmessage_test.h>
+#include <datasrc/tests/memory/memory_segment_mock.h>
#include <gtest/gtest.h>
@@ -73,7 +72,7 @@ protected:
EXPECT_TRUE(mem_sgmt_.allMemoryDeallocated());
}
- MemorySegmentTest mem_sgmt_;
+ MemorySegmentMock mem_sgmt_;
NSEC3Data* nsec3_data_;
const generic::NSEC3PARAM param_rdata_, param_rdata_nosalt_,
param_rdata_largesalt_;
@@ -88,7 +87,7 @@ protected:
// Shared by both test cases using NSEC3 and NSEC3PARAM Rdata
template <typename RdataType>
void
-checkNSEC3Data(MemorySegmentTest& mem_sgmt,
+checkNSEC3Data(MemorySegmentMock& mem_sgmt,
const Name& zone_name,
const RdataType& expect_rdata)
{
diff --git a/src/lib/datasrc/tests/memory/zone_data_updater_unittest.cc b/src/lib/datasrc/tests/memory/zone_data_updater_unittest.cc
index fd6412f..aed66c1 100644
--- a/src/lib/datasrc/tests/memory/zone_data_updater_unittest.cc
+++ b/src/lib/datasrc/tests/memory/zone_data_updater_unittest.cc
@@ -27,8 +27,7 @@
#include <util/memory_segment_local.h>
#include <util/memory_segment_mapped.h>
-
-#include "memory_segment_test.h"
+#include <datasrc/tests/memory/memory_segment_mock.h>
#include <gtest/gtest.h>
@@ -105,7 +104,7 @@ protected:
ZoneData* getZoneData() {
return (static_cast<ZoneData*>(
- mem_sgmt_->getNamedAddress("Test zone data")));
+ mem_sgmt_->getNamedAddress("Test zone data").second));
}
const Name zname_;
@@ -117,7 +116,7 @@ protected:
class TestSegmentCreator : public SegmentCreator {
public:
virtual SegmentPtr create() const {
- return (SegmentPtr(new test::MemorySegmentTest));
+ return (SegmentPtr(new test::MemorySegmentMock));
}
};
diff --git a/src/lib/datasrc/tests/memory/zone_finder_unittest.cc b/src/lib/datasrc/tests/memory/zone_finder_unittest.cc
index cf07107..e07ab27 100644
--- a/src/lib/datasrc/tests/memory/zone_finder_unittest.cc
+++ b/src/lib/datasrc/tests/memory/zone_finder_unittest.cc
@@ -12,8 +12,9 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-#include "memory_segment_test.h"
-#include "zone_table_segment_test.h"
+#include <datasrc/tests/memory/memory_segment_mock.h>
+#include <datasrc/tests/memory/zone_table_segment_mock.h>
+#include <datasrc/tests/memory/zone_loader_util.h>
// NOTE: this faked_nsec3 inclusion (and all related code below)
// was ported during #2109 for the convenience of implementing #2218
@@ -21,14 +22,14 @@
// In #2219 the original is expected to be removed, and this file should
// probably be moved here (and any leftover code not handled in #2218 should
// be cleaned up)
-#include "../../tests/faked_nsec3.h"
+#include <datasrc/tests/faked_nsec3.h>
#include <datasrc/memory/zone_finder.h>
#include <datasrc/memory/zone_data_updater.h>
#include <datasrc/memory/rdata_serialization.h>
#include <datasrc/memory/zone_table_segment.h>
#include <datasrc/memory/memory_client.h>
-#include <datasrc/data_source.h>
+#include <datasrc/exceptions.h>
#include <datasrc/client.h>
#include <testutils/dnsmessage_test.h>
@@ -220,7 +221,7 @@ protected:
const RRClass class_;
const Name origin_;
// The zone finder to torture by tests
- MemorySegmentTest mem_sgmt_;
+ MemorySegmentMock mem_sgmt_;
memory::ZoneData* zone_data_;
memory::InMemoryZoneFinder zone_finder_;
boost::scoped_ptr<ZoneDataUpdater> updater_;
@@ -292,7 +293,7 @@ protected:
* \param zone_finder Check different InMemoryZoneFinder object than
* zone_finder_ (if NULL, uses zone_finder_)
* \param check_wild_answer Checks that the answer has the same RRs, type
- * class and TTL as the eqxpected answer and that the name corresponds
+ * class and TTL as the expected answer and that the name corresponds
* to the one searched. It is meant for checking answers for wildcard
* queries.
*/
@@ -1610,13 +1611,14 @@ TEST_F(InMemoryZoneFinderTest, findOrphanRRSIG) {
// \brief testcase for #2504 (Problem in inmem NSEC denial of existence
// handling)
TEST_F(InMemoryZoneFinderTest, NSECNonExistentTest) {
+ const Name name("example.com.");
shared_ptr<ZoneTableSegment> ztable_segment(
- new ZoneTableSegmentTest(class_, mem_sgmt_));
+ new ZoneTableSegmentMock(class_, mem_sgmt_));
updater_.reset();
+ loadZoneIntoTable(*ztable_segment, name, class_,
+ TEST_DATA_DIR "/2504-test.zone");
InMemoryClient client(ztable_segment, class_);
- Name name("example.com.");
- client.load(name, TEST_DATA_DIR "/2504-test.zone");
DataSourceClient::FindResult result(client.findZone(name));
// Check for a non-existing name
@@ -1772,17 +1774,18 @@ TEST_F(InMemoryZoneFinderNSEC3Test, findNSEC3MissingOrigin) {
DefaultNSEC3HashCreator creator;
setNSEC3HashCreator(&creator);
+ const Name name("example.com.");
shared_ptr<ZoneTableSegment> ztable_segment(
- new ZoneTableSegmentTest(class_, mem_sgmt_));
+ new ZoneTableSegmentMock(class_, mem_sgmt_));
updater_.reset();
+ loadZoneIntoTable(*ztable_segment, name, class_,
+ TEST_DATA_DIR "/2503-test.zone");
InMemoryClient client(ztable_segment, class_);
- Name name("example.com.");
- client.load(name, TEST_DATA_DIR "/2503-test.zone");
DataSourceClient::FindResult result(client.findZone(name));
// Check for a non-existing name
- Name search_name("nonexist.example.com.");
+ const Name search_name("nonexist.example.com.");
ZoneFinder::FindNSEC3Result find_result(
result.zone_finder->findNSEC3(search_name, true));
// findNSEC3() must have completed (not throw or assert). Because
diff --git a/src/lib/datasrc/tests/memory/zone_loader_util.cc b/src/lib/datasrc/tests/memory/zone_loader_util.cc
new file mode 100644
index 0000000..bd55e66
--- /dev/null
+++ b/src/lib/datasrc/tests/memory/zone_loader_util.cc
@@ -0,0 +1,88 @@
+// 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 <datasrc/tests/memory/zone_loader_util.h>
+
+#include <datasrc/zone_iterator.h>
+#include <datasrc/cache_config.h>
+#include <datasrc/memory/zone_table_segment.h>
+#include <datasrc/memory/zone_data_loader.h>
+#include <datasrc/memory/zone_writer.h>
+
+#include <dns/dns_fwd.h>
+
+#include <cc/data.h>
+
+#include <string>
+
+namespace isc {
+namespace datasrc {
+namespace memory {
+namespace test {
+
+void
+loadZoneIntoTable(ZoneTableSegment& zt_sgmt, const dns::Name& zname,
+ const dns::RRClass& zclass, const std::string& zone_file)
+{
+ const isc::datasrc::internal::CacheConfig cache_conf(
+ "MasterFiles", NULL, *data::Element::fromJSON(
+ "{\"cache-enable\": true,"
+ " \"params\": {\"" + zname.toText() + "\": \"" + zone_file +
+ "\"}}"), true);
+ memory::ZoneWriter writer(zt_sgmt, cache_conf.getLoadAction(zclass, zname),
+ zname, zclass);
+ writer.load();
+ writer.install();
+ writer.cleanup();
+}
+
+namespace {
+// borrowed from CacheConfig's internal
+class IteratorLoader {
+public:
+ IteratorLoader(const dns::RRClass& rrclass, const dns::Name& name,
+ ZoneIterator& iterator) :
+ rrclass_(rrclass),
+ name_(name),
+ iterator_(iterator)
+ {}
+ memory::ZoneData* operator()(util::MemorySegment& segment) {
+ return (memory::loadZoneData(segment, rrclass_, name_, iterator_));
+ }
+private:
+ const dns::RRClass rrclass_;
+ const dns::Name name_;
+ ZoneIterator& iterator_;
+};
+}
+
+void
+loadZoneIntoTable(ZoneTableSegment& zt_sgmt, const dns::Name& zname,
+ const dns::RRClass& zclass, ZoneIterator& iterator)
+{
+ memory::ZoneWriter writer(zt_sgmt, IteratorLoader(zclass, zname, iterator),
+ zname, zclass);
+ writer.load();
+ writer.install();
+ writer.cleanup();
+}
+
+} // namespace test
+} // namespace memory
+} // namespace datasrc
+} // namespace isc
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/datasrc/tests/memory/zone_loader_util.h b/src/lib/datasrc/tests/memory/zone_loader_util.h
new file mode 100644
index 0000000..06eba87
--- /dev/null
+++ b/src/lib/datasrc/tests/memory/zone_loader_util.h
@@ -0,0 +1,57 @@
+// 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 DATASRC_MEMORY_TEST_ZONE_LOADER_UTIL_H
+#define DATASRC_MEMORY_TEST_ZONE_LOADER_UTIL_H 1
+
+#include <datasrc/memory/zone_table_segment.h>
+#include <datasrc/memory/zone_data_loader.h>
+
+#include <dns/dns_fwd.h>
+
+#include <string>
+
+namespace isc {
+namespace datasrc {
+namespace memory {
+namespace test {
+
+/// \brief A shortcut utility to load a specified zone into ZoneTableSegment
+/// from a file.
+///
+/// This function does nothing special, simply provides a shortcut for commonly
+/// used pattern that would be used in tests with a ZoneTableSegment loading
+/// a zone from file into it.
+void
+loadZoneIntoTable(ZoneTableSegment& zt_sgmt, const dns::Name& zname,
+ const dns::RRClass& zclass, const std::string& zone_file);
+
+/// \brief A shortcut utility to load a specified zone into ZoneTableSegment
+/// from a zone iterator.
+///
+/// This is similar to the other version, but use a zone iterator as the
+/// source of the zone data.
+void
+loadZoneIntoTable(ZoneTableSegment& zt_sgmt, const dns::Name& zname,
+ const dns::RRClass& zclass, ZoneIterator& iterator);
+} // namespace test
+} // namespace memory
+} // namespace datasrc
+} // namespace isc
+
+#endif // DATASRC_MEMORY_TEST_ZONE_LOADER_UTIL_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/datasrc/tests/memory/zone_table_segment_mapped_unittest.cc b/src/lib/datasrc/tests/memory/zone_table_segment_mapped_unittest.cc
new file mode 100644
index 0000000..441f433
--- /dev/null
+++ b/src/lib/datasrc/tests/memory/zone_table_segment_mapped_unittest.cc
@@ -0,0 +1,590 @@
+// 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 <datasrc/memory/zone_writer.h>
+#include <datasrc/memory/zone_table_segment_mapped.h>
+#include <util/random/random_number_generator.h>
+#include <util/unittests/check_valgrind.h>
+
+#include <gtest/gtest.h>
+#include <boost/format.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <boost/interprocess/file_mapping.hpp>
+
+#include <memory>
+#include <cerrno>
+
+#include <sys/stat.h>
+
+using namespace isc::dns;
+using namespace isc::datasrc::memory;
+using namespace isc::data;
+using namespace isc::util;
+using namespace isc::util::random;
+using namespace std;
+using boost::scoped_ptr;
+
+namespace {
+
+const char* const mapped_file = TEST_DATA_BUILDDIR "/test.mapped";
+const char* const mapped_file2 = TEST_DATA_BUILDDIR "/test2.mapped";
+
+class ZoneTableSegmentMappedTest : public ::testing::Test {
+protected:
+ ZoneTableSegmentMappedTest() :
+ ztable_segment_(
+ ZoneTableSegment::create(RRClass::IN(), "mapped")),
+ config_params_(
+ Element::fromJSON(
+ "{\"mapped-file\": \"" + std::string(mapped_file) + "\"}")),
+ config_params2_(
+ Element::fromJSON(
+ "{\"mapped-file\": \"" + std::string(mapped_file2) + "\"}"))
+ {
+ EXPECT_NE(static_cast<void*>(NULL), ztable_segment_.get());
+ // Verify that a ZoneTableSegmentMapped is created.
+ ZoneTableSegmentMapped* mapped_segment =
+ dynamic_cast<ZoneTableSegmentMapped*>(ztable_segment_.get());
+ EXPECT_NE(static_cast<void*>(NULL), mapped_segment);
+
+ createTestData();
+ }
+
+ ~ZoneTableSegmentMappedTest() {
+ ZoneTableSegment::destroy(ztable_segment_.release());
+ boost::interprocess::file_mapping::remove(mapped_file);
+ boost::interprocess::file_mapping::remove(mapped_file2);
+ }
+
+ typedef std::pair<std::string, int> TestDataElement;
+
+ void createTestData() {
+ UniformRandomIntegerGenerator gen(0, INT_MAX);
+ for (int i = 0; i < 256; ++i) {
+ const string name(boost::str(boost::format("name%d") % i));
+ const int value = gen();
+ test_data_.push_back(TestDataElement(name, value));
+ }
+ }
+
+ void setupMappedFiles();
+ void addData(MemorySegment& segment);
+ bool verifyData(const MemorySegment& segment);
+
+ // Ideally, this should be something similar to a
+ // SegmentObjectHolder, not an auto_ptr.
+ std::auto_ptr<ZoneTableSegment> ztable_segment_;
+ const ConstElementPtr config_params_;
+ const ConstElementPtr config_params2_;
+ std::vector<TestDataElement> test_data_;
+};
+
+bool
+fileExists(const char* path) {
+ struct stat sb;
+ const int status = stat(path, &sb);
+ if (status != 0) {
+ EXPECT_EQ(ENOENT, errno);
+ return (false);
+ }
+ return (true);
+}
+
+void
+deleteChecksum(MemorySegment& segment) {
+ segment.clearNamedAddress("zone_table_checksum");
+}
+
+void
+corruptChecksum(MemorySegment& segment) {
+ const MemorySegment::NamedAddressResult result =
+ segment.getNamedAddress("zone_table_checksum");
+ ASSERT_TRUE(result.first);
+
+ size_t checksum = *static_cast<size_t*>(result.second);
+ ++checksum;
+ *static_cast<size_t*>(result.second) = checksum;
+}
+
+void
+deleteHeader(MemorySegment& segment) {
+ segment.clearNamedAddress("zone_table_header");
+}
+
+void
+ZoneTableSegmentMappedTest::addData(MemorySegment& segment) {
+ // For purposes of this test, we assume that the following
+ // allocations do not resize the mapped segment. For this, we have
+ // to keep the size of test data reasonably small in
+ // createTestData().
+
+ // One by one, add all the elements in test_data_.
+ for (int i = 0; i < test_data_.size(); ++i) {
+ void* ptr = segment.allocate(sizeof(int));
+ ASSERT_TRUE(ptr);
+ *static_cast<int*>(ptr) = test_data_[i].second;
+ const bool grew = segment.setNamedAddress(test_data_[i].first.c_str(),
+ ptr);
+ ASSERT_FALSE(grew);
+ }
+}
+
+bool
+ZoneTableSegmentMappedTest::verifyData(const MemorySegment& segment) {
+ // One by one, verify all the elements in test_data_ exist and have
+ // the expected values.
+ for (int i = 0; i < test_data_.size(); ++i) {
+ const MemorySegment::NamedAddressResult result =
+ segment.getNamedAddress(test_data_[i].first.c_str());
+ if (!result.first) {
+ return (false);
+ }
+ if (*static_cast<int*>(result.second) != test_data_[i].second) {
+ return (false);
+ }
+ }
+
+ return (true);
+}
+
+void
+ZoneTableSegmentMappedTest::setupMappedFiles() {
+ ztable_segment_->reset(ZoneTableSegment::CREATE, config_params_);
+ addData(ztable_segment_->getMemorySegment());
+ EXPECT_TRUE(verifyData(ztable_segment_->getMemorySegment()));
+
+ ztable_segment_->reset(ZoneTableSegment::CREATE, config_params2_);
+ addData(ztable_segment_->getMemorySegment());
+ EXPECT_TRUE(verifyData(ztable_segment_->getMemorySegment()));
+
+ // Now, clear the segment, closing the underlying mapped file.
+ ztable_segment_->clear();
+}
+
+TEST_F(ZoneTableSegmentMappedTest, getImplType) {
+ EXPECT_EQ("mapped", ztable_segment_->getImplType());
+}
+
+TEST_F(ZoneTableSegmentMappedTest, getHeaderUninitialized) {
+ // This should throw as we haven't called reset() yet.
+ EXPECT_THROW(ztable_segment_->getHeader(), isc::InvalidOperation);
+}
+
+TEST_F(ZoneTableSegmentMappedTest, getMemorySegmentUninitialized) {
+ // This should throw as we haven't called reset() yet.
+ EXPECT_THROW(ztable_segment_->getMemorySegment(), isc::InvalidOperation);
+}
+
+TEST_F(ZoneTableSegmentMappedTest, isUsableUninitialized) {
+ // isUsable() must return false by default, when the segment has not
+ // been reset() yet.
+ EXPECT_FALSE(ztable_segment_->isUsable());
+}
+
+TEST_F(ZoneTableSegmentMappedTest, isWritableUninitialized) {
+ // isWritable() must return false by default, when the segment has
+ // not been reset() yet.
+ EXPECT_FALSE(ztable_segment_->isWritable());
+}
+
+TEST_F(ZoneTableSegmentMappedTest, resetBadConfig) {
+ // Open a mapped file in create mode.
+ ztable_segment_->reset(ZoneTableSegment::CREATE, config_params_);
+
+ // Populate it with some data.
+ addData(ztable_segment_->getMemorySegment());
+ EXPECT_TRUE(verifyData(ztable_segment_->getMemorySegment()));
+
+ // All the following resets() with invalid configuration must
+ // provide a strong exception guarantee that the segment is still
+ // usable as before.
+
+ // NULL is passed in config params
+ EXPECT_THROW({
+ ztable_segment_->reset(ZoneTableSegment::CREATE,
+ ConstElementPtr());
+ }, isc::InvalidParameter);
+
+ EXPECT_TRUE(verifyData(ztable_segment_->getMemorySegment()));
+
+ // Not a map
+ EXPECT_THROW({
+ ztable_segment_->reset(ZoneTableSegment::CREATE,
+ Element::fromJSON("42"));
+ }, isc::InvalidParameter);
+
+ EXPECT_TRUE(verifyData(ztable_segment_->getMemorySegment()));
+
+ // Empty map
+ EXPECT_THROW({
+ ztable_segment_->reset(ZoneTableSegment::CREATE,
+ Element::fromJSON("{}"));
+ }, isc::InvalidParameter);
+
+ EXPECT_TRUE(verifyData(ztable_segment_->getMemorySegment()));
+
+ // No "mapped-file" key
+ EXPECT_THROW({
+ ztable_segment_->reset(ZoneTableSegment::CREATE,
+ Element::fromJSON("{\"foo\": \"bar\"}"));
+ }, isc::InvalidParameter);
+
+ EXPECT_TRUE(verifyData(ztable_segment_->getMemorySegment()));
+
+ // Value of "mapped-file" key is not a string
+ EXPECT_THROW({
+ ztable_segment_->reset(ZoneTableSegment::CREATE,
+ Element::fromJSON("{\"mapped-file\": 42}"));
+ }, isc::InvalidParameter);
+
+ EXPECT_TRUE(verifyData(ztable_segment_->getMemorySegment()));
+}
+
+TEST_F(ZoneTableSegmentMappedTest, reset) {
+ // By default, the mapped file doesn't exist, so we cannot open it
+ // in READ_ONLY mode (which does not create the file).
+ EXPECT_THROW({
+ ztable_segment_->reset(ZoneTableSegment::READ_ONLY, config_params_);
+ }, MemorySegmentOpenError);
+
+ // The following should still throw, unaffected by the failed open.
+ EXPECT_THROW(ztable_segment_->getHeader(), isc::InvalidOperation);
+ EXPECT_THROW(ztable_segment_->getMemorySegment(), isc::InvalidOperation);
+
+ // isUsable() and isWritable() must still return false, because the
+ // segment has not been successfully reset() yet.
+ EXPECT_FALSE(ztable_segment_->isUsable());
+ EXPECT_FALSE(ztable_segment_->isWritable());
+
+ // READ_WRITE mode must create the mapped file if it doesn't exist
+ // (and must not result in an exception).
+ ztable_segment_->reset(ZoneTableSegment::READ_WRITE, config_params_);
+ EXPECT_TRUE(ztable_segment_->isUsable());
+ EXPECT_TRUE(ztable_segment_->isWritable());
+
+ // The following method calls should no longer throw:
+ EXPECT_NO_THROW(ztable_segment_->getHeader());
+ EXPECT_NO_THROW(ztable_segment_->getMemorySegment());
+
+ // Let's try to re-open the mapped file in READ_ONLY mode. It should
+ // not fail now.
+ ztable_segment_->reset(ZoneTableSegment::READ_ONLY, config_params_);
+ EXPECT_TRUE(ztable_segment_->isUsable());
+ EXPECT_FALSE(ztable_segment_->isWritable());
+
+ // Re-creating the mapped file should erase old data and should not
+ // trigger any exceptions inside reset() due to old data (such as
+ // named addresses).
+ ztable_segment_->reset(ZoneTableSegment::CREATE, config_params_);
+ EXPECT_TRUE(ztable_segment_->isUsable());
+ EXPECT_TRUE(ztable_segment_->isWritable());
+
+ // When we reset() with an invalid paramter and it fails, then the
+ // segment should still be usable.
+ EXPECT_THROW({
+ ztable_segment_->reset(ZoneTableSegment::CREATE,
+ Element::fromJSON("{}"));
+ }, isc::InvalidParameter);
+ EXPECT_TRUE(ztable_segment_->isUsable());
+ EXPECT_TRUE(ztable_segment_->isWritable());
+ // The following should not throw.
+ EXPECT_NO_THROW(ztable_segment_->getHeader());
+ EXPECT_NO_THROW(ztable_segment_->getMemorySegment());
+
+ // READ_WRITE with an existing map file ought to work too. This
+ // would use existing named addresses. This actually re-opens the
+ // currently open map.
+ ztable_segment_->reset(ZoneTableSegment::READ_WRITE, config_params_);
+ EXPECT_TRUE(ztable_segment_->isUsable());
+ EXPECT_TRUE(ztable_segment_->isWritable());
+}
+
+TEST_F(ZoneTableSegmentMappedTest, resetCreate) {
+ // At this point, the underlying file must not exist.
+ ASSERT_FALSE(fileExists(mapped_file));
+
+ // Open the underlying mapped file in create mode.
+ ztable_segment_->reset(ZoneTableSegment::CREATE, config_params_);
+
+ ASSERT_TRUE(ztable_segment_->isUsable());
+ ASSERT_TRUE(ztable_segment_->isWritable());
+
+ // Add the data.
+ addData(ztable_segment_->getMemorySegment());
+ EXPECT_TRUE(verifyData(ztable_segment_->getMemorySegment()));
+
+ // Close the segment.
+ ztable_segment_->clear();
+
+ // At this point, the underlying file must still exist.
+ ASSERT_TRUE(fileExists(mapped_file));
+
+ // Open the underlying mapped file in create mode again.
+ ztable_segment_->reset(ZoneTableSegment::CREATE, config_params_);
+
+ // The old data should be gone.
+ EXPECT_FALSE(verifyData(ztable_segment_->getMemorySegment()));
+}
+
+TEST_F(ZoneTableSegmentMappedTest, resetReadWrite) {
+ // At this point, the underlying file must not exist.
+ ASSERT_FALSE(fileExists(mapped_file));
+
+ // Open the underlying mapped file in read+write mode.
+ ztable_segment_->reset(ZoneTableSegment::READ_WRITE, config_params_);
+
+ ASSERT_TRUE(ztable_segment_->isUsable());
+ ASSERT_TRUE(ztable_segment_->isWritable());
+
+ // Add the data.
+ addData(ztable_segment_->getMemorySegment());
+ EXPECT_TRUE(verifyData(ztable_segment_->getMemorySegment()));
+
+ // Close the segment.
+ ztable_segment_->clear();
+
+ // At this point, the underlying file must still exist.
+ ASSERT_TRUE(fileExists(mapped_file));
+
+ // Open the underlying mapped file in read+write mode again.
+ ztable_segment_->reset(ZoneTableSegment::READ_WRITE, config_params_);
+
+ // The old data should still be available.
+ EXPECT_TRUE(verifyData(ztable_segment_->getMemorySegment()));
+}
+
+TEST_F(ZoneTableSegmentMappedTest, resetReadOnly) {
+ // At this point, the underlying file must not exist.
+ ASSERT_FALSE(fileExists(mapped_file));
+
+ // Open the underlying mapped file in read+write mode.
+ ztable_segment_->reset(ZoneTableSegment::READ_WRITE, config_params_);
+
+ ASSERT_TRUE(ztable_segment_->isUsable());
+ ASSERT_TRUE(ztable_segment_->isWritable());
+
+ // Add the data.
+ addData(ztable_segment_->getMemorySegment());
+ EXPECT_TRUE(verifyData(ztable_segment_->getMemorySegment()));
+
+ // Close the segment.
+ ztable_segment_->clear();
+
+ // At this point, the underlying file must still exist.
+ ASSERT_TRUE(fileExists(mapped_file));
+
+ // Open the underlying mapped file in read-only mode again.
+ ztable_segment_->reset(ZoneTableSegment::READ_ONLY, config_params_);
+
+ // The old data should still be available.
+ EXPECT_TRUE(verifyData(ztable_segment_->getMemorySegment()));
+
+ // But trying to allocate new data should result in an exception as
+ // the segment is read-only!
+ EXPECT_THROW(addData(ztable_segment_->getMemorySegment()),
+ MemorySegmentError);
+}
+
+TEST_F(ZoneTableSegmentMappedTest, clearUninitialized) {
+ // Clearing a segment that has not been reset() is a nop, as clear()
+ // returns it to a fresh uninitialized state anyway.
+ EXPECT_NO_THROW(ztable_segment_->clear());
+
+ // The following should still throw, because the segment has not
+ // been successfully reset() yet.
+ EXPECT_THROW(ztable_segment_->getHeader(), isc::InvalidOperation);
+ EXPECT_THROW(ztable_segment_->getMemorySegment(), isc::InvalidOperation);
+
+ // isWritable() must still return false, because the segment has not
+ // been successfully reset() yet.
+ EXPECT_FALSE(ztable_segment_->isUsable());
+ EXPECT_FALSE(ztable_segment_->isWritable());
+}
+
+TEST_F(ZoneTableSegmentMappedTest, clear) {
+ // First, open an underlying mapped file in read+write mode (doesn't
+ // exist yet)
+ ztable_segment_->reset(ZoneTableSegment::READ_WRITE, config_params_);
+
+ EXPECT_TRUE(ztable_segment_->isUsable());
+ EXPECT_TRUE(ztable_segment_->isWritable());
+ // The following method calls should no longer throw:
+ EXPECT_NO_THROW(ztable_segment_->getHeader());
+ EXPECT_NO_THROW(ztable_segment_->getMemorySegment());
+
+ // Now, clear the segment.
+ ztable_segment_->clear();
+
+ EXPECT_FALSE(ztable_segment_->isUsable());
+ EXPECT_FALSE(ztable_segment_->isWritable());
+ // The following method calls should now throw.
+ EXPECT_THROW(ztable_segment_->getHeader(), isc::InvalidOperation);
+ EXPECT_THROW(ztable_segment_->getMemorySegment(), isc::InvalidOperation);
+}
+
+TEST_F(ZoneTableSegmentMappedTest, resetFailedCorruptedChecksum) {
+ setupMappedFiles();
+
+ // Open mapped file 1 in read-write mode
+ ztable_segment_->reset(ZoneTableSegment::READ_WRITE, config_params_);
+
+ // Corrupt mapped file 2.
+ scoped_ptr<MemorySegmentMapped> segment
+ (new MemorySegmentMapped(mapped_file2,
+ MemorySegmentMapped::OPEN_OR_CREATE));
+ EXPECT_TRUE(verifyData(*segment));
+ corruptChecksum(*segment);
+ segment.reset();
+
+ // Resetting to mapped file 2 in read-write mode should fail
+ EXPECT_THROW({
+ ztable_segment_->reset(ZoneTableSegment::READ_WRITE, config_params2_);
+ }, ResetFailed);
+
+ EXPECT_TRUE(ztable_segment_->isUsable());
+ EXPECT_TRUE(ztable_segment_->isWritable());
+ // Check for the old data in the segment to make sure it is still
+ // available and correct.
+ EXPECT_TRUE(verifyData(ztable_segment_->getMemorySegment()));
+}
+
+TEST_F(ZoneTableSegmentMappedTest, resetFailedMissingChecksum) {
+ setupMappedFiles();
+
+ // Open mapped file 1 in read-write mode
+ ztable_segment_->reset(ZoneTableSegment::READ_WRITE, config_params_);
+
+ // Corrupt mapped file 2.
+ scoped_ptr<MemorySegmentMapped> segment
+ (new MemorySegmentMapped(mapped_file2,
+ MemorySegmentMapped::OPEN_OR_CREATE));
+ EXPECT_TRUE(verifyData(*segment));
+ deleteChecksum(*segment);
+ segment.reset();
+
+ // Resetting to mapped file 2 in read-only mode should fail
+ EXPECT_THROW({
+ ztable_segment_->reset(ZoneTableSegment::READ_ONLY, config_params2_);
+ }, ResetFailed);
+
+ EXPECT_TRUE(ztable_segment_->isUsable());
+ EXPECT_TRUE(ztable_segment_->isWritable());
+ // Check for the old data in the segment to make sure it is still
+ // available and correct.
+ EXPECT_TRUE(verifyData(ztable_segment_->getMemorySegment()));
+}
+
+TEST_F(ZoneTableSegmentMappedTest, resetFailedMissingHeader) {
+ setupMappedFiles();
+
+ // Open mapped file 1 in read-write mode
+ ztable_segment_->reset(ZoneTableSegment::READ_WRITE, config_params_);
+
+ // Corrupt mapped file 2.
+ scoped_ptr<MemorySegmentMapped> segment
+ (new MemorySegmentMapped(mapped_file2,
+ MemorySegmentMapped::OPEN_OR_CREATE));
+ EXPECT_TRUE(verifyData(*segment));
+ deleteHeader(*segment);
+ segment.reset();
+
+ // Resetting to mapped file 2 in read-only mode should fail
+ EXPECT_THROW({
+ ztable_segment_->reset(ZoneTableSegment::READ_ONLY, config_params2_);
+ }, ResetFailed);
+
+ EXPECT_TRUE(ztable_segment_->isUsable());
+ EXPECT_TRUE(ztable_segment_->isWritable());
+ // Check for the old data in the segment to make sure it is still
+ // available and correct.
+ EXPECT_TRUE(verifyData(ztable_segment_->getMemorySegment()));
+}
+
+TEST_F(ZoneTableSegmentMappedTest, resetCreateOverCorruptedFile) {
+ setupMappedFiles();
+
+ // Corrupt mapped file 1.
+ scoped_ptr<MemorySegmentMapped> segment
+ (new MemorySegmentMapped(mapped_file,
+ MemorySegmentMapped::OPEN_OR_CREATE));
+ EXPECT_TRUE(verifyData(*segment));
+ corruptChecksum(*segment);
+ segment.reset();
+
+ // Resetting to mapped file 1 in CREATE mode over a corrupted file
+ // should pass.
+ EXPECT_NO_THROW(ztable_segment_->reset(ZoneTableSegment::CREATE,
+ config_params_));
+
+ EXPECT_TRUE(ztable_segment_->isUsable());
+ EXPECT_TRUE(ztable_segment_->isWritable());
+ // Check for the old data in the segment. It should not be present
+ // (as we opened the segment in CREATE mode).
+ EXPECT_FALSE(verifyData(ztable_segment_->getMemorySegment()));
+
+ // Now try the same with missing checksum.
+ setupMappedFiles();
+
+ // Corrupt mapped file 1.
+ segment.reset(new MemorySegmentMapped(mapped_file,
+ MemorySegmentMapped::OPEN_OR_CREATE));
+ EXPECT_TRUE(verifyData(*segment));
+ deleteChecksum(*segment);
+ segment.reset();
+
+ // Resetting to mapped file 1 in CREATE mode over a file missing
+ // checksum should pass.
+ EXPECT_NO_THROW(ztable_segment_->reset(ZoneTableSegment::CREATE,
+ config_params_));
+
+ EXPECT_TRUE(ztable_segment_->isUsable());
+ EXPECT_TRUE(ztable_segment_->isWritable());
+ // Check for the old data in the segment. It should not be present
+ // (as we opened the segment in CREATE mode).
+ EXPECT_FALSE(verifyData(ztable_segment_->getMemorySegment()));
+}
+
+TEST_F(ZoneTableSegmentMappedTest, resetHeaderUninitialized) {
+ // This should throw as we haven't called reset() yet.
+ EXPECT_THROW(ztable_segment_->resetHeader(), isc::InvalidOperation);
+}
+
+TEST_F(ZoneTableSegmentMappedTest, resetHeader) {
+ // First, open an underlying mapped file in read+write mode (doesn't
+ // exist yet)
+ ztable_segment_->reset(ZoneTableSegment::READ_WRITE, config_params_);
+
+ // Check if a valid ZoneTable is found.
+ {
+ const ZoneTableHeader& header = ztable_segment_->getHeader();
+ const ZoneTable* table = header.getTable();
+ EXPECT_EQ(0, table->getZoneCount());
+ }
+
+ // Grow the segment by allocating something large.
+ EXPECT_THROW(ztable_segment_->getMemorySegment().allocate(1<<16),
+ MemorySegmentGrown);
+
+ // Reset the header address. This should not throw now.
+ EXPECT_NO_THROW(ztable_segment_->resetHeader());
+
+ // Check if a valid ZoneTable is found.
+ {
+ const ZoneTableHeader& header = ztable_segment_->getHeader();
+ const ZoneTable* table = header.getTable();
+ EXPECT_EQ(0, table->getZoneCount());
+ }
+}
+
+} // anonymous namespace
diff --git a/src/lib/datasrc/tests/memory/zone_table_segment_mock.h b/src/lib/datasrc/tests/memory/zone_table_segment_mock.h
new file mode 100644
index 0000000..bd10c1e
--- /dev/null
+++ b/src/lib/datasrc/tests/memory/zone_table_segment_mock.h
@@ -0,0 +1,99 @@
+// Copyright (C) 2012 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 DATASRC_MEMORY_ZONE_TABLE_SEGMENT_TEST_H
+#define DATASRC_MEMORY_ZONE_TABLE_SEGMENT_TEST_H 1
+
+#include <datasrc/memory/zone_table_segment.h>
+#include <datasrc/memory/zone_table.h>
+#include <datasrc/memory/zone_data.h>
+#include <datasrc/memory/zone_writer.h>
+
+#include <string>
+
+namespace isc {
+namespace datasrc {
+namespace memory {
+namespace test {
+
+// A special ZoneTableSegment that can be used for tests. It can be
+// passed a MemorySegment that can be used later to test if all memory
+// was de-allocated on it.
+class ZoneTableSegmentMock : public ZoneTableSegment {
+public:
+ ZoneTableSegmentMock(const isc::dns::RRClass& rrclass,
+ isc::util::MemorySegment& mem_sgmt) :
+ ZoneTableSegment(rrclass),
+ impl_type_("mock"),
+ mem_sgmt_(mem_sgmt),
+ header_(ZoneTable::create(mem_sgmt_, rrclass))
+ {}
+
+ virtual ~ZoneTableSegmentMock() {
+ ZoneTable::destroy(mem_sgmt_, header_.getTable());
+ }
+
+ const std::string& getImplType() const {
+ return (impl_type_);
+ }
+
+ virtual void reset(MemorySegmentOpenMode, isc::data::ConstElementPtr) {
+ isc_throw(isc::NotImplemented, "reset() is not implemented");
+ }
+
+ virtual void clear() {
+ isc_throw(isc::NotImplemented, "clear() is not implemented");
+ }
+
+ virtual void resetHeader() {
+ // This method does not have to do anything in this
+ // implementation.
+ }
+
+ virtual ZoneTableHeader& getHeader() {
+ return (header_);
+ }
+
+ virtual const ZoneTableHeader& getHeader() const {
+ return (header_);
+ }
+
+ virtual isc::util::MemorySegment& getMemorySegment() {
+ return (mem_sgmt_);
+ }
+
+ virtual bool isUsable() const {
+ return (true);
+ }
+
+ virtual bool isWritable() const {
+ return (true);
+ }
+
+private:
+ std::string impl_type_;
+ isc::util::MemorySegment& mem_sgmt_;
+ ZoneTableHeader header_;
+};
+
+} // namespace test
+} // namespace memory
+} // namespace datasrc
+} // namespace isc
+
+#endif // DATASRC_MEMORY_ZONE_TABLE_SEGMENT_TEST_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/datasrc/tests/memory/zone_table_segment_test.h b/src/lib/datasrc/tests/memory/zone_table_segment_test.h
deleted file mode 100644
index 2078036..0000000
--- a/src/lib/datasrc/tests/memory/zone_table_segment_test.h
+++ /dev/null
@@ -1,116 +0,0 @@
-// Copyright (C) 2012 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 DATASRC_MEMORY_ZONE_TABLE_SEGMENT_TEST_H
-#define DATASRC_MEMORY_ZONE_TABLE_SEGMENT_TEST_H 1
-
-#include <datasrc/memory/zone_table_segment.h>
-#include <datasrc/memory/zone_table.h>
-#include <datasrc/memory/zone_data.h>
-#include <datasrc/memory/zone_writer_local.h>
-
-namespace isc {
-namespace datasrc {
-namespace memory {
-namespace test {
-
-// A special ZoneTableSegment that can be used for tests. It can be
-// passed a MemorySegment that can be used later to test if all memory
-// was de-allocated on it.
-class ZoneTableSegmentTest : public ZoneTableSegment {
-public:
- ZoneTableSegmentTest(isc::dns::RRClass rrclass,
- isc::util::MemorySegment& mem_sgmt) :
- ZoneTableSegment(rrclass),
- mem_sgmt_(mem_sgmt),
- header_(ZoneTable::create(mem_sgmt_, rrclass))
- {}
-
- virtual ~ZoneTableSegmentTest() {
- ZoneTable::destroy(mem_sgmt_, header_.getTable());
- }
-
- virtual ZoneTableHeader& getHeader() {
- return (header_);
- }
-
- virtual const ZoneTableHeader& getHeader() const {
- return (header_);
- }
-
- virtual isc::util::MemorySegment& getMemorySegment() {
- return (mem_sgmt_);
- }
-
- virtual ZoneWriter* getZoneWriter(const LoadAction& load_action,
- const dns::Name& name,
- const dns::RRClass& rrclass)
- {
- return (new Writer(this, load_action, name, rrclass));
- }
-
-private:
- isc::util::MemorySegment& mem_sgmt_;
- ZoneTableHeader header_;
-
- // A writer for this segment. The implementation is similar
- // to ZoneWriterLocal, but all the error handling is stripped
- // for simplicity. Also, we do everything inside the
- // install(), for the same reason. We just need something
- // inside the tests, not a full-blown implementation
- // for background loading.
- class Writer : public ZoneWriter {
- public:
- Writer(ZoneTableSegmentTest* segment, const LoadAction& load_action,
- const dns::Name& name, const dns::RRClass& rrclass) :
- segment_(segment),
- load_action_(load_action),
- name_(name),
- rrclass_(rrclass)
- {}
-
- void load() {}
-
- void install() {
- ZoneTable* table(segment_->getHeader().getTable());
- const ZoneTable::AddResult
- result(table->addZone(segment_->getMemorySegment(), rrclass_,
- name_,
- load_action_(segment_->
- getMemorySegment())));
- if (result.zone_data != NULL) {
- ZoneData::destroy(segment_->getMemorySegment(),
- result.zone_data, rrclass_);
- }
- }
-
- virtual void cleanup() {}
- private:
- ZoneTableSegmentTest* segment_;
- LoadAction load_action_;
- dns::Name name_;
- dns::RRClass rrclass_;
- };
-};
-
-} // namespace test
-} // namespace memory
-} // namespace datasrc
-} // namespace isc
-
-#endif // DATASRC_MEMORY_ZONE_TABLE_SEGMENT_TEST_H
-
-// Local Variables:
-// mode: c++
-// End:
diff --git a/src/lib/datasrc/tests/memory/zone_table_segment_unittest.cc b/src/lib/datasrc/tests/memory/zone_table_segment_unittest.cc
index ac114e2..f0a07da 100644
--- a/src/lib/datasrc/tests/memory/zone_table_segment_unittest.cc
+++ b/src/lib/datasrc/tests/memory/zone_table_segment_unittest.cc
@@ -12,9 +12,8 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-#include <datasrc/memory/zone_writer_local.h>
+#include <datasrc/memory/zone_writer.h>
#include <datasrc/memory/zone_table_segment_local.h>
-#include <util/memory_segment_local.h>
#include <gtest/gtest.h>
#include <boost/scoped_ptr.hpp>
@@ -31,8 +30,7 @@ namespace {
class ZoneTableSegmentTest : public ::testing::Test {
protected:
ZoneTableSegmentTest() :
- ztable_segment_(ZoneTableSegment::create(isc::data::NullElement(),
- RRClass::IN()))
+ ztable_segment_(ZoneTableSegment::create(RRClass::IN(), "local"))
{}
void TearDown() {
@@ -43,10 +41,32 @@ protected:
ZoneTableSegment* ztable_segment_;
};
+TEST_F(ZoneTableSegmentTest, getImplType) {
+ EXPECT_EQ("local", ztable_segment_->getImplType());
+}
TEST_F(ZoneTableSegmentTest, create) {
// By default, a local zone table segment is created.
EXPECT_NE(static_cast<void*>(NULL), ztable_segment_);
+
+ // Unknown types of segment are rejected.
+ EXPECT_THROW(ZoneTableSegment::create(RRClass::IN(), "unknown"),
+ UnknownSegmentType);
+}
+
+TEST_F(ZoneTableSegmentTest, reset) {
+ // reset() should throw that it's not implemented so that any
+ // accidental calls are found out.
+ EXPECT_THROW({
+ ztable_segment_->reset(ZoneTableSegment::CREATE,
+ Element::fromJSON("{}"));
+ }, isc::NotImplemented);
+}
+
+TEST_F(ZoneTableSegmentTest, clear) {
+ // clear() should throw that it's not implemented so that any
+ // accidental calls are found out.
+ EXPECT_THROW(ztable_segment_->clear(), isc::NotImplemented);
}
// Helper function to check const and non-const methods.
@@ -68,6 +88,19 @@ TEST_F(ZoneTableSegmentTest, getHeader) {
// const version.
testGetHeader<const ZoneTableSegment, const ZoneTableHeader,
const ZoneTable>(ztable_segment_);
+
+ // This is a nop for local segments.
+ ztable_segment_->resetHeader();
+
+ // The following still behave as before after resetHeader().
+
+ // non-const version.
+ testGetHeader<ZoneTableSegment, ZoneTableHeader, ZoneTable>
+ (ztable_segment_);
+
+ // const version.
+ testGetHeader<const ZoneTableSegment, const ZoneTableHeader,
+ const ZoneTable>(ztable_segment_);
}
TEST_F(ZoneTableSegmentTest, getMemorySegment) {
@@ -76,22 +109,14 @@ TEST_F(ZoneTableSegmentTest, getMemorySegment) {
mem_sgmt.allMemoryDeallocated(); // use mem_sgmt
}
-ZoneData*
-loadAction(MemorySegment&) {
- // The function won't be called, so this is OK
- return (NULL);
+TEST_F(ZoneTableSegmentTest, isUsable) {
+ // Local segments are always usable.
+ EXPECT_TRUE(ztable_segment_->isUsable());
}
-// Test we can get a writer.
-TEST_F(ZoneTableSegmentTest, getZoneWriter) {
- scoped_ptr<ZoneWriter>
- writer(ztable_segment_->getZoneWriter(loadAction, Name("example.org"),
- RRClass::IN()));
- // We have to get something
- EXPECT_NE(static_cast<void*>(NULL), writer.get());
- // And for now, it should be the local writer
- EXPECT_NE(static_cast<void*>(NULL),
- dynamic_cast<ZoneWriterLocal*>(writer.get()));
+TEST_F(ZoneTableSegmentTest, isWritable) {
+ // Local segments are always writable.
+ EXPECT_TRUE(ztable_segment_->isWritable());
}
} // anonymous namespace
diff --git a/src/lib/datasrc/tests/memory/zone_table_unittest.cc b/src/lib/datasrc/tests/memory/zone_table_unittest.cc
index 3c53a59..300a50e 100644
--- a/src/lib/datasrc/tests/memory/zone_table_unittest.cc
+++ b/src/lib/datasrc/tests/memory/zone_table_unittest.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-#include "memory_segment_test.h"
-
#include <exceptions/exceptions.h>
#include <util/memory_segment_local.h>
@@ -26,6 +24,8 @@
#include <datasrc/memory/zone_table.h>
#include <datasrc/memory/segment_object_holder.h>
+#include <datasrc/tests/memory/memory_segment_mock.h>
+
#include <gtest/gtest.h>
#include <new> // for bad_alloc
@@ -56,7 +56,7 @@ protected:
}
const RRClass zclass_;
const Name zname1, zname2, zname3;
- test::MemorySegmentTest mem_sgmt_;
+ test::MemorySegmentMock mem_sgmt_;
ZoneTable* zone_table;
};
@@ -70,12 +70,17 @@ TEST_F(ZoneTableTest, create) {
}
TEST_F(ZoneTableTest, addZone) {
+ // By default there's no zone contained.
+ EXPECT_EQ(0, zone_table->getZoneCount());
+
// It doesn't accept empty (NULL) zones
EXPECT_THROW(zone_table->addZone(mem_sgmt_, zclass_, zname1, NULL),
isc::BadValue);
+ EXPECT_EQ(0, zone_table->getZoneCount()); // count is still 0
SegmentObjectHolder<ZoneData, RRClass> holder1(
- mem_sgmt_, ZoneData::create(mem_sgmt_, zname1), zclass_);
+ mem_sgmt_, zclass_);
+ 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_,
@@ -85,10 +90,12 @@ TEST_F(ZoneTableTest, addZone) {
EXPECT_EQ(static_cast<const ZoneData*>(NULL), result1.zone_data);
// It got released by it
EXPECT_EQ(static_cast<const ZoneData*>(NULL), holder1.get());
+ EXPECT_EQ(1, zone_table->getZoneCount()); // count is now incremented
// Duplicate add doesn't replace the existing data.
SegmentObjectHolder<ZoneData, RRClass> holder2(
- mem_sgmt_, ZoneData::create(mem_sgmt_, zname1), zclass_);
+ mem_sgmt_, zclass_);
+ holder2.set(ZoneData::create(mem_sgmt_, zname1));
const ZoneTable::AddResult result2(zone_table->addZone(mem_sgmt_, zclass_,
zname1,
holder2.release()));
@@ -99,10 +106,11 @@ TEST_F(ZoneTableTest, addZone) {
EXPECT_EQ(static_cast<const ZoneData*>(NULL), holder2.get());
// We need to release the old one manually
ZoneData::destroy(mem_sgmt_, result2.zone_data, zclass_);
+ EXPECT_EQ(1, zone_table->getZoneCount()); // count doesn't change.
SegmentObjectHolder<ZoneData, RRClass> holder3(
- mem_sgmt_, ZoneData::create(mem_sgmt_, Name("EXAMPLE.COM")),
- zclass_);
+ 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_,
Name("EXAMPLE.COM"),
@@ -111,21 +119,26 @@ TEST_F(ZoneTableTest, addZone) {
ZoneData::destroy(mem_sgmt_, result3.zone_data, zclass_);
// Add some more different ones. Should just succeed.
SegmentObjectHolder<ZoneData, RRClass> holder4(
- mem_sgmt_, ZoneData::create(mem_sgmt_, zname2), zclass_);
+ mem_sgmt_, zclass_);
+ holder4.set(ZoneData::create(mem_sgmt_, zname2));
EXPECT_EQ(result::SUCCESS,
zone_table->addZone(mem_sgmt_, zclass_, zname2,
holder4.release()).code);
+ EXPECT_EQ(2, zone_table->getZoneCount());
SegmentObjectHolder<ZoneData, RRClass> holder5(
- mem_sgmt_, ZoneData::create(mem_sgmt_, zname3), zclass_);
+ mem_sgmt_, zclass_);
+ holder5.set(ZoneData::create(mem_sgmt_, zname3));
EXPECT_EQ(result::SUCCESS,
zone_table->addZone(mem_sgmt_, zclass_, 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()).
SegmentObjectHolder<ZoneData, RRClass> holder6(
- mem_sgmt_, ZoneData::create(mem_sgmt_, Name("example.org")), zclass_);
+ 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()),
@@ -134,17 +147,20 @@ TEST_F(ZoneTableTest, addZone) {
TEST_F(ZoneTableTest, findZone) {
SegmentObjectHolder<ZoneData, RRClass> holder1(
- mem_sgmt_, ZoneData::create(mem_sgmt_, zname1), zclass_);
+ 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,
holder1.release()).code);
SegmentObjectHolder<ZoneData, RRClass> holder2(
- mem_sgmt_, ZoneData::create(mem_sgmt_, zname2), zclass_);
+ mem_sgmt_, zclass_);
+ holder2.set(ZoneData::create(mem_sgmt_, zname2));
EXPECT_EQ(result::SUCCESS,
zone_table->addZone(mem_sgmt_, zclass_, zname2,
holder2.release()).code);
SegmentObjectHolder<ZoneData, RRClass> holder3(
- mem_sgmt_, ZoneData::create(mem_sgmt_, zname3), zclass_);
+ mem_sgmt_, zclass_);
+ holder3.set(ZoneData::create(mem_sgmt_, zname3));
EXPECT_EQ(result::SUCCESS,
zone_table->addZone(mem_sgmt_, zclass_, zname3,
holder3.release()).code);
@@ -169,7 +185,8 @@ TEST_F(ZoneTableTest, findZone) {
// make sure the partial match is indeed the longest match by adding
// a zone with a shorter origin and query again.
SegmentObjectHolder<ZoneData, RRClass> holder4(
- mem_sgmt_, ZoneData::create(mem_sgmt_, Name("com")), zclass_);
+ mem_sgmt_, zclass_);
+ holder4.set(ZoneData::create(mem_sgmt_, Name("com")));
EXPECT_EQ(result::SUCCESS, zone_table->addZone(mem_sgmt_, zclass_,
Name("com"),
holder4.release()).code);
diff --git a/src/lib/datasrc/tests/memory/zone_writer_unittest.cc b/src/lib/datasrc/tests/memory/zone_writer_unittest.cc
index 13bcc3b..f4f2295 100644
--- a/src/lib/datasrc/tests/memory/zone_writer_unittest.cc
+++ b/src/lib/datasrc/tests/memory/zone_writer_unittest.cc
@@ -12,14 +12,16 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-#include <datasrc/memory/zone_writer_local.h>
+#include <datasrc/memory/zone_writer.h>
#include <datasrc/memory/zone_table_segment_local.h>
#include <datasrc/memory/zone_data.h>
-#include <cc/data.h>
#include <dns/rrclass.h>
#include <dns/name.h>
+#include <datasrc/tests/memory/memory_segment_mock.h>
+#include <datasrc/tests/memory/zone_table_segment_mock.h>
+
#include <gtest/gtest.h>
#include <boost/scoped_ptr.hpp>
@@ -30,41 +32,52 @@ using boost::bind;
using isc::dns::RRClass;
using isc::dns::Name;
using namespace isc::datasrc::memory;
+using namespace isc::datasrc::memory::test;
namespace {
class TestException {};
-class ZoneWriterLocalTest : public ::testing::Test {
+class ZoneTableSegmentHelper : public ZoneTableSegmentMock {
public:
- ZoneWriterLocalTest() :
- // FIXME: The NullElement probably isn't the best one, but we don't
- // know how the config will look, so it just fills the argument
- // (which is currently ignored)
- segment_(ZoneTableSegment::create(isc::data::NullElement(),
- RRClass::IN())),
+ ZoneTableSegmentHelper(const isc::dns::RRClass& rrclass,
+ isc::util::MemorySegment& mem_sgmt) :
+ ZoneTableSegmentMock(rrclass, mem_sgmt),
+ reset_header_called_(false)
+ {}
+
+ virtual void resetHeader() {
+ reset_header_called_ = true;
+ }
+
+ bool reset_header_called_;
+};
+
+class ZoneWriterTest : public ::testing::Test {
+protected:
+ ZoneWriterTest() :
+ segment_(new ZoneTableSegmentHelper(RRClass::IN(), mem_sgmt_)),
writer_(new
- ZoneWriterLocal(dynamic_cast<ZoneTableSegmentLocal*>(segment_.
- get()),
- bind(&ZoneWriterLocalTest::loadAction, this, _1),
- Name("example.org"), RRClass::IN())),
+ ZoneWriter(*segment_,
+ bind(&ZoneWriterTest::loadAction, this, _1),
+ Name("example.org"), RRClass::IN())),
load_called_(false),
load_throw_(false),
load_null_(false),
load_data_(false)
{}
- void TearDown() {
+ virtual void TearDown() {
// Release the writer
writer_.reset();
}
-protected:
- scoped_ptr<ZoneTableSegment> segment_;
- scoped_ptr<ZoneWriterLocal> writer_;
+ MemorySegmentMock mem_sgmt_;
+ scoped_ptr<ZoneTableSegmentHelper> segment_;
+ scoped_ptr<ZoneWriter> writer_;
bool load_called_;
bool load_throw_;
bool load_null_;
bool load_data_;
-private:
+public:
ZoneData* loadAction(isc::util::MemorySegment& segment) {
// Make sure it is the correct segment passed. We know the
// exact instance, can compare pointers to them.
@@ -91,38 +104,76 @@ private:
}
};
+class ReadOnlySegment : public ZoneTableSegmentMock {
+public:
+ ReadOnlySegment(const isc::dns::RRClass& rrclass,
+ isc::util::MemorySegment& mem_sgmt) :
+ ZoneTableSegmentMock(rrclass, mem_sgmt)
+ {}
+
+ // Returns false indicating that the segment is not usable. We
+ // override this too as ZoneTableSegment implementations may use it
+ // internally.
+ virtual bool isUsable() const {
+ return (false);
+ }
+
+ // Returns false indicating it is a read-only segment. It is used in
+ // the ZoneWriter tests.
+ virtual bool isWritable() const {
+ return (false);
+ }
+};
+
+TEST_F(ZoneWriterTest, constructForReadOnlySegment) {
+ MemorySegmentMock mem_sgmt;
+ ReadOnlySegment ztable_segment(RRClass::IN(), mem_sgmt);
+ EXPECT_THROW(ZoneWriter(ztable_segment,
+ bind(&ZoneWriterTest::loadAction, this, _1),
+ Name("example.org"), RRClass::IN()),
+ isc::InvalidOperation);
+}
+
// We call it the way we are supposed to, check every callback is called in the
// right moment.
-TEST_F(ZoneWriterLocalTest, correctCall) {
+TEST_F(ZoneWriterTest, correctCall) {
// Nothing called before we call it
EXPECT_FALSE(load_called_);
+ EXPECT_FALSE(segment_->reset_header_called_);
// Just the load gets called now
EXPECT_NO_THROW(writer_->load());
EXPECT_TRUE(load_called_);
+ EXPECT_TRUE(segment_->reset_header_called_);
load_called_ = false;
+ segment_->reset_header_called_ = false;
EXPECT_NO_THROW(writer_->install());
EXPECT_FALSE(load_called_);
+ EXPECT_TRUE(segment_->reset_header_called_);
// We don't check explicitly how this works, but call it to free memory. If
// everything is freed should be checked inside the TearDown.
EXPECT_NO_THROW(writer_->cleanup());
}
-TEST_F(ZoneWriterLocalTest, loadTwice) {
+TEST_F(ZoneWriterTest, loadTwice) {
// Load it the first time
EXPECT_NO_THROW(writer_->load());
EXPECT_TRUE(load_called_);
+ EXPECT_TRUE(segment_->reset_header_called_);
load_called_ = false;
+ segment_->reset_header_called_ = false;
// The second time, it should not be possible
EXPECT_THROW(writer_->load(), isc::InvalidOperation);
EXPECT_FALSE(load_called_);
+ EXPECT_FALSE(segment_->reset_header_called_);
// The object should not be damaged, try installing and clearing now
EXPECT_NO_THROW(writer_->install());
EXPECT_FALSE(load_called_);
+ EXPECT_TRUE(segment_->reset_header_called_);
// We don't check explicitly how this works, but call it to free memory. If
// everything is freed should be checked inside the TearDown.
@@ -131,25 +182,28 @@ TEST_F(ZoneWriterLocalTest, loadTwice) {
// Try loading after call to install and call to cleanup. Both is
// forbidden.
-TEST_F(ZoneWriterLocalTest, loadLater) {
+TEST_F(ZoneWriterTest, loadLater) {
// Load first, so we can install
EXPECT_NO_THROW(writer_->load());
EXPECT_NO_THROW(writer_->install());
// Reset so we see nothing is called now
load_called_ = false;
+ segment_->reset_header_called_ = false;
EXPECT_THROW(writer_->load(), isc::InvalidOperation);
EXPECT_FALSE(load_called_);
+ EXPECT_FALSE(segment_->reset_header_called_);
// Cleanup and try loading again. Still shouldn't work.
EXPECT_NO_THROW(writer_->cleanup());
EXPECT_THROW(writer_->load(), isc::InvalidOperation);
EXPECT_FALSE(load_called_);
+ EXPECT_FALSE(segment_->reset_header_called_);
}
// Try calling install at various bad times
-TEST_F(ZoneWriterLocalTest, invalidInstall) {
+TEST_F(ZoneWriterTest, invalidInstall) {
// Nothing loaded yet
EXPECT_THROW(writer_->install(), isc::InvalidOperation);
EXPECT_FALSE(load_called_);
@@ -166,7 +220,7 @@ TEST_F(ZoneWriterLocalTest, invalidInstall) {
// We check we can clean without installing first and nothing bad
// happens. We also misuse the testcase to check we can't install
// after cleanup.
-TEST_F(ZoneWriterLocalTest, cleanWithoutInstall) {
+TEST_F(ZoneWriterTest, cleanWithoutInstall) {
EXPECT_NO_THROW(writer_->load());
EXPECT_NO_THROW(writer_->cleanup());
@@ -177,7 +231,7 @@ TEST_F(ZoneWriterLocalTest, cleanWithoutInstall) {
}
// Test the case when load callback throws
-TEST_F(ZoneWriterLocalTest, loadThrows) {
+TEST_F(ZoneWriterTest, loadThrows) {
load_throw_ = true;
EXPECT_THROW(writer_->load(), TestException);
@@ -191,7 +245,7 @@ TEST_F(ZoneWriterLocalTest, loadThrows) {
// Check the strong exception guarantee - if it throws, nothing happened
// to the content.
-TEST_F(ZoneWriterLocalTest, retry) {
+TEST_F(ZoneWriterTest, retry) {
// First attempt fails due to some exception.
load_throw_ = true;
EXPECT_THROW(writer_->load(), TestException);
@@ -219,7 +273,7 @@ TEST_F(ZoneWriterLocalTest, retry) {
}
// Check the writer defends itsefl when load action returns NULL
-TEST_F(ZoneWriterLocalTest, loadNull) {
+TEST_F(ZoneWriterTest, loadNull) {
load_null_ = true;
EXPECT_THROW(writer_->load(), isc::InvalidOperation);
@@ -231,7 +285,7 @@ TEST_F(ZoneWriterLocalTest, loadNull) {
}
// Check the object cleans up in case we forget it.
-TEST_F(ZoneWriterLocalTest, autoCleanUp) {
+TEST_F(ZoneWriterTest, autoCleanUp) {
// Load data and forget about it. It should get released
// when the writer itself is destroyed.
EXPECT_NO_THROW(writer_->load());
diff --git a/src/lib/datasrc/tests/mock_client.cc b/src/lib/datasrc/tests/mock_client.cc
new file mode 100644
index 0000000..8cdc05d
--- /dev/null
+++ b/src/lib/datasrc/tests/mock_client.cc
@@ -0,0 +1,197 @@
+// Copyright (C) 2012 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 <datasrc/tests/mock_client.h>
+#include <datasrc/client.h>
+#include <datasrc/result.h>
+#include <datasrc/zone_iterator.h>
+#include <datasrc/exceptions.h>
+
+#include <dns/name.h>
+#include <dns/rrclass.h>
+#include <dns/rrset.h>
+#include <dns/rrttl.h>
+#include <dns/rdataclass.h>
+
+#include <cc/data.h>
+
+#include <gtest/gtest.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <vector>
+#include <set>
+#include <string>
+
+using namespace isc::dns;
+
+using boost::shared_ptr;
+using std::vector;
+using std::string;
+using std::set;
+
+namespace isc {
+namespace datasrc {
+namespace unittest {
+
+namespace {
+class Finder : public ZoneFinder {
+public:
+ Finder(const Name& origin) :
+ origin_(origin)
+ {}
+ Name getOrigin() const { return (origin_); }
+ // The rest is not to be called, so just have them
+ RRClass getClass() const {
+ isc_throw(isc::NotImplemented, "Not implemented");
+ }
+ shared_ptr<Context> find(const Name&, const RRType&,
+ const FindOptions)
+ {
+ isc_throw(isc::NotImplemented, "Not implemented");
+ }
+ shared_ptr<Context> findAll(const Name&,
+ vector<ConstRRsetPtr>&,
+ const FindOptions)
+ {
+ isc_throw(isc::NotImplemented, "Not implemented");
+ }
+ FindNSEC3Result findNSEC3(const Name&, bool) {
+ isc_throw(isc::NotImplemented, "Not implemented");
+ }
+private:
+ Name origin_;
+};
+
+class Iterator : public ZoneIterator {
+public:
+ Iterator(const Name& origin, bool include_a) :
+ origin_(origin),
+ soa_(new RRset(origin_, RRClass::IN(), RRType::SOA(),
+ RRTTL(3600)))
+ {
+ // The RData here is bogus, but it is not used to anything. There
+ // just needs to be some.
+ soa_->addRdata(rdata::generic::SOA(Name::ROOT_NAME(),
+ Name::ROOT_NAME(),
+ 0, 0, 0, 0, 0));
+ rrsets_.push_back(soa_);
+
+ RRsetPtr rrset(new RRset(origin_, RRClass::IN(), RRType::NS(),
+ RRTTL(3600)));
+ rrset->addRdata(rdata::generic::NS(Name::ROOT_NAME()));
+ rrsets_.push_back(rrset);
+
+ if (include_a) {
+ // Dummy A rrset. This is used for checking zone data
+ // after reload.
+ rrset.reset(new RRset(Name("tstzonedata").concatenate(origin_),
+ RRClass::IN(), RRType::A(),
+ RRTTL(3600)));
+ rrset->addRdata(rdata::in::A("192.0.2.1"));
+ rrsets_.push_back(rrset);
+ }
+
+ rrsets_.push_back(ConstRRsetPtr());
+
+ it_ = rrsets_.begin();
+ }
+ virtual isc::dns::ConstRRsetPtr getNextRRset() {
+ ConstRRsetPtr result = *it_;
+ ++it_;
+ return (result);
+ }
+ virtual isc::dns::ConstRRsetPtr getSOA() const {
+ return (soa_);
+ }
+private:
+ const Name origin_;
+ const RRsetPtr soa_;
+ vector<ConstRRsetPtr> rrsets_;
+ vector<ConstRRsetPtr>::const_iterator it_;
+};
+}
+
+// A test data source. It pretends it has some zones.
+
+MockDataSourceClient::MockDataSourceClient(const char* zone_names[]) :
+ have_a_(true), use_baditerator_(true)
+{
+ for (const char** zone(zone_names); *zone; ++zone) {
+ zones.insert(Name(*zone));
+ }
+}
+
+// Constructor from configuration. The list of zones will be empty, but
+// it will keep the configuration inside for further inspection.
+MockDataSourceClient::MockDataSourceClient(
+ const string& type,
+ const data::ConstElementPtr& configuration) :
+ type_(type),
+ configuration_(configuration),
+ have_a_(true), use_baditerator_(true)
+{
+ EXPECT_NE("MasterFiles", type) << "MasterFiles is a special case "
+ "and it never should be created as a data source client";
+ if (configuration_->getType() == data::Element::list) {
+ for (size_t i(0); i < configuration_->size(); ++i) {
+ zones.insert(Name(configuration_->get(i)->stringValue()));
+ }
+ }
+}
+
+DataSourceClient::FindResult
+MockDataSourceClient::findZone(const Name& name) const {
+ if (zones.empty()) {
+ return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
+ }
+ set<Name>::const_iterator it(zones.upper_bound(name));
+ if (it == zones.begin()) {
+ return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
+ }
+ --it;
+ NameComparisonResult compar(it->compare(name));
+ const ZoneFinderPtr finder(new Finder(*it));
+ switch (compar.getRelation()) {
+ case NameComparisonResult::EQUAL:
+ return (FindResult(result::SUCCESS, finder));
+ case NameComparisonResult::SUPERDOMAIN:
+ return (FindResult(result::PARTIALMATCH, finder));
+ default:
+ return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
+ }
+}
+
+// These methods are not used. They just need to be there to have
+// complete vtable.
+
+ZoneIteratorPtr
+MockDataSourceClient::getIterator(const Name& name, bool) const {
+ if (use_baditerator_ && name == Name("noiter.org")) {
+ isc_throw(isc::NotImplemented, "Asked not to be implemented");
+ } else if (use_baditerator_ && name == Name("null.org")) {
+ return (ZoneIteratorPtr());
+ } else {
+ FindResult result(findZone(name));
+ if (result.code == isc::datasrc::result::SUCCESS) {
+ return (ZoneIteratorPtr(new Iterator(name, have_a_)));
+ } else {
+ isc_throw(NoSuchZone, "No such zone");
+ }
+ }
+}
+
+} // end of unittest
+} // end of datasrc
+} // end of isc
diff --git a/src/lib/datasrc/tests/mock_client.h b/src/lib/datasrc/tests/mock_client.h
new file mode 100644
index 0000000..7a01440
--- /dev/null
+++ b/src/lib/datasrc/tests/mock_client.h
@@ -0,0 +1,83 @@
+// Copyright (C) 2012 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 <datasrc/client.h>
+
+#include <dns/dns_fwd.h>
+#include <dns/rrset.h>
+
+#include <cc/data.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <set>
+#include <vector>
+
+namespace isc {
+namespace datasrc {
+namespace unittest {
+
+// A test data source. It pretends it has some zones.
+class MockDataSourceClient : public DataSourceClient {
+public:
+ // Constructor from a list of zones.
+ MockDataSourceClient(const char* zone_names[]);
+
+ // Constructor from configuration. The list of zones will be empty, but
+ // it will keep the configuration inside for further inspection.
+ MockDataSourceClient(const std::string& type,
+ const data::ConstElementPtr& configuration);
+
+ virtual FindResult findZone(const dns::Name& name) const;
+ // These methods are not used. They just need to be there to have
+ // complete vtable.
+ virtual ZoneUpdaterPtr getUpdater(const dns::Name&, bool, bool) const {
+ isc_throw(isc::NotImplemented, "Not implemented");
+ }
+ virtual std::pair<ZoneJournalReader::Result, ZoneJournalReaderPtr>
+ getJournalReader(const dns::Name&, uint32_t, uint32_t) const
+ {
+ isc_throw(isc::NotImplemented, "Not implemented");
+ }
+ virtual ZoneIteratorPtr getIterator(const dns::Name& name, bool) const;
+ void disableA() { have_a_ = false; }
+ void enableA() { have_a_ = true; }
+ void disableBadIterator() { use_baditerator_ = false; }
+ void enableBadIterator() { use_baditerator_ = true; }
+ void eraseZone(const dns::Name& zone_name) {
+ zones.erase(zone_name);
+ }
+
+ /// \brief Dynamically add a zone to the data source.
+ ///
+ /// \return true if the zone is newly added; false if it already exists.
+ bool insertZone(const dns::Name& zone_name) {
+ return (zones.insert(zone_name).second);
+ }
+ const std::string type_;
+ const data::ConstElementPtr configuration_;
+
+private:
+ std::set<dns::Name> zones;
+ bool have_a_; // control the iterator behavior whether to include A record
+ bool use_baditerator_; // whether to use bogus zone iterators for tests
+};
+
+} // end of unittest
+} // end of datasrc
+} // end of isc
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/datasrc/tests/sqlite3_accessor_unittest.cc b/src/lib/datasrc/tests/sqlite3_accessor_unittest.cc
index c263304..ce34d25 100644
--- a/src/lib/datasrc/tests/sqlite3_accessor_unittest.cc
+++ b/src/lib/datasrc/tests/sqlite3_accessor_unittest.cc
@@ -16,7 +16,7 @@
#include <datasrc/sqlite3_accessor.h>
-#include <datasrc/data_source.h>
+#include <datasrc/exceptions.h>
#include <dns/rrclass.h>
@@ -806,7 +806,7 @@ const char* const new_data[] = {
};
const char* const deleted_data[] = {
// Existing data to be removed commonly used by some of the tests below
- "foo.bar.example.com.", "A", "192.0.2.1"
+ "foo.bar.example.com.", "A", "192.0.2.1", "com.example.bar.foo."
};
const char* const nsec3_data[DatabaseAccessor::ADD_NSEC3_COLUMN_COUNT] = {
// example NSEC3 parameters. Using "apex_hash" just as a convenient
@@ -853,6 +853,7 @@ protected:
std::string add_columns[DatabaseAccessor::ADD_COLUMN_COUNT];
std::string add_nsec3_columns[DatabaseAccessor::ADD_NSEC3_COLUMN_COUNT];
std::string del_params[DatabaseAccessor::DEL_PARAM_COUNT];
+ std::string del_nsec3_params[DatabaseAccessor::DEL_NSEC3_PARAM_COUNT];
std::string diff_params[DatabaseAccessor::DIFF_PARAM_COUNT];
vector<const char* const*> expected_stored; // placeholder for checkRecords
@@ -1192,8 +1193,9 @@ TEST_F(SQLite3Update, deleteNSEC3Record) {
// Delete it, and confirm that.
copy(nsec3_deleted_data,
- nsec3_deleted_data + DatabaseAccessor::DEL_PARAM_COUNT, del_params);
- accessor->deleteNSEC3RecordInZone(del_params);
+ nsec3_deleted_data + DatabaseAccessor::DEL_NSEC3_PARAM_COUNT,
+ del_nsec3_params);
+ accessor->deleteNSEC3RecordInZone(del_nsec3_params);
checkNSEC3Records(*accessor, zone_id, apex_hash, empty_stored);
// Commit the change, and confirm the deleted data still isn't there.
@@ -1222,6 +1224,7 @@ TEST_F(SQLite3Update, deleteNonexistent) {
// Replace the name with a non existent one, then try to delete it.
// nothing should happen.
del_params[DatabaseAccessor::DEL_NAME] = "no-such-name.example.com.";
+ del_params[DatabaseAccessor::DEL_RNAME] = "com.example.no-such-name.";
checkRecords(*accessor, zone_id, "no-such-name.example.com.",
empty_stored);
accessor->deleteRecordInZone(del_params);
@@ -1249,7 +1252,7 @@ TEST_F(SQLite3Update, invalidDelete) {
EXPECT_THROW(accessor->deleteRecordInZone(del_params), DataSourceError);
// Same for NSEC3.
- EXPECT_THROW(accessor->deleteNSEC3RecordInZone(del_params),
+ EXPECT_THROW(accessor->deleteNSEC3RecordInZone(del_nsec3_params),
DataSourceError);
}
@@ -1535,7 +1538,7 @@ TEST_F(SQLite3Update, addDiffWithUpdate) {
// the basic tests so far pass. But we check it in case we miss something.
const char* const old_a_record[] = {
- "dns01.example.com.", "A", "192.0.2.1"
+ "dns01.example.com.", "A", "192.0.2.1", "com.example.dns01."
};
const char* const new_a_record[] = {
"dns01.example.com.", "com.example.dns01.", "3600", "A", "",
@@ -1544,6 +1547,7 @@ TEST_F(SQLite3Update, addDiffWithUpdate) {
const char* const old_soa_record[] = {
"example.com.", "SOA",
"ns.example.com. admin.example.com. 1234 3600 1800 2419200 7200",
+ "com.example."
};
const char* const new_soa_record[] = {
"dns01.example.com.", "com.example.dns01.", "3600", "A", "",
diff --git a/src/lib/datasrc/tests/zone_finder_context_unittest.cc b/src/lib/datasrc/tests/zone_finder_context_unittest.cc
index a5c8a8f..fc08305 100644
--- a/src/lib/datasrc/tests/zone_finder_context_unittest.cc
+++ b/src/lib/datasrc/tests/zone_finder_context_unittest.cc
@@ -18,9 +18,13 @@
#include <dns/name.h>
#include <dns/rrclass.h>
+#include <cc/data.h>
+
#include <datasrc/zone_finder.h>
+#include <datasrc/cache_config.h>
#include <datasrc/memory/memory_client.h>
#include <datasrc/memory/zone_table_segment.h>
+#include <datasrc/memory/zone_writer.h>
#include <datasrc/database.h>
#include <datasrc/sqlite3_accessor.h>
@@ -44,6 +48,7 @@ using namespace isc::data;
using namespace isc::util;
using namespace isc::dns;
using namespace isc::datasrc;
+using isc::data::Element;
using isc::datasrc::memory::InMemoryClient;
using isc::datasrc::memory::ZoneTableSegment;
using namespace isc::testutils;
@@ -63,14 +68,23 @@ typedef DataSourceClientPtr (*ClientCreator)(RRClass, const Name&);
// Creator for the in-memory client to be tested
DataSourceClientPtr
-createInMemoryClient(RRClass zclass, const Name& zname)
-{
- const ElementPtr config(Element::fromJSON("{}"));
+createInMemoryClient(RRClass zclass, const Name& zname) {
+ const internal::CacheConfig cache_conf(
+ "MasterFiles", NULL, *Element::fromJSON(
+ "{\"cache-enable\": true,"
+ " \"params\":"
+ " {\"" + zname.toText() + "\": \"" +
+ string(TEST_ZONE_FILE) + "\"}}"), true);
shared_ptr<ZoneTableSegment> ztable_segment(
- ZoneTableSegment::create(*config, zclass));
+ ZoneTableSegment::create(zclass, cache_conf.getSegmentType()));
+ memory::ZoneWriter writer(*ztable_segment,
+ cache_conf.getLoadAction(zclass, zname),
+ zname, zclass);
+ writer.load();
+ writer.install();
+ writer.cleanup();
shared_ptr<InMemoryClient> client(new InMemoryClient(ztable_segment,
zclass));
- client->load(zname, TEST_ZONE_FILE);
return (client);
}
@@ -107,7 +121,7 @@ createSQLite3ClientWithNS(RRClass zclass, const Name& zname) {
}
// The test class. Its parameterized so we can share the test scnearios
-// for any concrete data source implementaitons.
+// for any concrete data source implementations.
class ZoneFinderContextTest :
public ::testing::TestWithParam<ClientCreator>
{
diff --git a/src/lib/datasrc/tests/zone_loader_unittest.cc b/src/lib/datasrc/tests/zone_loader_unittest.cc
index 4b42185..80d23b7 100644
--- a/src/lib/datasrc/tests/zone_loader_unittest.cc
+++ b/src/lib/datasrc/tests/zone_loader_unittest.cc
@@ -13,11 +13,13 @@
// PERFORMANCE OF THIS SOFTWARE.
#include <datasrc/zone_loader.h>
-#include <datasrc/data_source.h>
+#include <datasrc/exceptions.h>
#include <datasrc/rrset_collection_base.h>
+#include <datasrc/cache_config.h>
#include <datasrc/memory/zone_table_segment.h>
#include <datasrc/memory/memory_client.h>
+#include <datasrc/memory/zone_writer.h>
#include <dns/rrclass.h>
#include <dns/name.h>
@@ -26,16 +28,20 @@
#include <util/memory_segment_local.h>
#include <exceptions/exceptions.h>
+#include <cc/data.h>
+
#include <gtest/gtest.h>
#include <boost/shared_ptr.hpp>
#include <boost/scoped_ptr.hpp>
#include <boost/foreach.hpp>
+
#include <string>
#include <vector>
using namespace isc::dns;
using namespace isc::datasrc;
+using isc::data::Element;
using boost::shared_ptr;
using std::string;
using std::vector;
@@ -287,13 +293,41 @@ MockClient::getUpdater(const Name& name, bool replace, bool journaling) const {
class ZoneLoaderTest : public ::testing::Test {
protected:
ZoneLoaderTest() :
- rrclass_(RRClass::IN()),
- ztable_segment_(memory::ZoneTableSegment::
- create(isc::data::NullElement(), rrclass_)),
- source_client_(ztable_segment_, rrclass_)
- {}
+ rrclass_(RRClass::IN())
+ {
+ // Use ROOT_NAME as a placeholder; it will be ignored if filename is
+ // null.
+ prepareSource(Name::ROOT_NAME(), NULL);
+ }
void prepareSource(const Name& zone, const char* filename) {
- source_client_.load(zone, string(TEST_DATA_DIR) + "/" + filename);
+ // Cleanup the existing data in the right order
+ source_client_.reset();
+ ztable_segment_.reset();
+
+ // (re)configure zone table, then (re)construct the in-memory client
+ // with it.
+ string param_data;
+ if (filename) {
+ param_data = "\"" + zone.toText() + "\": \"" +
+ string(TEST_DATA_DIR) + "/" + filename + "\"";
+ }
+ const internal::CacheConfig cache_conf(
+ "MasterFiles", NULL, *Element::fromJSON(
+ "{\"cache-enable\": true,"
+ " \"params\": {" + param_data + "}}"), true);
+ ztable_segment_.reset(memory::ZoneTableSegment::create(
+ rrclass_, cache_conf.getSegmentType()));
+ if (filename) {
+ boost::scoped_ptr<memory::ZoneWriter> writer(
+ new memory::ZoneWriter(*ztable_segment_,
+ cache_conf.getLoadAction(rrclass_, zone),
+ zone, rrclass_));
+ writer->load();
+ writer->install();
+ writer->cleanup();
+ }
+ source_client_.reset(new memory::InMemoryClient(ztable_segment_,
+ rrclass_));
}
private:
const RRClass rrclass_;
@@ -305,7 +339,7 @@ private:
// But the shared pointer won't let us, will it?
shared_ptr<memory::ZoneTableSegment> ztable_segment_;
protected:
- memory::InMemoryClient source_client_;
+ boost::scoped_ptr<memory::InMemoryClient> source_client_;
// This one is mocked. It will help us see what is happening inside.
// Also, mocking it is simpler than setting up an sqlite3 client.
MockClient destination_client_;
@@ -314,7 +348,7 @@ protected:
// Use the loader to load an unsigned zone.
TEST_F(ZoneLoaderTest, copyUnsigned) {
prepareSource(Name::ROOT_NAME(), "root.zone");
- ZoneLoader loader(destination_client_, Name::ROOT_NAME(), source_client_);
+ ZoneLoader loader(destination_client_, Name::ROOT_NAME(), *source_client_);
// It gets the updater directly in the constructor
ASSERT_EQ(1, destination_client_.provided_updaters_.size());
EXPECT_EQ(Name::ROOT_NAME(), destination_client_.provided_updaters_[0]);
@@ -355,7 +389,7 @@ TEST_F(ZoneLoaderTest, copyUnsigned) {
// Try loading incrementally.
TEST_F(ZoneLoaderTest, copyUnsignedIncremental) {
prepareSource(Name::ROOT_NAME(), "root.zone");
- ZoneLoader loader(destination_client_, Name::ROOT_NAME(), source_client_);
+ ZoneLoader loader(destination_client_, Name::ROOT_NAME(), *source_client_);
// Try loading few RRs first.
loader.loadIncremental(10);
@@ -390,7 +424,7 @@ TEST_F(ZoneLoaderTest, copyUnsignedIncremental) {
TEST_F(ZoneLoaderTest, copySigned) {
prepareSource(Name("example.org"), "example.org.nsec3-signed");
ZoneLoader loader(destination_client_, Name("example.org"),
- source_client_);
+ *source_client_);
loader.load();
// All the RRs are there, including the ones in NSEC3 namespace
@@ -416,13 +450,13 @@ TEST_F(ZoneLoaderTest, copyMissingDestination) {
destination_client_.missing_zone_ = true;
prepareSource(Name::ROOT_NAME(), "root.zone");
EXPECT_THROW(ZoneLoader(destination_client_, Name::ROOT_NAME(),
- source_client_), DataSourceError);
+ *source_client_), DataSourceError);
}
// If the source zone does not exist, it throws
TEST_F(ZoneLoaderTest, copyMissingSource) {
EXPECT_THROW(ZoneLoader(destination_client_, Name::ROOT_NAME(),
- source_client_), DataSourceError);
+ *source_client_), DataSourceError);
}
// The class of the source and destination are different
@@ -430,7 +464,7 @@ TEST_F(ZoneLoaderTest, classMismatch) {
destination_client_.rrclass_ = RRClass::CH();
prepareSource(Name::ROOT_NAME(), "root.zone");
EXPECT_THROW(ZoneLoader(destination_client_, Name::ROOT_NAME(),
- source_client_), isc::InvalidParameter);
+ *source_client_), isc::InvalidParameter);
}
// Load an unsigned zone, all at once
@@ -593,7 +627,7 @@ TEST_F(ZoneLoaderTest, loadCheckWarn) {
TEST_F(ZoneLoaderTest, copyCheckWarn) {
prepareSource(Name("example.org"), "checkwarn.zone");
ZoneLoader loader(destination_client_, Name("example.org"),
- source_client_);
+ *source_client_);
EXPECT_TRUE(loader.loadIncremental(10));
// The messages go to the log. We don't have an easy way to examine them.
// But the zone was committed and contains all 3 RRs
diff --git a/src/lib/datasrc/tests/zone_table_accessor_unittest.cc b/src/lib/datasrc/tests/zone_table_accessor_unittest.cc
new file mode 100644
index 0000000..e5164b5
--- /dev/null
+++ b/src/lib/datasrc/tests/zone_table_accessor_unittest.cc
@@ -0,0 +1,112 @@
+// 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 <datasrc/zone_table_accessor_cache.h>
+#include <datasrc/cache_config.h>
+#include <datasrc/tests/mock_client.h>
+
+#include <exceptions/exceptions.h>
+
+#include <cc/data.h>
+
+#include <dns/name.h>
+
+#include <gtest/gtest.h>
+
+using namespace isc::dns;
+using namespace isc::datasrc;
+using namespace isc::datasrc::internal;
+using isc::data::Element;
+using isc::datasrc::unittest::MockDataSourceClient;
+
+namespace {
+
+// This test checks the abstract ZoneTableAccessor interface using
+// ZoneTableAccessorCache instances, thereby testing the top level interface
+// and the derived class behavior. If ZoneTableAccessorCache becomes more
+// complicated we may have to separate some test cases into dedicated test
+// fixture.
+class ZoneTableAccessorTest : public ::testing::Test {
+protected:
+ ZoneTableAccessorTest() :
+ // The paths of the zone files are dummy and don't even exist,
+ // but it doesn't matter in this test.
+ config_spec_(Element::fromJSON(
+ "{\"cache-enable\": true,"
+ " \"params\": "
+ " {\"example.com\": \"/example-com.zone\","
+ " \"example.org\": \"/example-org.zone\"}"
+ "}")),
+ cache_config_("MasterFiles", NULL, *config_spec_, true),
+ accessor_(cache_config_)
+ {}
+
+private:
+ const isc::data::ConstElementPtr config_spec_;
+ const CacheConfig cache_config_;
+protected:
+ ZoneTableAccessorCache accessor_;
+};
+
+TEST_F(ZoneTableAccessorTest, iteratorFromCache) {
+ // Confirm basic iterator behavior.
+ ZoneTableAccessor::IteratorPtr it = accessor_.getIterator();
+ ASSERT_TRUE(it);
+ EXPECT_FALSE(it->isLast());
+ EXPECT_EQ(0, it->getCurrent().index); // index is always 0 for this version
+ EXPECT_EQ(Name("example.com"), it->getCurrent().origin);
+
+ it->next();
+ EXPECT_FALSE(it->isLast());
+ EXPECT_EQ(0, it->getCurrent().index);
+ EXPECT_EQ(Name("example.org"), it->getCurrent().origin);
+
+ it->next(); // shouldn't cause disruption
+ EXPECT_TRUE(it->isLast());
+
+ // getCurrent() and next() will be rejected once iterator reaches the end
+ EXPECT_THROW(it->getCurrent(), isc::InvalidOperation);
+ EXPECT_THROW(it->next(), isc::InvalidOperation);
+}
+
+TEST_F(ZoneTableAccessorTest, emptyTable) {
+ // Empty zone table is possible, while mostly useless.
+ const CacheConfig empty_config(
+ "MasterFiles", NULL, *Element::fromJSON(
+ "{\"cache-enable\": true, \"params\": {}}"), true);
+ ZoneTableAccessorCache accessor(empty_config);
+ ZoneTableAccessor::IteratorPtr it = accessor.getIterator();
+ ASSERT_TRUE(it);
+ EXPECT_TRUE(it->isLast());
+ EXPECT_THROW(it->getCurrent(), isc::InvalidOperation);
+ EXPECT_THROW(it->next(), isc::InvalidOperation);
+}
+
+TEST_F(ZoneTableAccessorTest, disabledTable) {
+ // Zone table based on disabled cache is effectively empty.
+ const char* zones[] = { "example.org.", "example.com.", NULL };
+ MockDataSourceClient mock_client(zones);
+ const CacheConfig mock_config(
+ "mock", &mock_client, *Element::fromJSON(
+ "{\"cache-enable\": false,"
+ " \"cache-zones\": [\"example.com\", \"example.org\"]}"), true);
+ ZoneTableAccessorCache accessor(mock_config);
+ ZoneTableAccessor::IteratorPtr it = accessor.getIterator();
+ ASSERT_TRUE(it);
+ EXPECT_TRUE(it->isLast());
+ EXPECT_THROW(it->getCurrent(), isc::InvalidOperation);
+ EXPECT_THROW(it->next(), isc::InvalidOperation);
+}
+
+}
diff --git a/src/lib/datasrc/zone_finder.cc b/src/lib/datasrc/zone_finder.cc
index b4240c0..70cb8bf 100644
--- a/src/lib/datasrc/zone_finder.cc
+++ b/src/lib/datasrc/zone_finder.cc
@@ -13,8 +13,9 @@
// PERFORMANCE OF THIS SOFTWARE.
#include <datasrc/zone_finder.h>
-#include <datasrc/data_source.h>
+#include <datasrc/exceptions.h>
+#include <dns/rrclass.h>
#include <dns/rdata.h>
#include <dns/rrset.h>
#include <dns/rrtype.h>
diff --git a/src/lib/datasrc/zone_loader.cc b/src/lib/datasrc/zone_loader.cc
index d0f4a64..8044bc0 100644
--- a/src/lib/datasrc/zone_loader.cc
+++ b/src/lib/datasrc/zone_loader.cc
@@ -16,7 +16,7 @@
#include <datasrc/master_loader_callbacks.h>
#include <datasrc/client.h>
-#include <datasrc/data_source.h>
+#include <datasrc/exceptions.h>
#include <datasrc/zone_iterator.h>
#include <datasrc/zone.h>
#include <datasrc/logger.h>
diff --git a/src/lib/datasrc/zone_loader.h b/src/lib/datasrc/zone_loader.h
index 2a4559e..068dc35 100644
--- a/src/lib/datasrc/zone_loader.h
+++ b/src/lib/datasrc/zone_loader.h
@@ -15,7 +15,7 @@
#ifndef DATASRC_ZONE_LOADER_H
#define DATASRC_ZONE_LOADER_H
-#include <datasrc/data_source.h>
+#include <datasrc/exceptions.h>
#include <dns/master_loader.h>
diff --git a/src/lib/datasrc/zone_table_accessor.h b/src/lib/datasrc/zone_table_accessor.h
new file mode 100644
index 0000000..378c7eb
--- /dev/null
+++ b/src/lib/datasrc/zone_table_accessor.h
@@ -0,0 +1,212 @@
+// 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 DATASRC_ZONE_TABLE_ACCESSOR_H
+#define DATASRC_ZONE_TABLE_ACCESSOR_H
+
+#include <dns/name.h>
+
+#include <boost/noncopyable.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include <stdint.h>
+
+namespace isc {
+namespace datasrc {
+
+/// \brief Information of a zone stored in a data source zone table.
+///
+/// This is a straightforward composite type that represents an entry of
+/// the conceptual zone table referenced by \c ZoneTableAccessor.
+/// An object of this structure is specifically intended to be returned by
+/// \c ZoneTableIterator.
+///
+/// This is essentially a read-only tuple; only created by
+/// \c ZoneTableAccessor, and once created it will be immutable.
+///
+/// \note Once Trac #2144 is completed, this struct must be defined as
+/// non-assignable because it has a const member variable.
+struct ZoneSpec {
+ /// \brief Constructor.
+ ZoneSpec(uint32_t index_param, const dns::Name& origin_param) :
+ index(index_param), origin(origin_param)
+ {}
+
+ /// \brief Numeric zone index.
+ ///
+ /// In the current initial version, this field is just a placeholder.
+ /// In the future, we'll probably define it as a unique index in the table
+ /// for that particular zone so that applications can distinguish
+ /// and specify different zones efficiently. Until it's fixed, this field
+ /// shouldn't be used by applications.
+ const uint32_t index;
+
+ /// \brief The origin name of the zone.
+ const dns::Name origin;
+};
+
+/// \brief A simple iterator of zone table.
+///
+/// This is an abstract base class providing simple iteration operation
+/// over zones stored in a data source. A concrete object of this class
+/// is expected to be returned by \c ZoneTableAccessor::getIterator().
+///
+/// The interface is intentionally simplified and limited: it works
+/// "forward-only", i.e, only goes from begin to end one time; it's not
+/// copyable, assignable, nor comparable. For the latter reasons it's not
+/// compatible with standard iterator traits. It's simplified because it's
+/// not clear what kind of primitive can be used in specific data sources.
+/// In particular, iteration in a database-based data source would be very
+/// restrictive. So it's better to begin with minimal guaranteed features
+/// at the base class. If we find it possible to loosen the restriction
+/// as we implement more derived versions, we may extend the features later.
+///
+/// Likewise, this iterator does not guarantee the ordering of the zones
+/// returned by \c getCurrent(). It's probably possible to ensure some
+/// sorted order, but until we can be sure it's the case for many cases
+/// in practice, we'll not rely on it.
+///
+/// A concrete object of this class is created by specific derived
+/// implementation for the corresponding data source. The implementation
+/// must ensure the iterator is located at the "beginning" of the zone table,
+/// and that subsequent calls to \c next() go through all the zones
+/// one by one, until \c isLast() returns \c true. The implementation must
+/// support the concept of "empty table"; in that case \c isLast() will
+/// return \c true from the beginning.
+class ZoneTableIterator : boost::noncopyable {
+protected:
+ /// \brief The constructor.
+ ///
+ /// This class is not expected to be instantiated directly, so the
+ /// constructor is hidden from normal applications as protected.
+ ZoneTableIterator() {}
+
+public:
+ /// \brief The destructor.
+ virtual ~ZoneTableIterator() {}
+
+ /// \brief Return if the iterator reaches the end of the zone table.
+ virtual bool isLast() const = 0;
+
+ /// \brief Move the iterator to the next zone of the table.
+ ///
+ /// This method must not be called once the iterator reaches the end
+ /// of the table.
+ ///
+ /// \throw InvalidOperation called after reaching the end of table.
+ void next() {
+ // Perform common check, and delegate the actual work to the protected
+ // method.
+ if (isLast()) {
+ isc_throw(InvalidOperation,
+ "next() called on iterator beyond end of zone table");
+ }
+ nextImpl();
+ }
+
+ /// \brief Return the information of the zone at which the iterator is
+ /// currently located in the form of \c ZoneSpec.
+ ///
+ /// This method must not be called once the iterator reaches the end
+ /// of the zone table.
+ ///
+ /// \throw InvalidOperation called after reaching the end of table.
+ ///
+ /// \return Information of the "current" zone.
+ ZoneSpec getCurrent() const {
+ // Perform common check, and delegate the actual work to the protected
+ // method.
+ if (isLast()) {
+ isc_throw(InvalidOperation,
+ "getCurrent() called on iterator beyond "
+ "end of zone table");
+ }
+ return (getCurrentImpl());
+ }
+
+protected:
+ /// \brief Actual implementation of \c next().
+ ///
+ /// Each derived class must provide the implementation of \c next()
+ /// in its data source specific form, except for the common
+ /// validation check.
+ virtual void nextImpl() = 0;
+
+ /// \brief Actual implementation of \c getCurrent().
+ ///
+ /// Each derived class must provide the implementation of
+ /// \c getCurrent() in its data source specific form, except for the
+ /// common validation check.
+ virtual ZoneSpec getCurrentImpl() const = 0;
+};
+
+/// \brief An abstract accessor to conceptual zone table for a data source.
+///
+/// This is an abstract base class providing common interfaces to get access
+/// to a conceptual "zone table" corresponding to a specific data source.
+/// A zone table would contain a set of information about DNS zones stored in
+/// the data source. It's "conceptual" in that the actual form of the
+/// information is specific to the data source implementation.
+///
+/// The initial version of this class only provides one simple feature:
+/// iterating over the table so that an application can get a list of
+/// all zones of a specific data source (of a specific RR class). In
+/// future, this class will be extended so that, e.g., applications can
+/// add or remove zones.
+///
+/// \note It may make sense to move \c DataSourceClient::createZone()
+/// and \c DataSourceClient::deleteZone() to this class.
+class ZoneTableAccessor : boost::noncopyable {
+protected:
+ /// \brief The constructor.
+ ///
+ /// This class is not expected to be instantiated directly, so the
+ /// constructor is hidden from normal applications as protected.
+ ZoneTableAccessor() {}
+
+public:
+ /// \brief Shortcut type for a smart pointer of \c ZoneTableIterator
+ typedef boost::shared_ptr<ZoneTableIterator> IteratorPtr;
+
+ /// \brief The destructor.
+ virtual ~ZoneTableAccessor() {}
+
+ /// \brief Return a zone table iterator.
+ ///
+ /// In general, the specific implementation of the iterator object would
+ /// contain some form of reference to the underlying data source
+ /// (e.g., a database connection or a pointer to memory region), which
+ /// would be valid only until the object that created the instance of
+ /// the accessor is destroyed. The iterator must not be used beyond
+ /// the lifetime of such a creator object, and normally it's expected to
+ /// be even more ephemeral: it would be created by this method in a
+ /// single method or function and only used in that limited scope.
+ ///
+ /// \throw std::bad_alloc Memory allocation for the iterator object failed.
+ /// \throw Others There will be other cases as more implementations
+ /// are added (in this initial version, it's not really decided yet).
+ ///
+ /// \return A smart pointer to a newly created iterator object. Once
+ /// returned, the \c ZoneTableAccessor effectively releases its ownership.
+ virtual IteratorPtr getIterator() const = 0;
+};
+
+}
+}
+
+#endif // DATASRC_ZONE_TABLE_ACCESSOR_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/datasrc/zone_table_accessor_cache.cc b/src/lib/datasrc/zone_table_accessor_cache.cc
new file mode 100644
index 0000000..b1d26ac
--- /dev/null
+++ b/src/lib/datasrc/zone_table_accessor_cache.cc
@@ -0,0 +1,60 @@
+// 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 <datasrc/zone_table_accessor_cache.h>
+#include <datasrc/cache_config.h>
+
+#include <exceptions/exceptions.h>
+
+namespace isc {
+namespace datasrc {
+namespace internal {
+
+namespace {
+// This is a straightforward wrapper of CacheConfig::ConstZoneIterator to
+// implement ZoneTableIterator interfaces.
+class ZoneTableIteratorCache : public ZoneTableIterator {
+public:
+ ZoneTableIteratorCache(const CacheConfig& config) :
+ it_(config.begin()),
+ it_end_(config.end())
+ {}
+
+ virtual void nextImpl() {
+ ++it_;
+ }
+
+ virtual ZoneSpec getCurrentImpl() const {
+ return (ZoneSpec(0, it_->first)); // index is always 0 in this version.
+ }
+
+ virtual bool isLast() const {
+ return (it_ == it_end_);
+ }
+
+private:
+ CacheConfig::ConstZoneIterator it_;
+ CacheConfig::ConstZoneIterator const it_end_;
+};
+}
+
+ZoneTableAccessor::IteratorPtr
+ZoneTableAccessorCache::getIterator() const {
+ return (ZoneTableAccessor::IteratorPtr(
+ new ZoneTableIteratorCache(config_)));
+}
+
+}
+}
+}
diff --git a/src/lib/datasrc/zone_table_accessor_cache.h b/src/lib/datasrc/zone_table_accessor_cache.h
new file mode 100644
index 0000000..314a9fd
--- /dev/null
+++ b/src/lib/datasrc/zone_table_accessor_cache.h
@@ -0,0 +1,76 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef DATASRC_ZONE_TABLE_ACCESSOR_CACHE_H
+#define DATASRC_ZONE_TABLE_ACCESSOR_CACHE_H
+
+#include <datasrc/zone_table_accessor.h>
+
+#include <boost/noncopyable.hpp>
+
+namespace isc {
+namespace datasrc {
+namespace internal {
+class CacheConfig;
+
+/// \brief A \c ZoneTableAccessor implementation for in-memory cache.
+///
+/// This class implements the \c ZoneTableAccessor interface for in-memory
+/// cache. Its conceptual table consists of the zones that are specified
+/// to be loaded into memory in configuration. Note that these zones
+/// may or may not actually be loaded in memory. In fact, this class object
+/// is intended to be used by applications that load these zones into memory,
+/// so that the application can get a list of zones to be loaded. Also, even
+/// after loading, some zone may still not be loaded, e.g., due to an error
+/// in the corresponding zone file.
+///
+/// An object of this class is expected to be returned by
+/// \c ConfigurableClientList. Normal applications shouldn't instantiate
+/// this class directly. It's still defined to be publicly visible for
+/// testing purposes but, to clarify the intent, it's hidden in the
+/// "internal" namespace.
+class ZoneTableAccessorCache : public ZoneTableAccessor {
+public:
+ /// \brief Constructor.
+ ///
+ /// This class takes a \c CacheConfig object and holds it throughout
+ /// its lifetime. The caller must ensure that the configuration is
+ /// valid throughout the lifetime of this accessor object.
+ ///
+ /// \throw None
+ ///
+ /// \param config The cache configuration that the accessor refers to.
+ ZoneTableAccessorCache(const CacheConfig& config) : config_(config) {}
+
+ /// \brief In-memory cache version of \c getIterator().
+ ///
+ /// As returned from this version of iterator, \c ZoneSpec::index
+ /// will always be set to 0 at the moment.
+ ///
+ /// \throw None except std::bad_alloc in case of memory allocation failure
+ virtual IteratorPtr getIterator() const;
+
+private:
+ const CacheConfig& config_;
+};
+
+}
+}
+}
+
+#endif // DATASRC_ZONE_TABLE_ACCESSOR_CACHE_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dhcp/Makefile.am b/src/lib/dhcp/Makefile.am
index f169fe6..1e292bd 100644
--- a/src/lib/dhcp/Makefile.am
+++ b/src/lib/dhcp/Makefile.am
@@ -35,6 +35,9 @@ libb10_dhcp___la_SOURCES += option_definition.cc option_definition.h
libb10_dhcp___la_SOURCES += option_space.cc option_space.h
libb10_dhcp___la_SOURCES += pkt6.cc pkt6.h
libb10_dhcp___la_SOURCES += pkt4.cc pkt4.h
+libb10_dhcp___la_SOURCES += pkt_filter.h
+libb10_dhcp___la_SOURCES += pkt_filter_inet.cc pkt_filter_inet.h
+libb10_dhcp___la_SOURCES += pkt_filter_lpf.cc pkt_filter_lpf.h
libb10_dhcp___la_SOURCES += std_option_defs.h
libb10_dhcp___la_CXXFLAGS = $(AM_CXXFLAGS)
diff --git a/src/lib/dhcp/dhcp6.h b/src/lib/dhcp/dhcp6.h
index 874ee46..d5201a9 100644
--- a/src/lib/dhcp/dhcp6.h
+++ b/src/lib/dhcp/dhcp6.h
@@ -114,7 +114,7 @@ extern const int dhcpv6_type_name_max;
// Define hardware types
// Taken from http://www.iana.org/assignments/arp-parameters/
#define HWTYPE_ETHERNET 0x0001
-#define HWTYPE_INIFINIBAND 0x0020
+#define HWTYPE_INFINIBAND 0x0020
// Taken from http://www.iana.org/assignments/enterprise-numbers
#define ENTERPRISE_ID_ISC 2495
diff --git a/src/lib/dhcp/duid.cc b/src/lib/dhcp/duid.cc
index e0df45d..8570d26 100644
--- a/src/lib/dhcp/duid.cc
+++ b/src/lib/dhcp/duid.cc
@@ -28,15 +28,20 @@ namespace dhcp {
DUID::DUID(const std::vector<uint8_t>& duid) {
if (duid.size() > MAX_DUID_LEN) {
isc_throw(OutOfRange, "DUID too large");
- } else {
- duid_ = duid;
}
+ if (duid.empty()) {
+ isc_throw(OutOfRange, "Empty DUIDs are not allowed");
+ }
+ duid_ = duid;
}
DUID::DUID(const uint8_t* data, size_t len) {
if (len > MAX_DUID_LEN) {
isc_throw(OutOfRange, "DUID too large");
}
+ if (len == 0) {
+ isc_throw(OutOfRange, "Empty DUIDs/Client-ids not allowed");
+ }
duid_ = std::vector<uint8_t>(data, data + len);
}
@@ -83,11 +88,19 @@ bool DUID::operator!=(const DUID& other) const {
// Constructor based on vector<uint8_t>
ClientId::ClientId(const std::vector<uint8_t>& clientid)
: DUID(clientid) {
+ if (clientid.size() < MIN_CLIENT_ID_LEN) {
+ isc_throw(OutOfRange, "client-id is too short (" << clientid.size()
+ << "), at least 2 is required");
+ }
}
// Constructor based on C-style data
ClientId::ClientId(const uint8_t *clientid, size_t len)
: DUID(clientid, len) {
+ if (len < MIN_CLIENT_ID_LEN) {
+ isc_throw(OutOfRange, "client-id is too short (" << len
+ << "), at least 2 is required");
+ }
}
// Returns a copy of client-id data
diff --git a/src/lib/dhcp/duid.h b/src/lib/dhcp/duid.h
index 21c6b8f..d20a58d 100644
--- a/src/lib/dhcp/duid.h
+++ b/src/lib/dhcp/duid.h
@@ -35,6 +35,11 @@ class DUID {
/// As defined in RFC3315, section 9.1
static const size_t MAX_DUID_LEN = 128;
+ /// @brief minimum duid size
+ /// There's no explicit minimal DUID size specified in RFC3315,
+ /// so let's use absolute minimum
+ static const size_t MIN_DUID_LEN = 1;
+
/// @brief specifies DUID type
typedef enum {
DUID_UNKNOWN = 0, ///< invalid/unknown type
@@ -88,6 +93,13 @@ typedef boost::shared_ptr<DUID> DuidPtr;
/// a client-id
class ClientId : public DUID {
public:
+
+ /// @brief Minimum size of a client ID
+ ///
+ /// Excerpt from RFC2132, section 9.14.
+ /// The code for this option is 61, and its minimum length is 2.
+ static const size_t MIN_CLIENT_ID_LEN = 2;
+
/// @brief Maximum size of a client ID
///
/// This is the same as the maximum size of the underlying DUID.
diff --git a/src/lib/dhcp/hwaddr.cc b/src/lib/dhcp/hwaddr.cc
index d19f2ad..eb23b44 100644
--- a/src/lib/dhcp/hwaddr.cc
+++ b/src/lib/dhcp/hwaddr.cc
@@ -14,9 +14,11 @@
#include <dhcp/hwaddr.h>
#include <dhcp/dhcp4.h>
+#include <exceptions/exceptions.h>
#include <iomanip>
#include <sstream>
#include <vector>
+#include <string.h>
namespace isc {
namespace dhcp {
@@ -27,10 +29,17 @@ HWAddr::HWAddr()
HWAddr::HWAddr(const uint8_t* hwaddr, size_t len, uint8_t htype)
:hwaddr_(hwaddr, hwaddr + len), htype_(htype) {
+ if (len > MAX_HWADDR_LEN) {
+ isc_throw(InvalidParameter, "hwaddr length exceeds MAX_HWADDR_LEN");
+ }
}
HWAddr::HWAddr(const std::vector<uint8_t>& hwaddr, uint8_t htype)
:hwaddr_(hwaddr), htype_(htype) {
+ if (hwaddr.size() > MAX_HWADDR_LEN) {
+ isc_throw(InvalidParameter,
+ "address vector size exceeds MAX_HWADDR_LEN");
+ }
}
std::string HWAddr::toText() const {
@@ -50,7 +59,7 @@ std::string HWAddr::toText() const {
}
bool HWAddr::operator==(const HWAddr& other) const {
- return ((this->htype_ == other.htype_) &&
+ return ((this->htype_ == other.htype_) &&
(this->hwaddr_ == other.hwaddr_));
}
diff --git a/src/lib/dhcp/hwaddr.h b/src/lib/dhcp/hwaddr.h
index 93b06a1..13a16bf 100644
--- a/src/lib/dhcp/hwaddr.h
+++ b/src/lib/dhcp/hwaddr.h
@@ -27,6 +27,8 @@ namespace dhcp {
/// @brief Hardware type that represents information from DHCPv4 packet
struct HWAddr {
public:
+ /// @brief Maximum size of a hardware address.
+ static const size_t MAX_HWADDR_LEN = 20;
/// @brief default constructor
HWAddr();
diff --git a/src/lib/dhcp/iface_mgr.cc b/src/lib/dhcp/iface_mgr.cc
index a4f4659..74f5fe8 100644
--- a/src/lib/dhcp/iface_mgr.cc
+++ b/src/lib/dhcp/iface_mgr.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -22,6 +22,7 @@
#include <dhcp/dhcp4.h>
#include <dhcp/dhcp6.h>
#include <dhcp/iface_mgr.h>
+#include <dhcp/pkt_filter_inet.h>
#include <exceptions/exceptions.h>
#include <util/io/pktinfo_utilities.h>
@@ -47,7 +48,7 @@ IfaceMgr::instance() {
return (iface_mgr);
}
-IfaceMgr::Iface::Iface(const std::string& name, int ifindex)
+Iface::Iface(const std::string& name, int ifindex)
:name_(name), ifindex_(ifindex), mac_len_(0), hardware_type_(0),
flag_loopback_(false), flag_up_(false), flag_running_(false),
flag_multicast_(false), flag_broadcast_(false), flags_(0)
@@ -56,7 +57,7 @@ IfaceMgr::Iface::Iface(const std::string& name, int ifindex)
}
void
-IfaceMgr::Iface::closeSockets() {
+Iface::closeSockets() {
for (SocketCollection::iterator sock = sockets_.begin();
sock != sockets_.end(); ++sock) {
close(sock->sockfd_);
@@ -65,14 +66,14 @@ IfaceMgr::Iface::closeSockets() {
}
std::string
-IfaceMgr::Iface::getFullName() const {
+Iface::getFullName() const {
ostringstream tmp;
tmp << name_ << "/" << ifindex_;
return (tmp.str());
}
std::string
-IfaceMgr::Iface::getPlainMac() const {
+Iface::getPlainMac() const {
ostringstream tmp;
tmp.fill('0');
tmp << hex;
@@ -86,18 +87,18 @@ IfaceMgr::Iface::getPlainMac() const {
return (tmp.str());
}
-void IfaceMgr::Iface::setMac(const uint8_t* mac, size_t len) {
- if (len > IfaceMgr::MAX_MAC_LEN) {
+void Iface::setMac(const uint8_t* mac, size_t len) {
+ if (len > MAX_MAC_LEN) {
isc_throw(OutOfRange, "Interface " << getFullName()
<< " was detected to have link address of length "
<< len << ", but maximum supported length is "
- << IfaceMgr::MAX_MAC_LEN);
+ << MAX_MAC_LEN);
}
mac_len_ = len;
memcpy(mac_, mac, len);
}
-bool IfaceMgr::Iface::delAddress(const isc::asiolink::IOAddress& addr) {
+bool Iface::delAddress(const isc::asiolink::IOAddress& addr) {
for (AddressCollection::iterator a = addrs_.begin();
a!=addrs_.end(); ++a) {
if (*a==addr) {
@@ -108,7 +109,7 @@ bool IfaceMgr::Iface::delAddress(const isc::asiolink::IOAddress& addr) {
return (false);
}
-bool IfaceMgr::Iface::delSocket(uint16_t sockfd) {
+bool Iface::delSocket(uint16_t sockfd) {
list<SocketInfo>::iterator sock = sockets_.begin();
while (sock!=sockets_.end()) {
if (sock->sockfd_ == sockfd) {
@@ -124,7 +125,8 @@ bool IfaceMgr::Iface::delSocket(uint16_t sockfd) {
IfaceMgr::IfaceMgr()
:control_buf_len_(CMSG_SPACE(sizeof(struct in6_pktinfo))),
control_buf_(new char[control_buf_len_]),
- session_socket_(INVALID_SOCKET), session_callback_(NULL)
+ session_socket_(INVALID_SOCKET), session_callback_(NULL),
+ packet_filter_(new PktFilterInet())
{
try {
@@ -193,10 +195,23 @@ void IfaceMgr::stubDetectIfaces() {
addInterface(iface);
}
-bool IfaceMgr::openSockets4(const uint16_t port) {
+bool IfaceMgr::openSockets4(const uint16_t port, const bool use_bcast) {
int sock;
int count = 0;
+// This option is used to bind sockets to particular interfaces.
+// This is currently the only way to discover on which interface
+// the broadcast packet has been received. If this option is
+// not supported then only one interface should be confugured
+// to listen for broadcast traffic.
+#ifdef SO_BINDTODEVICE
+ const bool bind_to_device = true;
+#else
+ const bool bind_to_device = false;
+#endif
+
+ int bcast_num = 0;
+
for (IfaceCollection::iterator iface = ifaces_.begin();
iface != ifaces_.end();
++iface) {
@@ -207,8 +222,8 @@ bool IfaceMgr::openSockets4(const uint16_t port) {
continue;
}
- AddressCollection addrs = iface->getAddresses();
- for (AddressCollection::iterator addr = addrs.begin();
+ Iface::AddressCollection addrs = iface->getAddresses();
+ for (Iface::AddressCollection::iterator addr = addrs.begin();
addr != addrs.end();
++addr) {
@@ -217,9 +232,40 @@ bool IfaceMgr::openSockets4(const uint16_t port) {
continue;
}
- sock = openSocket(iface->getName(), *addr, port);
+ // If selected interface is broadcast capable set appropriate
+ // options on the socket so as it can receive and send broadcast
+ // messages.
+ if (iface->flag_broadcast_ && use_bcast) {
+ // If our OS supports binding socket to a device we can listen
+ // for broadcast messages on multiple interfaces. Otherwise we
+ // bind to INADDR_ANY address but we can do it only once. Thus,
+ // if one socket has been bound we can't do it any further.
+ if (!bind_to_device && bcast_num > 0) {
+ isc_throw(SocketConfigError, "SO_BINDTODEVICE socket option is"
+ << " not supported on this OS; therefore, DHCP"
+ << " server can only listen broadcast traffic on"
+ << " a single interface");
+
+ } else {
+ // We haven't open any broadcast sockets yet, so we can
+ // open at least one more.
+ sock = openSocket(iface->getName(), *addr, port, true, true);
+ // Binding socket to an interface is not supported so we can't
+ // open any more broadcast sockets. Increase the number of
+ // opened broadcast sockets.
+ if (!bind_to_device) {
+ ++bcast_num;
+ }
+ }
+
+ } else {
+ // Not broadcast capable, do not set broadcast flags.
+ sock = openSocket(iface->getName(), *addr, port, false, false);
+
+ }
if (sock < 0) {
- isc_throw(SocketConfigError, "failed to open unicast socket");
+ isc_throw(SocketConfigError, "failed to open IPv4 socket"
+ << " supporting broadcast traffic");
}
count++;
@@ -242,8 +288,8 @@ bool IfaceMgr::openSockets6(const uint16_t port) {
continue;
}
- AddressCollection addrs = iface->getAddresses();
- for (AddressCollection::iterator addr = addrs.begin();
+ Iface::AddressCollection addrs = iface->getAddresses();
+ for (Iface::AddressCollection::iterator addr = addrs.begin();
addr != addrs.end();
++addr) {
@@ -304,7 +350,7 @@ IfaceMgr::printIfaces(std::ostream& out /*= std::cout*/) {
iface!=ifaces_.end();
++iface) {
- const AddressCollection& addrs = iface->getAddresses();
+ const Iface::AddressCollection& addrs = iface->getAddresses();
out << "Detected interface " << iface->getFullName()
<< ", hwtype=" << iface->getHWType()
@@ -318,7 +364,7 @@ IfaceMgr::printIfaces(std::ostream& out /*= std::cout*/) {
<< ")" << endl;
out << " " << addrs.size() << " addr(s):";
- for (AddressCollection::const_iterator addr = addrs.begin();
+ for (Iface::AddressCollection::const_iterator addr = addrs.begin();
addr != addrs.end(); ++addr) {
out << " " << addr->toText();
}
@@ -326,7 +372,7 @@ IfaceMgr::printIfaces(std::ostream& out /*= std::cout*/) {
}
}
-IfaceMgr::Iface*
+Iface*
IfaceMgr::getIface(int ifindex) {
for (IfaceCollection::iterator iface=ifaces_.begin();
iface!=ifaces_.end();
@@ -338,7 +384,7 @@ IfaceMgr::getIface(int ifindex) {
return (NULL); // not found
}
-IfaceMgr::Iface*
+Iface*
IfaceMgr::getIface(const std::string& ifname) {
for (IfaceCollection::iterator iface=ifaces_.begin();
iface!=ifaces_.end();
@@ -351,13 +397,14 @@ IfaceMgr::getIface(const std::string& ifname) {
}
int IfaceMgr::openSocket(const std::string& ifname, const IOAddress& addr,
- const uint16_t port) {
+ const uint16_t port, const bool receive_bcast,
+ const bool send_bcast) {
Iface* iface = getIface(ifname);
if (!iface) {
isc_throw(BadValue, "There is no " << ifname << " interface present.");
}
if (addr.isV4()) {
- return openSocket4(*iface, addr, port);
+ return openSocket4(*iface, addr, port, receive_bcast, send_bcast);
} else if (addr.isV6()) {
return openSocket6(*iface, addr, port);
@@ -383,8 +430,8 @@ int IfaceMgr::openSocketFromIface(const std::string& ifname,
// Interface is now detected. Search for address on interface
// that matches address family (v6 or v4).
- AddressCollection addrs = iface->getAddresses();
- AddressCollection::iterator addr_it = addrs.begin();
+ Iface::AddressCollection addrs = iface->getAddresses();
+ Iface::AddressCollection::iterator addr_it = addrs.begin();
while (addr_it != addrs.end()) {
if (addr_it->getFamily() == family) {
// We have interface and address so let's open socket.
@@ -420,9 +467,9 @@ int IfaceMgr::openSocketFromAddress(const IOAddress& addr,
iface != ifaces_.end();
++iface) {
- AddressCollection addrs = iface->getAddresses();
+ Iface::AddressCollection addrs = iface->getAddresses();
- for (AddressCollection::iterator addr_it = addrs.begin();
+ for (Iface::AddressCollection::iterator addr_it = addrs.begin();
addr_it != addrs.end();
++addr_it) {
@@ -509,43 +556,6 @@ IfaceMgr::getLocalAddress(const IOAddress& remote_addr, const uint16_t port) {
return IOAddress(local_address);
}
-int IfaceMgr::openSocket4(Iface& iface, const IOAddress& addr, uint16_t port) {
-
- struct sockaddr_in addr4;
- memset(&addr4, 0, sizeof(sockaddr));
- addr4.sin_family = AF_INET;
- addr4.sin_port = htons(port);
-
- addr4.sin_addr.s_addr = htonl(addr);
- //addr4.sin_addr.s_addr = 0; // anyaddr: this will receive 0.0.0.0 => 255.255.255.255 traffic
- // addr4.sin_addr.s_addr = 0xffffffffu; // broadcast address. This will receive 0.0.0.0 => 255.255.255.255 as well
-
- int sock = socket(AF_INET, SOCK_DGRAM, 0);
- if (sock < 0) {
- isc_throw(SocketConfigError, "Failed to create UDP6 socket.");
- }
-
- if (bind(sock, (struct sockaddr *)&addr4, sizeof(addr4)) < 0) {
- close(sock);
- isc_throw(SocketConfigError, "Failed to bind socket " << sock << " to " << addr.toText()
- << "/port=" << port);
- }
-
- // if there is no support for IP_PKTINFO, we are really out of luck
- // it will be difficult to undersand, where this packet came from
-#if defined(IP_PKTINFO)
- int flag = 1;
- if (setsockopt(sock, IPPROTO_IP, IP_PKTINFO, &flag, sizeof(flag)) != 0) {
- close(sock);
- isc_throw(SocketConfigError, "setsockopt: IP_PKTINFO: failed.");
- }
-#endif
-
- SocketInfo info(sock, addr, port);
- iface.addSocket(info);
-
- return (sock);
-}
int IfaceMgr::openSocket6(Iface& iface, const IOAddress& addr, uint16_t port) {
@@ -620,6 +630,22 @@ int IfaceMgr::openSocket6(Iface& iface, const IOAddress& addr, uint16_t port) {
return (sock);
}
+int IfaceMgr::openSocket4(Iface& iface, const IOAddress& addr,
+ const uint16_t port, const bool receive_bcast,
+ const bool send_bcast) {
+
+ // Skip checking if the packet_filter_ is non-NULL because this check
+ // has been already done when packet filter object was set.
+
+ int sock = packet_filter_->openSocket(iface, addr, port,
+ receive_bcast, send_bcast);
+
+ SocketInfo info(sock, addr, port);
+ iface.addSocket(info);
+
+ return (sock);
+}
+
bool
IfaceMgr::joinMulticast(int sock, const std::string& ifname,
const std::string & mcast) {
@@ -722,53 +748,17 @@ IfaceMgr::send(const Pkt6Ptr& pkt) {
}
bool
-IfaceMgr::send(const Pkt4Ptr& pkt)
-{
+IfaceMgr::send(const Pkt4Ptr& pkt) {
+
Iface* iface = getIface(pkt->getIface());
if (!iface) {
isc_throw(BadValue, "Unable to send Pkt4. Invalid interface ("
<< pkt->getIface() << ") specified.");
}
- memset(&control_buf_[0], 0, control_buf_len_);
-
-
- // Set the target address we're sending to.
- sockaddr_in to;
- memset(&to, 0, sizeof(to));
- to.sin_family = AF_INET;
- to.sin_port = htons(pkt->getRemotePort());
- to.sin_addr.s_addr = htonl(pkt->getRemoteAddr());
-
- struct msghdr m;
- // Initialize our message header structure.
- memset(&m, 0, sizeof(m));
- m.msg_name = &to;
- m.msg_namelen = sizeof(to);
-
- // Set the data buffer we're sending. (Using this wacky
- // "scatter-gather" stuff... we only have a single chunk
- // of data to send, so we declare a single vector entry.)
- struct iovec v;
- memset(&v, 0, sizeof(v));
- // iov_base field is of void * type. We use it for packet
- // transmission, so this buffer will not be modified.
- v.iov_base = const_cast<void *>(pkt->getBuffer().getData());
- v.iov_len = pkt->getBuffer().getLength();
- m.msg_iov = &v;
- m.msg_iovlen = 1;
-
- // call OS-specific routines (like setting interface index)
- os_send4(m, control_buf_, control_buf_len_, pkt);
-
- pkt->updateTimestamp();
-
- int result = sendmsg(getSocket(*pkt), &m, 0);
- if (result < 0) {
- isc_throw(SocketWriteError, "pkt4 send failed");
- }
-
- return (result);
+ // Skip checking if packet filter is non-NULL because it has been
+ // already checked when packet filter was set.
+ return (packet_filter_->send(getSocket(*pkt), pkt));
}
@@ -792,8 +782,8 @@ IfaceMgr::receive4(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */) {
/// provided set to indicated which sockets have something to read.
for (iface = ifaces_.begin(); iface != ifaces_.end(); ++iface) {
- const SocketCollection& socket_collection = iface->getSockets();
- for (SocketCollection::const_iterator s = socket_collection.begin();
+ const Iface::SocketCollection& socket_collection = iface->getSockets();
+ for (Iface::SocketCollection::const_iterator s = socket_collection.begin();
s != socket_collection.end(); ++s) {
// Only deal with IPv4 addresses.
@@ -848,8 +838,8 @@ IfaceMgr::receive4(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */) {
// Let's find out which interface/socket has the data
for (iface = ifaces_.begin(); iface != ifaces_.end(); ++iface) {
- const SocketCollection& socket_collection = iface->getSockets();
- for (SocketCollection::const_iterator s = socket_collection.begin();
+ const Iface::SocketCollection& socket_collection = iface->getSockets();
+ for (Iface::SocketCollection::const_iterator s = socket_collection.begin();
s != socket_collection.end(); ++s) {
if (FD_ISSET(s->sockfd_, &sockets)) {
candidate = &(*s);
@@ -866,64 +856,9 @@ IfaceMgr::receive4(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */) {
}
// Now we have a socket, let's get some data from it!
- struct sockaddr_in from_addr;
- uint8_t buf[RCVBUFSIZE];
-
- memset(&control_buf_[0], 0, control_buf_len_);
- memset(&from_addr, 0, sizeof(from_addr));
-
- // Initialize our message header structure.
- struct msghdr m;
- memset(&m, 0, sizeof(m));
-
- // Point so we can get the from address.
- m.msg_name = &from_addr;
- m.msg_namelen = sizeof(from_addr);
-
- struct iovec v;
- v.iov_base = static_cast<void*>(buf);
- v.iov_len = RCVBUFSIZE;
- m.msg_iov = &v;
- m.msg_iovlen = 1;
-
- // Getting the interface is a bit more involved.
- //
- // We set up some space for a "control message". We have
- // previously asked the kernel to give us packet
- // information (when we initialized the interface), so we
- // should get the destination address from that.
- m.msg_control = &control_buf_[0];
- m.msg_controllen = control_buf_len_;
-
- result = recvmsg(candidate->sockfd_, &m, 0);
- if (result < 0) {
- isc_throw(SocketReadError, "failed to receive UDP4 data");
- }
-
- // We have all data let's create Pkt4 object.
- Pkt4Ptr pkt = Pkt4Ptr(new Pkt4(buf, result));
-
- pkt->updateTimestamp();
-
- unsigned int ifindex = iface->getIndex();
-
- IOAddress from(htonl(from_addr.sin_addr.s_addr));
- uint16_t from_port = htons(from_addr.sin_port);
-
- // Set receiving interface based on information, which socket was used to
- // receive data. OS-specific info (see os_receive4()) may be more reliable,
- // so this value may be overwritten.
- pkt->setIndex(ifindex);
- pkt->setIface(iface->getName());
- pkt->setRemoteAddr(from);
- pkt->setRemotePort(from_port);
- pkt->setLocalPort(candidate->port_);
-
- if (!os_receive4(m, pkt)) {
- isc_throw(SocketReadError, "unable to find pktinfo");
- }
-
- return (pkt);
+ // Skip checking if packet filter is non-NULL because it has been
+ // already checked when packet filter was set.
+ return (packet_filter_->receive(*iface, *candidate));
}
Pkt6Ptr IfaceMgr::receive6(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */ ) {
@@ -945,8 +880,8 @@ Pkt6Ptr IfaceMgr::receive6(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */
/// provided set to indicated which sockets have something to read.
IfaceCollection::const_iterator iface;
for (iface = ifaces_.begin(); iface != ifaces_.end(); ++iface) {
- const SocketCollection& socket_collection = iface->getSockets();
- for (SocketCollection::const_iterator s = socket_collection.begin();
+ const Iface::SocketCollection& socket_collection = iface->getSockets();
+ for (Iface::SocketCollection::const_iterator s = socket_collection.begin();
s != socket_collection.end(); ++s) {
// Only deal with IPv6 addresses.
@@ -1001,8 +936,8 @@ Pkt6Ptr IfaceMgr::receive6(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */
// Let's find out which interface/socket has the data
for (iface = ifaces_.begin(); iface != ifaces_.end(); ++iface) {
- const SocketCollection& socket_collection = iface->getSockets();
- for (SocketCollection::const_iterator s = socket_collection.begin();
+ const Iface::SocketCollection& socket_collection = iface->getSockets();
+ for (Iface::SocketCollection::const_iterator s = socket_collection.begin();
s != socket_collection.end(); ++s) {
if (FD_ISSET(s->sockfd_, &sockets)) {
candidate = &(*s);
@@ -1122,8 +1057,8 @@ uint16_t IfaceMgr::getSocket(const isc::dhcp::Pkt6& pkt) {
<< pkt.getIface());
}
- const SocketCollection& socket_collection = iface->getSockets();
- SocketCollection::const_iterator s;
+ const Iface::SocketCollection& socket_collection = iface->getSockets();
+ Iface::SocketCollection::const_iterator s;
for (s = socket_collection.begin(); s != socket_collection.end(); ++s) {
if ((s->family_ == AF_INET6) &&
(!s->addr_.getAddress().to_v6().is_multicast())) {
@@ -1145,8 +1080,8 @@ uint16_t IfaceMgr::getSocket(isc::dhcp::Pkt4 const& pkt) {
<< pkt.getIface());
}
- const SocketCollection& socket_collection = iface->getSockets();
- SocketCollection::const_iterator s;
+ const Iface::SocketCollection& socket_collection = iface->getSockets();
+ Iface::SocketCollection::const_iterator s;
for (s = socket_collection.begin(); s != socket_collection.end(); ++s) {
if (s->family_ == AF_INET) {
return (s->sockfd_);
diff --git a/src/lib/dhcp/iface_mgr.h b/src/lib/dhcp/iface_mgr.h
index a669a6d..2085b97 100644
--- a/src/lib/dhcp/iface_mgr.h
+++ b/src/lib/dhcp/iface_mgr.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -20,6 +20,7 @@
#include <dhcp/dhcp6.h>
#include <dhcp/pkt4.h>
#include <dhcp/pkt6.h>
+#include <dhcp/pkt_filter.h>
#include <boost/noncopyable.hpp>
#include <boost/scoped_array.hpp>
@@ -38,6 +39,13 @@ public:
isc::Exception(file, line, what) { };
};
+/// @brief IfaceMgr exception thrown when invalid packet filter object specified.
+class InvalidPacketFilter : public Exception {
+public:
+ InvalidPacketFilter(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
/// @brief IfaceMgr exception thrown thrown when socket opening
/// or configuration failed.
class SocketConfigError : public Exception {
@@ -62,6 +70,219 @@ public:
isc::Exception(file, line, what) { };
};
+/// Holds information about socket.
+struct SocketInfo {
+ uint16_t sockfd_; /// socket descriptor
+ isc::asiolink::IOAddress addr_; /// bound address
+ uint16_t port_; /// socket port
+ uint16_t family_; /// IPv4 or IPv6
+
+ /// @brief SocketInfo constructor.
+ ///
+ /// @param sockfd socket descriptor
+ /// @param addr an address the socket is bound to
+ /// @param port a port the socket is bound to
+ SocketInfo(uint16_t sockfd, const isc::asiolink::IOAddress& addr,
+ uint16_t port)
+ :sockfd_(sockfd), addr_(addr), port_(port), family_(addr.getFamily()) { }
+};
+
+
+/// @brief represents a single network interface
+///
+/// Iface structure represents network interface with all useful
+/// information, like name, interface index, MAC address and
+/// list of assigned addresses
+class Iface {
+public:
+
+ /// maximum MAC address length (Infiniband uses 20 bytes)
+ static const unsigned int MAX_MAC_LEN = 20;
+
+ /// type that defines list of addresses
+ typedef std::vector<isc::asiolink::IOAddress> AddressCollection;
+
+ /// type that holds a list of socket informations
+ /// @todo: Add SocketCollectionConstIter type
+ typedef std::list<SocketInfo> SocketCollection;
+
+ /// @brief Iface constructor.
+ ///
+ /// Creates Iface object that represents network interface.
+ ///
+ /// @param name name of the interface
+ /// @param ifindex interface index (unique integer identifier)
+ Iface(const std::string& name, int ifindex);
+
+ /// @brief Closes all open sockets on interface.
+ void closeSockets();
+
+ /// @brief Returns full interface name as "ifname/ifindex" string.
+ ///
+ /// @return string with interface name
+ std::string getFullName() const;
+
+ /// @brief Returns link-layer address a plain text.
+ ///
+ /// @return MAC address as a plain text (string)
+ std::string getPlainMac() const;
+
+ /// @brief Sets MAC address of the interface.
+ ///
+ /// @param mac pointer to MAC address buffer
+ /// @param macLen length of mac address
+ void setMac(const uint8_t* mac, size_t macLen);
+
+ /// @brief Returns MAC length.
+ ///
+ /// @return length of MAC address
+ size_t getMacLen() const { return mac_len_; }
+
+ /// @brief Returns pointer to MAC address.
+ ///
+ /// Note: Returned pointer is only valid as long as the interface object
+ /// that returned it.
+ const uint8_t* getMac() const { return mac_; }
+
+ /// @brief Sets flag_*_ fields based on bitmask value returned by OS
+ ///
+ /// Note: Implementation of this method is OS-dependent as bits have
+ /// different meaning on each OS.
+ ///
+ /// @param flags bitmask value returned by OS in interface detection
+ void setFlags(uint32_t flags);
+
+ /// @brief Returns interface index.
+ ///
+ /// @return interface index
+ uint16_t getIndex() const { return ifindex_; }
+
+ /// @brief Returns interface name.
+ ///
+ /// @return interface name
+ std::string getName() const { return name_; };
+
+ /// @brief Sets up hardware type of the interface.
+ ///
+ /// @param type hardware type
+ void setHWType(uint16_t type ) { hardware_type_ = type; }
+
+ /// @brief Returns hardware type of the interface.
+ ///
+ /// @return hardware type
+ uint16_t getHWType() const { return hardware_type_; }
+
+ /// @brief Returns all interfaces available on an interface.
+ ///
+ /// Care should be taken to not use this collection after Iface object
+ /// ceases to exist. That is easy in most cases as Iface objects are
+ /// created by IfaceMgr that is a singleton an is expected to be
+ /// available at all time. We may revisit this if we ever decide to
+ /// implement dynamic interface detection, but such fancy feature would
+ /// mostly be useful for clients with wifi/vpn/virtual interfaces.
+ ///
+ /// @return collection of addresses
+ const AddressCollection& getAddresses() const { return addrs_; }
+
+ /// @brief Adds an address to an interface.
+ ///
+ /// This only adds an address to collection, it does not physically
+ /// configure address on actual network interface.
+ ///
+ /// @param addr address to be added
+ void addAddress(const isc::asiolink::IOAddress& addr) {
+ addrs_.push_back(addr);
+ }
+
+ /// @brief Deletes an address from an interface.
+ ///
+ /// This only deletes address from collection, it does not physically
+ /// remove address configuration from actual network interface.
+ ///
+ /// @param addr address to be removed.
+ ///
+ /// @return true if removal was successful (address was in collection),
+ /// false otherwise
+ bool delAddress(const isc::asiolink::IOAddress& addr);
+
+ /// @brief Adds socket descriptor to an interface.
+ ///
+ /// @param sock SocketInfo structure that describes socket.
+ void addSocket(const SocketInfo& sock) {
+ sockets_.push_back(sock);
+ }
+
+ /// @brief Closes socket.
+ ///
+ /// Closes socket and removes corresponding SocketInfo structure
+ /// from an interface.
+ ///
+ /// @param sockfd socket descriptor to be closed/removed.
+ /// @return true if there was such socket, false otherwise
+ bool delSocket(uint16_t sockfd);
+
+ /// @brief Returns collection of all sockets added to interface.
+ ///
+ /// When new socket is created with @ref IfaceMgr::openSocket
+ /// it is added to sockets collection on particular interface.
+ /// If socket is opened by other means (e.g. function that does
+ /// not use @ref IfaceMgr::openSocket) it will not be available
+ /// in this collection. Note that functions like
+ /// @ref IfaceMgr::openSocketFromIface use
+ /// @ref IfaceMgr::openSocket internally.
+ /// The returned reference is only valid during the lifetime of
+ /// the IfaceMgr object that returned it.
+ ///
+ /// @return collection of sockets added to interface
+ const SocketCollection& getSockets() const { return sockets_; }
+
+protected:
+ /// socket used to sending data
+ SocketCollection sockets_;
+
+ /// network interface name
+ std::string name_;
+
+ /// interface index (a value that uniquely indentifies an interface)
+ int ifindex_;
+
+ /// list of assigned addresses
+ AddressCollection addrs_;
+
+ /// link-layer address
+ uint8_t mac_[MAX_MAC_LEN];
+
+ /// length of link-layer address (usually 6)
+ size_t mac_len_;
+
+ /// hardware type
+ uint16_t hardware_type_;
+
+public:
+ /// @todo: Make those fields protected once we start supporting more
+ /// than just Linux
+
+ /// specifies if selected interface is loopback
+ bool flag_loopback_;
+
+ /// specifies if selected interface is up
+ bool flag_up_;
+
+ /// flag specifies if selected interface is running
+ /// (e.g. cable plugged in, wifi associated)
+ bool flag_running_;
+
+ /// flag specifies if selected interface is multicast capable
+ bool flag_multicast_;
+
+ /// flag specifies if selected interface is broadcast capable
+ bool flag_broadcast_;
+
+ /// interface flags (this value is as is returned by OS,
+ /// it may mean different things on different OSes)
+ uint32_t flags_;
+};
+
/// @brief handles network interfaces, transmission and reception
///
/// IfaceMgr is an interface manager class that detects available network
@@ -70,15 +291,9 @@ public:
///
class IfaceMgr : public boost::noncopyable {
public:
- /// type that defines list of addresses
- typedef std::vector<isc::asiolink::IOAddress> AddressCollection;
-
/// defines callback used when commands are received over control session
typedef void (*SessionCallback) (void);
- /// maximum MAC address length (Infiniband uses 20 bytes)
- static const unsigned int MAX_MAC_LEN = 20;
-
/// @brief Packet reception buffer size
///
/// RFC3315 states that server responses may be
@@ -88,211 +303,6 @@ public:
/// we don't support packets larger than 1500.
static const uint32_t RCVBUFSIZE = 1500;
- /// Holds information about socket.
- struct SocketInfo {
- uint16_t sockfd_; /// socket descriptor
- isc::asiolink::IOAddress addr_; /// bound address
- uint16_t port_; /// socket port
- uint16_t family_; /// IPv4 or IPv6
-
- /// @brief SocketInfo constructor.
- ///
- /// @param sockfd socket descriptor
- /// @param addr an address the socket is bound to
- /// @param port a port the socket is bound to
- SocketInfo(uint16_t sockfd, const isc::asiolink::IOAddress& addr,
- uint16_t port)
- :sockfd_(sockfd), addr_(addr), port_(port), family_(addr.getFamily()) { }
- };
-
- /// type that holds a list of socket informations
- /// @todo: Add SocketCollectionConstIter type
- typedef std::list<SocketInfo> SocketCollection;
-
-
- /// @brief represents a single network interface
- ///
- /// Iface structure represents network interface with all useful
- /// information, like name, interface index, MAC address and
- /// list of assigned addresses
- class Iface {
- public:
- /// @brief Iface constructor.
- ///
- /// Creates Iface object that represents network interface.
- ///
- /// @param name name of the interface
- /// @param ifindex interface index (unique integer identifier)
- Iface(const std::string& name, int ifindex);
-
- /// @brief Closes all open sockets on interface.
- void closeSockets();
-
- /// @brief Returns full interface name as "ifname/ifindex" string.
- ///
- /// @return string with interface name
- std::string getFullName() const;
-
- /// @brief Returns link-layer address a plain text.
- ///
- /// @return MAC address as a plain text (string)
- std::string getPlainMac() const;
-
- /// @brief Sets MAC address of the interface.
- ///
- /// @param mac pointer to MAC address buffer
- /// @param macLen length of mac address
- void setMac(const uint8_t* mac, size_t macLen);
-
- /// @brief Returns MAC length.
- ///
- /// @return length of MAC address
- size_t getMacLen() const { return mac_len_; }
-
- /// @brief Returns pointer to MAC address.
- ///
- /// Note: Returned pointer is only valid as long as the interface object
- /// that returned it.
- const uint8_t* getMac() const { return mac_; }
-
- /// @brief Sets flag_*_ fields based on bitmask value returned by OS
- ///
- /// Note: Implementation of this method is OS-dependent as bits have
- /// different meaning on each OS.
- ///
- /// @param flags bitmask value returned by OS in interface detection
- void setFlags(uint32_t flags);
-
- /// @brief Returns interface index.
- ///
- /// @return interface index
- uint16_t getIndex() const { return ifindex_; }
-
- /// @brief Returns interface name.
- ///
- /// @return interface name
- std::string getName() const { return name_; };
-
- /// @brief Sets up hardware type of the interface.
- ///
- /// @param type hardware type
- void setHWType(uint16_t type ) { hardware_type_ = type; }
-
- /// @brief Returns hardware type of the interface.
- ///
- /// @return hardware type
- uint16_t getHWType() const { return hardware_type_; }
-
- /// @brief Returns all interfaces available on an interface.
- ///
- /// Care should be taken to not use this collection after Iface object
- /// ceases to exist. That is easy in most cases as Iface objects are
- /// created by IfaceMgr that is a singleton an is expected to be
- /// available at all time. We may revisit this if we ever decide to
- /// implement dynamic interface detection, but such fancy feature would
- /// mostly be useful for clients with wifi/vpn/virtual interfaces.
- ///
- /// @return collection of addresses
- const AddressCollection& getAddresses() const { return addrs_; }
-
- /// @brief Adds an address to an interface.
- ///
- /// This only adds an address to collection, it does not physically
- /// configure address on actual network interface.
- ///
- /// @param addr address to be added
- void addAddress(const isc::asiolink::IOAddress& addr) {
- addrs_.push_back(addr);
- }
-
- /// @brief Deletes an address from an interface.
- ///
- /// This only deletes address from collection, it does not physically
- /// remove address configuration from actual network interface.
- ///
- /// @param addr address to be removed.
- ///
- /// @return true if removal was successful (address was in collection),
- /// false otherwise
- bool delAddress(const isc::asiolink::IOAddress& addr);
-
- /// @brief Adds socket descriptor to an interface.
- ///
- /// @param sock SocketInfo structure that describes socket.
- void addSocket(const SocketInfo& sock)
- { sockets_.push_back(sock); }
-
- /// @brief Closes socket.
- ///
- /// Closes socket and removes corresponding SocketInfo structure
- /// from an interface.
- ///
- /// @param sockfd socket descriptor to be closed/removed.
- /// @return true if there was such socket, false otherwise
- bool delSocket(uint16_t sockfd);
-
- /// @brief Returns collection of all sockets added to interface.
- ///
- /// When new socket is created with @ref IfaceMgr::openSocket
- /// it is added to sockets collection on particular interface.
- /// If socket is opened by other means (e.g. function that does
- /// not use @ref IfaceMgr::openSocket) it will not be available
- /// in this collection. Note that functions like
- /// @ref IfaceMgr::openSocketFromIface use
- /// @ref IfaceMgr::openSocket internally.
- /// The returned reference is only valid during the lifetime of
- /// the IfaceMgr object that returned it.
- ///
- /// @return collection of sockets added to interface
- const SocketCollection& getSockets() const { return sockets_; }
-
- protected:
- /// socket used to sending data
- SocketCollection sockets_;
-
- /// network interface name
- std::string name_;
-
- /// interface index (a value that uniquely indentifies an interface)
- int ifindex_;
-
- /// list of assigned addresses
- AddressCollection addrs_;
-
- /// link-layer address
- uint8_t mac_[MAX_MAC_LEN];
-
- /// length of link-layer address (usually 6)
- size_t mac_len_;
-
- /// hardware type
- uint16_t hardware_type_;
-
- public:
- /// @todo: Make those fields protected once we start supporting more
- /// than just Linux
-
- /// specifies if selected interface is loopback
- bool flag_loopback_;
-
- /// specifies if selected interface is up
- bool flag_up_;
-
- /// flag specifies if selected interface is running
- /// (e.g. cable plugged in, wifi associated)
- bool flag_running_;
-
- /// flag specifies if selected interface is multicast capable
- bool flag_multicast_;
-
- /// flag specifies if selected interface is broadcast capable
- bool flag_broadcast_;
-
- /// interface flags (this value is as is returned by OS,
- /// it may mean different things on different OSes)
- uint32_t flags_;
- };
-
// TODO performance improvement: we may change this into
// 2 maps (ifindex-indexed and name-indexed) and
// also hide it (make it public make tests easier for now)
@@ -306,6 +316,16 @@ public:
/// @return the only existing instance of interface manager
static IfaceMgr& instance();
+ /// @brief Check if packet be sent directly to the client having no address.
+ ///
+ /// Checks if IfaceMgr can send DHCPv4 packet to the client
+ /// who hasn't got address assigned. If this is not supported
+ /// broadcast address should be used to send response to
+ /// the client.
+ ///
+ /// @return true if direct response is supported.
+ bool isDirectResponseSupported();
+
/// @brief Returns interface with specified interface index
///
/// @param ifindex index of searched interface
@@ -434,6 +454,10 @@ public:
/// @param ifname name of the interface
/// @param addr address to be bound.
/// @param port UDP port.
+ /// @param receive_bcast configure IPv4 socket to receive broadcast messages.
+ /// This parameter is ignored for IPv6 sockets.
+ /// @param send_bcast configure IPv4 socket to send broadcast messages.
+ /// This parameter is ignored for IPv6 sockets.
///
/// Method will throw if socket creation, socket binding or multicast
/// join fails.
@@ -442,7 +466,9 @@ public:
/// group join were all successful.
int openSocket(const std::string& ifname,
const isc::asiolink::IOAddress& addr,
- const uint16_t port);
+ const uint16_t port,
+ const bool receive_bcast = false,
+ const bool send_bcast = false);
/// @brief Opens UDP/IP socket and binds it to interface specified.
///
@@ -504,18 +530,20 @@ public:
/// @return true if any sockets were open
bool openSockets6(const uint16_t port = DHCP6_SERVER_PORT);
- /// @brief Closes all open sockets.
- /// Is used in destructor, but also from Dhcpv4_srv and Dhcpv6_srv classes.
- void closeSockets();
-
/// Opens IPv4 sockets on detected interfaces.
/// Will throw exception if socket creation fails.
///
/// @param port specifies port number (usually DHCP4_SERVER_PORT)
+ /// @param use_bcast configure sockets to support broadcast messages.
///
/// @throw SocketOpenFailure if tried and failed to open socket.
/// @return true if any sockets were open
- bool openSockets4(const uint16_t port = DHCP4_SERVER_PORT);
+ bool openSockets4(const uint16_t port = DHCP4_SERVER_PORT,
+ const bool use_bcast = true);
+
+ /// @brief Closes all open sockets.
+ /// Is used in destructor, but also from Dhcpv4_srv and Dhcpv6_srv classes.
+ void closeSockets();
/// @brief returns number of detected interfaces
///
@@ -534,6 +562,24 @@ public:
session_callback_ = callback;
}
+ /// @brief Set Packet Filter object to handle send/receive packets.
+ ///
+ /// Packet Filters expose low-level functions handling sockets opening
+ /// and sending/receiving packets through those sockets. This function
+ /// sets custom Packet Filter (represented by a class derived from PktFilter)
+ /// to be used by IfaceMgr.
+ ///
+ /// @param packet_filter new packet filter to be used by IfaceMgr to send/receive
+ /// packets and open sockets.
+ ///
+ /// @throw InvalidPacketFilter if provided packet filter object is NULL.
+ void setPacketFilter(const boost::shared_ptr<PktFilter>& packet_filter) {
+ if (!packet_filter) {
+ isc_throw(InvalidPacketFilter, "NULL packet filter object specified");
+ }
+ packet_filter_ = packet_filter;
+ }
+
/// A value of socket descriptor representing "not specified" state.
static const int INVALID_SOCKET = -1;
@@ -557,9 +603,13 @@ protected:
/// @param iface reference to interface structure.
/// @param addr an address the created socket should be bound to
/// @param port a port that created socket should be bound to
+ /// @param receive_bcast configure socket to receive broadcast messages
+ /// @param send_bcast configure socket to send broadcast messages.
///
/// @return socket descriptor
- int openSocket4(Iface& iface, const isc::asiolink::IOAddress& addr, uint16_t port);
+ int openSocket4(Iface& iface, const isc::asiolink::IOAddress& addr,
+ const uint16_t port, const bool receive_bcast = false,
+ const bool send_bcast = false);
/// @brief Opens IPv6 socket.
///
@@ -585,7 +635,7 @@ protected:
///
/// This method will eventually detect available interfaces. For now
/// it offers stub implementation. First interface name and link-local
- /// IPv6 address is read from intefaces.txt file.
+ /// IPv6 address is read from interfaces.txt file.
void
detectIfaces();
@@ -594,7 +644,7 @@ protected:
/// This implementations reads a single line from interfaces.txt file
/// and pretends to detect such interface. First interface name and
/// link-local IPv6 address or IPv4 address is read from the
- /// intefaces.txt file.
+ /// interfaces.txt file.
void
stubDetectIfaces();
@@ -674,10 +724,20 @@ private:
/// @param remote_addr remote address to connect to
/// @param port port to be used
/// @return local address to be used to connect to remote address
- /// @throw isc::Unexpected if unable to indentify local address
+ /// @throw isc::Unexpected if unable to identify local address
isc::asiolink::IOAddress
getLocalAddress(const isc::asiolink::IOAddress& remote_addr,
const uint16_t port);
+
+ /// Holds instance of a class derived from PktFilter, used by the
+ /// IfaceMgr to open sockets and send/receive packets through these
+ /// sockets. It is possible to supply custom object using
+ /// setPacketFilter class. Various Packet Filters differ mainly by using
+ /// different types of sockets, e.g. SOCK_DGRAM, SOCK_RAW and different
+ /// families, e.g. AF_INET, AF_PACKET etc. Another possible type of
+ /// Packet Filter is the one used for unit testing, which doesn't
+ /// open sockets but rather mimics their behavior (mock object).
+ boost::shared_ptr<PktFilter> packet_filter_;
};
}; // namespace isc::dhcp
diff --git a/src/lib/dhcp/iface_mgr_bsd.cc b/src/lib/dhcp/iface_mgr_bsd.cc
index e3f11a1..afd97bb 100644
--- a/src/lib/dhcp/iface_mgr_bsd.cc
+++ b/src/lib/dhcp/iface_mgr_bsd.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011, 2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -34,6 +34,11 @@ IfaceMgr::detectIfaces() {
stubDetectIfaces();
}
+bool
+IfaceMgr::isDirectResponseSupported() {
+ return (false);
+}
+
void IfaceMgr::os_send4(struct msghdr& /*m*/,
boost::scoped_array<char>& /*control_buf*/,
size_t /*control_buf_len*/,
diff --git a/src/lib/dhcp/iface_mgr_linux.cc b/src/lib/dhcp/iface_mgr_linux.cc
index e7d5048..71a32d8 100644
--- a/src/lib/dhcp/iface_mgr_linux.cc
+++ b/src/lib/dhcp/iface_mgr_linux.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -67,7 +67,7 @@ public:
/// interfaces) uses memory aliasing. Linux kernel returns a memory
/// blob that should be interpreted as series of nlmessages. There
/// are different nlmsg structures defined with varying size. They
-/// have one thing common - inital fields are laid out in the same
+/// have one thing common - initial fields are laid out in the same
/// way as nlmsghdr. Therefore different messages can be represented
/// as nlmsghdr with followed variable number of bytes that are
/// message-specific. The only reasonable way to represent this in
@@ -103,7 +103,7 @@ public:
void rtnl_send_request(int family, int type);
void rtnl_store_reply(NetlinkMessages& storage, const nlmsghdr* msg);
void parse_rtattr(RTattribPtrs& table, rtattr* rta, int len);
- void ipaddrs_get(IfaceMgr::Iface& iface, NetlinkMessages& addr_info);
+ void ipaddrs_get(Iface& iface, NetlinkMessages& addr_info);
void rtnl_process_reply(NetlinkMessages& info);
void release_list(NetlinkMessages& messages);
void rtnl_close_socket();
@@ -277,7 +277,7 @@ void Netlink::parse_rtattr(RTattribPtrs& table, struct rtattr* rta, int len)
///
/// @param iface interface representation (addresses will be added here)
/// @param addr_info collection of parsed netlink messages
-void Netlink::ipaddrs_get(IfaceMgr::Iface& iface, NetlinkMessages& addr_info) {
+void Netlink::ipaddrs_get(Iface& iface, NetlinkMessages& addr_info) {
uint8_t addr[V6ADDRESS_LEN];
RTattribPtrs rta_tb;
@@ -476,7 +476,7 @@ void IfaceMgr::detectIfaces() {
iface.setHWType(interface_info->ifi_type);
iface.setFlags(interface_info->ifi_flags);
- // Does inetface have LL_ADDR?
+ // Does interface have LL_ADDR?
if (attribs_table[IFLA_ADDRESS]) {
iface.setMac(static_cast<const uint8_t*>(RTA_DATA(attribs_table[IFLA_ADDRESS])),
RTA_PAYLOAD(attribs_table[IFLA_ADDRESS]));
@@ -494,13 +494,18 @@ void IfaceMgr::detectIfaces() {
nl.release_list(addr_info);
}
+bool
+IfaceMgr::isDirectResponseSupported() {
+ return (false);
+}
+
/// @brief sets flag_*_ fields.
///
/// This implementation is OS-specific as bits have different meaning
/// on different OSes.
///
/// @param flags flags bitfield read from OS
-void IfaceMgr::Iface::setFlags(uint32_t flags) {
+void Iface::setFlags(uint32_t flags) {
flags_ = flags;
flag_loopback_ = flags & IFF_LOOPBACK;
@@ -510,56 +515,15 @@ void IfaceMgr::Iface::setFlags(uint32_t flags) {
flag_broadcast_ = flags & IFF_BROADCAST;
}
-void IfaceMgr::os_send4(struct msghdr& m, boost::scoped_array<char>& control_buf,
- size_t control_buf_len, const Pkt4Ptr& pkt) {
-
- // Setting the interface is a bit more involved.
- //
- // We have to create a "control message", and set that to
- // define the IPv4 packet information. We could set the
- // source address if we wanted, but we can safely let the
- // kernel decide what that should be.
- m.msg_control = &control_buf[0];
- m.msg_controllen = control_buf_len;
- struct cmsghdr* cmsg = CMSG_FIRSTHDR(&m);
- cmsg->cmsg_level = IPPROTO_IP;
- cmsg->cmsg_type = IP_PKTINFO;
- cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo));
- struct in_pktinfo* pktinfo =(struct in_pktinfo *)CMSG_DATA(cmsg);
- memset(pktinfo, 0, sizeof(struct in_pktinfo));
- pktinfo->ipi_ifindex = pkt->getIndex();
- m.msg_controllen = cmsg->cmsg_len;
-}
-
-bool IfaceMgr::os_receive4(struct msghdr& m, Pkt4Ptr& pkt) {
- struct cmsghdr* cmsg;
- struct in_pktinfo* pktinfo;
- struct in_addr to_addr;
-
- memset(&to_addr, 0, sizeof(to_addr));
- cmsg = CMSG_FIRSTHDR(&m);
- while (cmsg != NULL) {
- if ((cmsg->cmsg_level == IPPROTO_IP) &&
- (cmsg->cmsg_type == IP_PKTINFO)) {
- pktinfo = (struct in_pktinfo*)CMSG_DATA(cmsg);
+void IfaceMgr::os_send4(struct msghdr&, boost::scoped_array<char>&,
+ size_t, const Pkt4Ptr&) {
+ return;
- pkt->setIndex(pktinfo->ipi_ifindex);
- pkt->setLocalAddr(IOAddress(htonl(pktinfo->ipi_addr.s_addr)));
- return (true);
-
- // This field is useful, when we are bound to unicast
- // address e.g. 192.0.2.1 and the packet was sent to
- // broadcast. This will return broadcast address, not
- // the address we are bound to.
-
- // XXX: Perhaps we should uncomment this:
- // to_addr = pktinfo->ipi_spec_dst;
- }
- cmsg = CMSG_NXTHDR(&m, cmsg);
- }
+}
- return (false);
+bool IfaceMgr::os_receive4(struct msghdr&, Pkt4Ptr&) {
+ return (true);
}
} // end of isc::dhcp namespace
diff --git a/src/lib/dhcp/iface_mgr_sun.cc b/src/lib/dhcp/iface_mgr_sun.cc
index 5847906..1556b70 100644
--- a/src/lib/dhcp/iface_mgr_sun.cc
+++ b/src/lib/dhcp/iface_mgr_sun.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011, 2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -34,6 +34,11 @@ IfaceMgr::detectIfaces() {
stubDetectIfaces();
}
+bool
+IfaceMgr::isDirectResponseSupported() {
+ return (false);
+}
+
void IfaceMgr::os_send4(struct msghdr& /*m*/,
boost::scoped_array<char>& /*control_buf*/,
size_t /*control_buf_len*/,
diff --git a/src/lib/dhcp/libdhcp++.cc b/src/lib/dhcp/libdhcp++.cc
index 78f511d..697c33e 100644
--- a/src/lib/dhcp/libdhcp++.cc
+++ b/src/lib/dhcp/libdhcp++.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -128,7 +128,9 @@ LibDHCP::optionFactory(Option::Universe u,
size_t LibDHCP::unpackOptions6(const OptionBuffer& buf,
- isc::dhcp::Option::OptionCollection& options) {
+ isc::dhcp::Option::OptionCollection& options,
+ size_t* relay_msg_offset /* = 0 */,
+ size_t* relay_msg_len /* = 0 */) {
size_t offset = 0;
size_t length = buf.size();
@@ -143,6 +145,7 @@ size_t LibDHCP::unpackOptions6(const OptionBuffer& buf,
while (offset + 4 <= length) {
uint16_t opt_type = isc::util::readUint16(&buf[offset]);
offset += 2;
+
uint16_t opt_len = isc::util::readUint16(&buf[offset]);
offset += 2;
@@ -151,6 +154,16 @@ size_t LibDHCP::unpackOptions6(const OptionBuffer& buf,
return (offset);
}
+ if (opt_type == D6O_RELAY_MSG && relay_msg_offset && relay_msg_len) {
+ // remember offset of the beginning of the relay-msg option
+ *relay_msg_offset = offset;
+ *relay_msg_len = opt_len;
+
+ // do not create that relay-msg option
+ offset += opt_len;
+ continue;
+ }
+
// Get all definitions with the particular option code. Note that option
// code is non-unique within this container however at this point we
// expect to get one option definition with the particular code. If more
@@ -193,7 +206,7 @@ size_t LibDHCP::unpackOptions6(const OptionBuffer& buf,
}
size_t LibDHCP::unpackOptions4(const OptionBuffer& buf,
- isc::dhcp::Option::OptionCollection& options) {
+ isc::dhcp::Option::OptionCollection& options) {
size_t offset = 0;
// Get the list of stdandard option definitions.
diff --git a/src/lib/dhcp/libdhcp++.dox b/src/lib/dhcp/libdhcp++.dox
index 194175a..eabc392 100644
--- a/src/lib/dhcp/libdhcp++.dox
+++ b/src/lib/dhcp/libdhcp++.dox
@@ -57,6 +57,53 @@ DHCPv6, but is rarely used in DHCPv4. isc::dhcp::Option::addOption(),
isc::dhcp::Option::delOption(), isc::dhcp::Option::getOption() can be used
for that purpose.
+ at section libdhcpRelay Relay v6 support in Pkt6
+
+DHCPv6 clients that are not connected to the same link as DHCPv6
+servers need relays to reach the server. Each relay receives a message
+on a client facing interface, encapsulates it into RELAY_MSG option
+and sends as RELAY_FORW message towards the server (or the next relay,
+which is closer to the server). This procedure can be repeated up to
+32 times. Kea is able to support up to 32 relays. Each traversed relay
+may add certain options. The most obvious example is interface-id
+option, but there may be other options as well. Each relay may add such
+an option, regardless of whether other relays added it before. Thanks
+to encapsulation, those options are separated and it is possible to
+differentiate which relay inserted specific instance of an option.
+
+Interface-id is used to identify a subnet (or interface) the original message
+came from and is used for that purpose on two occasions. First, the server
+uses the interface-id included by the first relay (the one closest to
+the client) to select appropriate subnet for a given request. Server includes
+that interface-id in its copy, when sending data back to the client.
+This will be used by the relay to choose proper interface when forwarding
+response towards the client.
+
+Pkt6 class has a public Pkt6::relay_info_ field, which is of type Pkt6::RelayInfo.
+This is a simple structure that represents the information in each RELAY_FORW
+or RELAY_REPL message. It is important to understand the order in which
+the data appear here. Consider the following network:
+
+\verbatim
+client-------relay1-----relay2-----relay3----server
+\endverbatim
+
+Client will transmit SOLICIT message. Relay1 will forward it as
+RELAY_FORW with SOLICIT in it. Relay2 forward it as RELAY_FORW with
+RELAY_FORW with SOLICIT in it. Finally the third relay will add yet
+another RELAY_FORW around it. The server will parse the packet and
+create Pkt6 object for it. Its relay_info_ will have 3
+elements. Packet parsing is done in reverse order, compare to the
+order the packet traversed in the network. The first element
+(relay_info_[0]) will represent relay3 information (the "last" relay or
+in other words the one closest to the server). The second element
+will represent relay2. The third element (relay_info_[2]) will represent
+the first relay (relay1) or in other words the one closest to the client.
+
+Packets sent by the server must maintain the same encapsulation order.
+This is easy to do - just copy data from client's message object into
+server's response object. See Pkt6::coyRelayInfo for details.
+
@section libdhcpIfaceMgr Interface Manager
Interface Manager (or IfaceMgr) is an abstraction layer about low-level
diff --git a/src/lib/dhcp/libdhcp++.h b/src/lib/dhcp/libdhcp++.h
index c242611..9d8bcab 100644
--- a/src/lib/dhcp/libdhcp++.h
+++ b/src/lib/dhcp/libdhcp++.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -115,14 +115,27 @@ public:
/// @brief Parses provided buffer as DHCPv6 options and creates Option objects.
///
- /// Parses provided buffer and stores created Option objects
- /// in options container.
+ /// Parses provided buffer and stores created Option objects in options
+ /// container. The last two parameters are optional and are used in
+ /// relay parsing. If they are specified, relay-msg option is not created,
+ /// but rather those two parameters are specified to point out where
+ /// the relay-msg option resides and what is its length. This is perfromance
+ /// optimization that avoids unnecessary copying of potentially large
+ /// relay-msg option. It is not used for anything, except in the next
+ /// iteration its content will be treated as buffer to be parsed.
///
/// @param buf Buffer to be parsed.
/// @param options Reference to option container. Options will be
/// put here.
+ /// @param relay_msg_offset reference to a size_t structure. If specified,
+ /// offset to beginning of relay_msg option will be stored in it.
+ /// @param relay_msg_len reference to a size_t structure. If specified,
+ /// length of the relay_msg option will be stored in it.
+ /// @return offset to the first byte after last parsed option
static size_t unpackOptions6(const OptionBuffer& buf,
- isc::dhcp::Option::OptionCollection& options);
+ isc::dhcp::Option::OptionCollection& options,
+ size_t* relay_msg_offset = 0,
+ size_t* relay_msg_len = 0);
/// Registers factory method that produces options of specific option types.
///
diff --git a/src/lib/dhcp/option_custom.cc b/src/lib/dhcp/option_custom.cc
index 3d2a1a9..e807928 100644
--- a/src/lib/dhcp/option_custom.cc
+++ b/src/lib/dhcp/option_custom.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -230,7 +230,7 @@ OptionCustom::createBuffers(const OptionBuffer& data_buf) {
// 1 byte larger than the size of the string
// representation of this FQDN.
data_size = fqdn.size() + 1;
- } else {
+ } else if ( (*field == OPT_BINARY_TYPE) || (*field == OPT_STRING_TYPE) ) {
// In other case we are dealing with string or binary value
// which size can't be determined. Thus we consume the
// remaining part of the buffer for it. Note that variable
@@ -238,14 +238,11 @@ OptionCustom::createBuffers(const OptionBuffer& data_buf) {
// that the validate() function in OptionDefinition object
// should have checked wheter it is a case for this option.
data_size = std::distance(data, data_buf.end());
- }
- if (data_size == 0) {
+ } else {
// If we reached the end of buffer we assume that this option is
// truncated because there is no remaining data to initialize
// an option field.
- if (data_size == 0) {
- isc_throw(OutOfRange, "option buffer truncated");
- }
+ isc_throw(OutOfRange, "option buffer truncated");
}
} else {
// Our data field requires that there is a certain chunk of
diff --git a/src/lib/dhcp/option_custom.h b/src/lib/dhcp/option_custom.h
index 079c270..5766cb4 100644
--- a/src/lib/dhcp/option_custom.h
+++ b/src/lib/dhcp/option_custom.h
@@ -205,7 +205,7 @@ public:
// is consistent with an option definition.
checkDataType<T>(index);
// When we created the buffer we have checked that it has a
- // valid size so this condition here should be always fulfiled.
+ // valid size so this condition here should be always fulfilled.
assert(buffers_[index].size() == OptionDataTypeTraits<T>::len);
// Read an integer value.
return (OptionDataTypeUtil::readInt<T>(buffers_[index]));
diff --git a/src/lib/dhcp/option_definition.h b/src/lib/dhcp/option_definition.h
index 233f778..0aa0e17 100644
--- a/src/lib/dhcp/option_definition.h
+++ b/src/lib/dhcp/option_definition.h
@@ -167,7 +167,7 @@ public:
///
/// This constructor sets the name of the option space that is
/// encapsulated by this option. The encapsulated option space
- /// indentifies sub-options that are carried within this option.
+ /// identifies sub-options that are carried within this option.
/// This constructor does not allow to set array indicator
/// because options comprising an array of data fields must
/// not be used with sub-options.
@@ -186,7 +186,7 @@ public:
///
/// This constructor sets the name of the option space that is
/// encapsulated by this option. The encapsulated option space
- /// indentifies sub-options that are carried within this option.
+ /// identifies sub-options that are carried within this option.
/// This constructor does not allow to set array indicator
/// because options comprising an array of data fields must
/// not be used with sub-options.
diff --git a/src/lib/dhcp/pkt4.h b/src/lib/dhcp/pkt4.h
index 664686b..e7f33c5 100644
--- a/src/lib/dhcp/pkt4.h
+++ b/src/lib/dhcp/pkt4.h
@@ -441,7 +441,7 @@ protected:
/// @brief interface index
///
/// Each network interface has assigned unique ifindex. It is functional
- /// equvalent of name, but sometimes more useful, e.g. when using crazy
+ /// equivalent of name, but sometimes more useful, e.g. when using crazy
/// systems that allow spaces in interface names e.g. MS Windows)
uint32_t ifindex_;
diff --git a/src/lib/dhcp/pkt6.cc b/src/lib/dhcp/pkt6.cc
index c3a98bf..27c8ca6 100644
--- a/src/lib/dhcp/pkt6.cc
+++ b/src/lib/dhcp/pkt6.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -21,10 +21,17 @@
#include <sstream>
using namespace std;
+using namespace isc::asiolink;
namespace isc {
namespace dhcp {
+Pkt6::RelayInfo::RelayInfo()
+ :msg_type_(0), hop_count_(0), linkaddr_("::"), peeraddr_("::"), relay_msg_len_(0) {
+ // interface_id_, subscriber_id_, remote_id_ initialized to NULL
+ // echo_options_ initialized to empty collection
+}
+
Pkt6::Pkt6(const uint8_t* buf, uint32_t buf_len, DHCPv6Proto proto /* = UDP */) :
proto_(proto),
msg_type_(0),
@@ -54,9 +61,117 @@ Pkt6::Pkt6(uint8_t msg_type, uint32_t transid, DHCPv6Proto proto /*= UDP*/) :
}
uint16_t Pkt6::len() {
+ if (relay_info_.empty()) {
+ return (directLen());
+ } else {
+ // Unfortunately we need to re-calculate relay size every time, because
+ // we need to make sure that once a new option is added, its extra size
+ // is reflected in Pkt6::len().
+ calculateRelaySizes();
+ return (relay_info_[0].relay_msg_len_ + getRelayOverhead(relay_info_[0]));
+ }
+}
+
+OptionPtr Pkt6::getAnyRelayOption(uint16_t opt_type, RelaySearchOrder order) {
+
+ if (relay_info_.empty()) {
+ // There's no relay info, this is a direct message
+ return (OptionPtr());
+ }
+
+ int start = 0; // First relay to check
+ int end = 0; // Last relay to check
+ int direction = 0; // How we going to iterate: forward or backward?
+
+ switch (order) {
+ case RELAY_SEARCH_FROM_CLIENT:
+ // Search backwards
+ start = relay_info_.size() - 1;
+ end = 0;
+ direction = -1;
+ break;
+ case RELAY_SEARCH_FROM_SERVER:
+ // Search forward
+ start = 0;
+ end = relay_info_.size() - 1;
+ direction = 1;
+ break;
+ case RELAY_GET_FIRST:
+ // Look at the innermost relay only
+ start = relay_info_.size() - 1;
+ end = start;
+ direction = 1;
+ break;
+ case RELAY_GET_LAST:
+ // Look at the outermost relay only
+ start = 0;
+ end = 0;
+ direction = 1;
+ }
+
+ // This is a tricky loop. It must go from start to end, but it must work in
+ // both directions (start > end; or start < end). We can't use regular
+ // exit condition, because we don't know whether to use i <= end or i >= end.
+ // That's why we check if in the next iteration we would go past the
+ // list (end + direction). It is similar to STL concept of end pointing
+ // to a place after the last element
+ for (int i = start; i != end + direction; i += direction) {
+ OptionPtr opt = getRelayOption(opt_type, i);
+ if (opt) {
+ return (opt);
+ }
+ }
+
+ // We iterated over specified relays and haven't found what we were
+ // looking for
+ return (OptionPtr());
+}
+
+
+OptionPtr Pkt6::getRelayOption(uint16_t opt_type, uint8_t relay_level) {
+ if (relay_level >= relay_info_.size()) {
+ isc_throw(OutOfRange, "This message was relayed " << relay_info_.size() << " time(s)."
+ << " There is no info about " << relay_level + 1 << " relay.");
+ }
+
+ for (Option::OptionCollection::iterator it = relay_info_[relay_level].options_.begin();
+ it != relay_info_[relay_level].options_.end(); ++it) {
+ if ((*it).second->getType() == opt_type) {
+ return (it->second);
+ }
+ }
+
+ return (OptionPtr());
+}
+
+uint16_t Pkt6::getRelayOverhead(const RelayInfo& relay) const {
+ uint16_t len = DHCPV6_RELAY_HDR_LEN // fixed header
+ + Option::OPTION6_HDR_LEN; // header of the relay-msg option
+
+ for (Option::OptionCollection::const_iterator opt = relay.options_.begin();
+ opt != relay.options_.end(); ++opt) {
+ len += (opt->second)->len();
+ }
+
+ return (len);
+}
+
+uint16_t Pkt6::calculateRelaySizes() {
+
+ uint16_t len = directLen(); // start with length of all options
+
+ for (int relay_index = relay_info_.size(); relay_index > 0; --relay_index) {
+ relay_info_[relay_index - 1].relay_msg_len_ = len;
+ len += getRelayOverhead(relay_info_[relay_index - 1]);
+ }
+
+ return (len);
+}
+
+uint16_t Pkt6::directLen() const {
uint16_t length = DHCPV6_PKT_HDR_LEN; // DHCPv6 header
- for (Option::OptionCollection::iterator it = options_.begin();
+ for (Option::OptionCollection::const_iterator it = options_.begin();
it != options_.end();
++it) {
length += (*it).second->len();
@@ -82,6 +197,50 @@ Pkt6::pack() {
bool
Pkt6::packUDP() {
try {
+
+ // is this a relayed packet?
+ if (!relay_info_.empty()) {
+
+ // calculate size needed for each relay (if there is only one relay,
+ // then it will be equal to "regular" length + relay-forw header +
+ // size of relay-msg option header + possibly size of interface-id
+ // option (if present). If there is more than one relay, the whole
+ // process is called iteratively for each relay.
+ calculateRelaySizes();
+
+ // Now for each relay, we need to...
+ for (vector<RelayInfo>::iterator relay = relay_info_.begin();
+ relay != relay_info_.end(); ++relay) {
+
+ // build relay-forw/relay-repl header (see RFC3315, section 7)
+ bufferOut_.writeUint8(relay->msg_type_);
+ bufferOut_.writeUint8(relay->hop_count_);
+ bufferOut_.writeData(&(relay->linkaddr_.toBytes()[0]),
+ isc::asiolink::V6ADDRESS_LEN);
+ bufferOut_.writeData(&relay->peeraddr_.toBytes()[0],
+ isc::asiolink::V6ADDRESS_LEN);
+
+ // store every option in this relay scope. Usually that will be
+ // only interface-id, but occasionally other options may be
+ // present here as well (vendor-opts for Cable modems,
+ // subscriber-id, remote-id, options echoed back from Echo
+ // Request Option, etc.)
+ for (Option::OptionCollection::const_iterator opt =
+ relay->options_.begin();
+ opt != relay->options_.end(); ++opt) {
+ (opt->second)->pack(bufferOut_);
+ }
+
+ // and include header relay-msg option. Its payload will be
+ // generated in the next iteration (if there are more relays)
+ // or outside the loop (if there are no more relays and the
+ // payload is a direct message)
+ bufferOut_.writeUint16(D6O_RELAY_MSG);
+ bufferOut_.writeUint16(relay->relay_msg_len_);
+ }
+
+ }
+
// DHCPv6 header: message-type (1 octect) + transaction id (3 octets)
bufferOut_.writeUint8(msg_type_);
// store 3-octet transaction-id
@@ -127,12 +286,43 @@ Pkt6::unpackUDP() {
return (false);
}
msg_type_ = data_[0];
- transid_ = ( (data_[1]) << 16 ) +
- ((data_[2]) << 8) + (data_[3]);
+ switch (msg_type_) {
+ case DHCPV6_SOLICIT:
+ case DHCPV6_ADVERTISE:
+ case DHCPV6_REQUEST:
+ case DHCPV6_CONFIRM:
+ case DHCPV6_RENEW:
+ case DHCPV6_REBIND:
+ case DHCPV6_REPLY:
+ case DHCPV6_DECLINE:
+ case DHCPV6_RECONFIGURE:
+ case DHCPV6_INFORMATION_REQUEST:
+ default: // assume that uknown messages are not using relay format
+ {
+ return (unpackMsg(data_.begin(), data_.end()));
+ }
+ case DHCPV6_RELAY_FORW:
+ case DHCPV6_RELAY_REPL:
+ return (unpackRelayMsg());
+ }
+}
+
+bool
+Pkt6::unpackMsg(OptionBuffer::const_iterator begin,
+ OptionBuffer::const_iterator end) {
+ if (std::distance(begin, end) < 4) {
+ // truncated message (less than 4 bytes)
+ return (false);
+ }
+
+ msg_type_ = *begin++;
+
+ transid_ = ( (*begin++) << 16 ) +
+ ((*begin++) << 8) + (*begin++);
transid_ = transid_ & 0xffffff;
try {
- OptionBuffer opt_buffer(data_.begin() + 4, data_.end());
+ OptionBuffer opt_buffer(begin, end);
LibDHCP::unpackOptions6(opt_buffer, options_);
} catch (const Exception& e) {
@@ -143,6 +333,97 @@ Pkt6::unpackUDP() {
}
bool
+Pkt6::unpackRelayMsg() {
+
+ // we use offset + bufsize, because we want to avoid creating unnecessary
+ // copies. There may be up to 32 relays. While using InputBuffer would
+ // be probably a bit cleaner, copying data up to 32 times is unacceptable
+ // price here. Hence a single buffer with offets and lengths.
+ size_t bufsize = data_.size();
+ size_t offset = 0;
+
+ while (bufsize >= DHCPV6_RELAY_HDR_LEN) {
+
+ RelayInfo relay;
+
+ size_t relay_msg_offset = 0;
+ size_t relay_msg_len = 0;
+
+ // parse fixed header first (first 34 bytes)
+ relay.msg_type_ = data_[offset++];
+ relay.hop_count_ = data_[offset++];
+ relay.linkaddr_ = IOAddress::fromBytes(AF_INET6, &data_[offset]);
+ offset += isc::asiolink::V6ADDRESS_LEN;
+ relay.peeraddr_ = IOAddress::fromBytes(AF_INET6, &data_[offset]);
+ offset += isc::asiolink::V6ADDRESS_LEN;
+ bufsize -= DHCPV6_RELAY_HDR_LEN; // 34 bytes (1+1+16+16)
+
+ try {
+ // parse the rest as options
+ OptionBuffer opt_buffer(&data_[offset], &data_[offset+bufsize]);
+ LibDHCP::unpackOptions6(opt_buffer, relay.options_, &relay_msg_offset,
+ &relay_msg_len);
+
+ /// @todo: check that each option appears at most once
+ //relay.interface_id_ = options->getOption(D6O_INTERFACE_ID);
+ //relay.subscriber_id_ = options->getOption(D6O_SUBSCRIBER_ID);
+ //relay.remote_id_ = options->getOption(D6O_REMOTE_ID);
+
+ if (relay_msg_offset == 0 || relay_msg_len == 0) {
+ isc_throw(BadValue, "Mandatory relay-msg option missing");
+ }
+
+ // store relay information parsed so far
+ addRelayInfo(relay);
+
+ /// @todo: implement ERO here
+
+ if (relay_msg_len >= bufsize) {
+ // length of the relay_msg option extends beyond end of the message
+ isc_throw(Unexpected, "Relay-msg option is truncated.");
+ return false;
+ }
+ uint8_t inner_type = data_[offset + relay_msg_offset];
+ offset += relay_msg_offset; // offset is relative
+ bufsize = relay_msg_len; // length is absolute
+
+ if ( (inner_type != DHCPV6_RELAY_FORW) &&
+ (inner_type != DHCPV6_RELAY_REPL)) {
+ // Ok, the inner message is not encapsulated, let's decode it
+ // directly
+ return (unpackMsg(data_.begin() + offset, data_.begin() + offset
+ + relay_msg_len));
+ }
+
+ // Oh well, there's inner relay-forw or relay-repl inside. Let's
+ // unpack it as well. The next loop iteration will take care
+ // of that.
+ } catch (const Exception& e) {
+ /// @todo: throw exception here once we turn this function to void.
+ return (false);
+ }
+ }
+
+ if ( (offset == data_.size()) && (bufsize == 0) ) {
+ // message has been parsed completely
+ return (true);
+ }
+
+ /// @todo: log here that there are additional unparsed bytes
+ return (true);
+}
+
+void
+Pkt6::addRelayInfo(const RelayInfo& relay) {
+ if (relay_info_.size() > 32) {
+ isc_throw(BadValue, "Massage cannot be encapsulated more than 32 times");
+ }
+
+ /// @todo: Implement type checks here (e.g. we could receive relay-forw in relay-repl)
+ relay_info_.push_back(relay);
+}
+
+bool
Pkt6::unpackTCP() {
isc_throw(Unexpected, "DHCPv6 over TCP (bulk leasequery and failover) "
"not implemented yet.");
@@ -258,5 +539,33 @@ const char* Pkt6::getName() const {
return (getName(getType()));
}
+void Pkt6::copyRelayInfo(const Pkt6Ptr& question) {
+
+ // We use index rather than iterator, because we need that as a parameter
+ // passed to getRelayOption()
+ for (int i = 0; i < question->relay_info_.size(); ++i) {
+ RelayInfo info;
+ info.msg_type_ = DHCPV6_RELAY_REPL;
+ info.hop_count_ = question->relay_info_[i].hop_count_;
+ info.linkaddr_ = question->relay_info_[i].linkaddr_;
+ info.peeraddr_ = question->relay_info_[i].peeraddr_;
+
+ // Is there an interface-id option in this nesting level?
+ // If there is, we need to echo it back
+ OptionPtr opt = question->getRelayOption(D6O_INTERFACE_ID, i);
+ // taken from question->RelayInfo_[i].options_
+ if (opt) {
+ info.options_.insert(make_pair(opt->getType(), opt));
+ }
+
+ /// @todo: Implement support for ERO (Echo Request Option, RFC4994)
+
+ // Add this relay-forw info (client's message) to our relay-repl
+ // message (server's response)
+ relay_info_.push_back(info);
+ }
+}
+
+
} // end of isc::dhcp namespace
} // end of isc namespace
diff --git a/src/lib/dhcp/pkt6.h b/src/lib/dhcp/pkt6.h
index 6ffea2b..c65142e 100644
--- a/src/lib/dhcp/pkt6.h
+++ b/src/lib/dhcp/pkt6.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -30,17 +30,67 @@ namespace isc {
namespace dhcp {
+class Pkt6;
+typedef boost::shared_ptr<Pkt6> Pkt6Ptr;
+
class Pkt6 {
public:
- /// specifes DHCPv6 packet header length
+ /// specifies non-relayed DHCPv6 packet header length (over UDP)
const static size_t DHCPV6_PKT_HDR_LEN = 4;
+ /// specifies relay DHCPv6 packet header length (over UDP)
+ const static size_t DHCPV6_RELAY_HDR_LEN = 34;
+
/// DHCPv6 transport protocol
enum DHCPv6Proto {
UDP = 0, // most packets are UDP
TCP = 1 // there are TCP DHCPv6 packets (bulk leasequery, failover)
};
+ /// @brief defines relay search pattern
+ ///
+ /// Defines order in which options are searched in a message that
+ /// passed through mulitple relays. RELAY_SEACH_FROM_CLIENT will
+ /// start search from the relay that was the closest to the client
+ /// (i.e. innermost in the encapsulated message, which also means
+ /// this was the first relay that forwarded packet received by the
+ /// server and this will be the last relay that will handle the
+ /// response that server sent towards the client.).
+ /// RELAY_SEARCH_FROM_SERVER is the opposite. This will be the
+ /// relay closest to the server (i.e. outermost in the encapsulated
+ /// message, which also means it was the last relay that relayed
+ /// the received message and will be the first one to process
+ /// server's response). RELAY_GET_FIRST will try to get option from
+ /// the first relay only (closest to the client), RELAY_GET_LAST will
+ /// try to get option form the the last relay (closest to the server).
+ enum RelaySearchOrder {
+ RELAY_SEARCH_FROM_CLIENT = 1,
+ RELAY_SEARCH_FROM_SERVER = 2,
+ RELAY_GET_FIRST = 3,
+ RELAY_GET_LAST = 4
+ };
+
+ /// @brief structure that describes a single relay information
+ ///
+ /// Client sends messages. Each relay along its way will encapsulate the message.
+ /// This structure represents all information added by a single relay.
+ struct RelayInfo {
+
+ /// @brief default constructor
+ RelayInfo();
+ uint8_t msg_type_; ///< message type (RELAY-FORW oro RELAY-REPL)
+ uint8_t hop_count_; ///< number of traversed relays (up to 32)
+ isc::asiolink::IOAddress linkaddr_;///< fixed field in relay-forw/relay-reply
+ isc::asiolink::IOAddress peeraddr_;///< fixed field in relay-forw/relay-reply
+
+ /// @brief length of the relay_msg_len
+ /// Used when calculating length during pack/unpack
+ uint16_t relay_msg_len_;
+
+ /// options received from a specified relay, except relay-msg option
+ isc::dhcp::Option::OptionCollection options_;
+ };
+
/// Constructor, used in replying to a message
///
/// @param msg_type type of message (SOLICIT=1, ADVERTISE=2, ...)
@@ -89,7 +139,6 @@ public:
/// @return reference to output buffer
const isc::util::OutputBuffer& getBuffer() const { return (bufferOut_); };
-
/// @brief Returns reference to input buffer.
///
/// @return reference to input buffer
@@ -160,6 +209,35 @@ public:
/// @return pointer to found option (or NULL)
OptionPtr getOption(uint16_t type);
+ /// @brief returns option inserted by relay
+ ///
+ /// Returns an option from specified relay scope (inserted by a given relay
+ /// if this is received packet or to be decapsulated by a given relay if
+ /// this is a transmitted packet). nesting_level specifies which relay
+ /// scope is to be used. 0 is the outermost encapsulation (relay closest to
+ /// the server). pkt->relay_info_.size() - 1 is the innermost encapsulation
+ /// (relay closest to the client).
+ ///
+ /// @throw isc::OutOfRange if nesting level has invalid value.
+ ///
+ /// @param option_code code of the requested option
+ /// @param nesting_level see description above
+ ///
+ /// @return pointer to the option (or NULL if there is no such option)
+ OptionPtr getRelayOption(uint16_t option_code, uint8_t nesting_level);
+
+ /// @brief Return first instance of a specified option
+ ///
+ /// When a client's packet traverses multiple relays, each passing relay may
+ /// insert extra options. This method allows the specific instance of a given
+ /// option to be obtained (e.g. closest to the client, closest to the server,
+ /// etc.) See @ref RelaySearchOrder for a detailed description.
+ ///
+ /// @param option_code searched option
+ /// @param order option search order (see @ref RelaySearchOrder)
+ /// @return option pointer (or NULL if no option matches specified criteria)
+ OptionPtr getAnyRelayOption(uint16_t option_code, RelaySearchOrder order);
+
/// @brief Returns all instances of specified type.
///
/// Returns all instances of options of the specified type. DHCPv6 protocol
@@ -246,7 +324,7 @@ public:
/// @brief Returns packet timestamp.
///
/// Returns packet timestamp value updated when
- /// packet is received or send.
+ /// packet is received or sent.
///
/// @return packet timestamp.
const boost::posix_time::ptime& getTimestamp() const { return timestamp_; }
@@ -259,8 +337,18 @@ public:
/// @return interface name
void setIface(const std::string& iface ) { iface_ = iface; };
+ /// @brief add information about one traversed relay
+ ///
+ /// This adds information about one traversed relay, i.e.
+ /// one relay-forw or relay-repl level of encapsulation.
+ ///
+ /// @param relay structure with necessary relay information
+ void addRelayInfo(const RelayInfo& relay);
+
/// collection of options present in this message
///
+ /// @todo: Text mentions protected, but this is really public
+ ///
/// @warning This protected member is accessed by derived
/// classes directly. One of such derived classes is
/// @ref perfdhcp::PerfPkt6. The impact on derived clasess'
@@ -305,6 +393,23 @@ public:
/// be freed by the caller.
const char* getName() const;
+ /// @brief copies relay information from client's packet to server's response
+ ///
+ /// This information is not simply copied over. Some parameter are
+ /// removed, msg_type_is updated (RELAY-FORW => RELAY-REPL), etc.
+ ///
+ /// @param question client's packet
+ void copyRelayInfo(const Pkt6Ptr& question);
+
+ /// relay information
+ ///
+ /// this is a public field. Otherwise we hit one of the two problems:
+ /// we return reference to an internal field (and that reference could
+ /// be potentially used past Pkt6 object lifetime causing badness) or
+ /// we return a copy (which is inefficient and also causes any updates
+ /// to be impossible). Therefore public field is considered the best
+ /// (or least bad) solution.
+ std::vector<RelayInfo> relay_info_;
protected:
/// Builds on wire packet for TCP transmission.
///
@@ -340,6 +445,44 @@ protected:
/// @return true, if build was successful
bool unpackUDP();
+ /// @brief unpacks direct (non-relayed) message
+ ///
+ /// This method unpacks specified buffer range as a direct
+ /// (e.g. solicit or request) message. This method is called from
+ /// unpackUDP() when received message is detected to be direct.
+ ///
+ /// @param begin start of the buffer
+ /// @param end end of the buffer
+ /// @return true if parsing was successful and there are no leftover bytes
+ bool unpackMsg(OptionBuffer::const_iterator begin,
+ OptionBuffer::const_iterator end);
+
+ /// @brief unpacks relayed message (RELAY-FORW or RELAY-REPL)
+ ///
+ /// This method is called from unpackUDP() when received message
+ /// is detected to be relay-message. It goes iteratively over
+ /// all relays (if there are multiple encapsulation levels).
+ ///
+ /// @return true if parsing was successful
+ bool unpackRelayMsg();
+
+ /// @brief calculates overhead introduced in specified relay
+ ///
+ /// It is used when calculating message size and packing message
+ /// @param relay RelayInfo structure that holds information about relay
+ /// @return number of bytes needed to store relay information
+ uint16_t getRelayOverhead(const RelayInfo& relay) const;
+
+ /// @brief calculates overhead for all relays defined for this message
+ /// @return number of bytes needed to store all relay information
+ uint16_t calculateRelaySizes();
+
+ /// @brief calculates size of the message as if it was not relayed at all
+ ///
+ /// This is equal to len() if the message was not relayed.
+ /// @return number of bytes required to store the message
+ uint16_t directLen() const;
+
/// UDP (usually) or TCP (bulk leasequery or failover)
DHCPv6Proto proto_;
@@ -365,7 +508,7 @@ protected:
/// @brief interface index
///
/// interface index (each network interface has assigned unique ifindex
- /// it is functional equvalent of name, but sometimes more useful, e.g.
+ /// it is functional equivalent of name, but sometimes more useful, e.g.
/// when using crazy systems that allow spaces in interface names
/// e.g. windows
int ifindex_;
@@ -396,8 +539,6 @@ protected:
boost::posix_time::ptime timestamp_;
}; // Pkt6 class
-typedef boost::shared_ptr<Pkt6> Pkt6Ptr;
-
} // isc::dhcp namespace
} // isc namespace
diff --git a/src/lib/dhcp/pkt_filter.h b/src/lib/dhcp/pkt_filter.h
new file mode 100644
index 0000000..946bd14
--- /dev/null
+++ b/src/lib/dhcp/pkt_filter.h
@@ -0,0 +1,84 @@
+// 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 PKT_FILTER_H
+#define PKT_FILTER_H
+
+#include <asiolink/io_address.h>
+
+namespace isc {
+namespace dhcp {
+
+struct SocketInfo;
+
+/// Forward declaration to the class representing interface
+class Iface;
+
+/// @brief Abstract packet handling class
+///
+/// This class represents low level method to send and receive DHCP packet.
+/// Different methods, represented by classes derived from this class, use
+/// different socket families and socket types. Also, various packet filtering
+/// methods can be implemented by derived classes, e.g. Linux Packet
+/// Filtering (LPF) or Berkeley Packet Filtering (BPF).
+///
+/// Low-level code operating on sockets may require special privileges to execute.
+/// For example: opening raw socket or opening socket on low port number requires
+/// root privileges. This makes it impossible or very hard to unit test the IfaceMgr.
+/// In order to overcome this problem, it is recommended to create mock object derived
+/// from this class that mimics the behavior of the real packet handling class making
+/// IfaceMgr testable.
+class PktFilter {
+public:
+
+ /// @brief Virtual Destructor
+ virtual ~PktFilter() { }
+
+ /// @brief Open socket.
+ ///
+ /// @param iface interface descriptor
+ /// @param addr address on the interface to be used to send packets.
+ /// @param port port number.
+ /// @param receive_bcast configure socket to receive broadcast messages
+ /// @param send_bcast configure socket to send broadcast messages.
+ ///
+ /// @return created socket's descriptor
+ virtual int openSocket(const Iface& iface,
+ const isc::asiolink::IOAddress& addr,
+ const uint16_t port,
+ const bool receive_bcast,
+ const bool send_bcast) = 0;
+
+ /// @brief Receive packet over specified socket.
+ ///
+ /// @param iface interface
+ /// @param socket_info structure holding socket information
+ ///
+ /// @return Received packet
+ virtual Pkt4Ptr receive(const Iface& iface,
+ const SocketInfo& socket_info) = 0;
+
+ /// @brief Send packet over specified socket.
+ ///
+ /// @param sockfd socket descriptor
+ /// @param pkt packet to be sent
+ ///
+ /// @return result of sending the packet. It is 0 if successful.
+ virtual int send(uint16_t sockfd, const Pkt4Ptr& pkt) = 0;
+};
+
+} // namespace isc::dhcp
+} // namespace isc
+
+#endif // PKT_FILTER_H
diff --git a/src/lib/dhcp/pkt_filter_inet.cc b/src/lib/dhcp/pkt_filter_inet.cc
new file mode 100644
index 0000000..a6360aa
--- /dev/null
+++ b/src/lib/dhcp/pkt_filter_inet.cc
@@ -0,0 +1,264 @@
+// 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 <dhcp/iface_mgr.h>
+#include <dhcp/pkt4.h>
+#include <dhcp/pkt_filter_inet.h>
+
+using namespace isc::asiolink;
+
+namespace isc {
+namespace dhcp {
+
+PktFilterInet::PktFilterInet()
+ : control_buf_len_(CMSG_SPACE(sizeof(struct in6_pktinfo))),
+ control_buf_(new char[control_buf_len_])
+{
+}
+
+// iface is only used when SO_BINDTODEVICE is defined and thus
+// the code section using this variable is compiled.
+#ifdef SO_BINDTODEVICE
+int PktFilterInet::openSocket(const Iface& iface,
+ const isc::asiolink::IOAddress& addr,
+ const uint16_t port,
+ const bool receive_bcast,
+ const bool send_bcast) {
+
+#else
+int PktFilterInet::openSocket(const Iface&,
+ const isc::asiolink::IOAddress& addr,
+ const uint16_t port,
+ const bool receive_bcast,
+ const bool send_bcast) {
+
+
+#endif
+
+ struct sockaddr_in addr4;
+ memset(&addr4, 0, sizeof(sockaddr));
+ addr4.sin_family = AF_INET;
+ addr4.sin_port = htons(port);
+
+ // If we are to receive broadcast messages we have to bind
+ // to "ANY" address.
+ if (receive_bcast) {
+ addr4.sin_addr.s_addr = INADDR_ANY;
+ } else {
+ addr4.sin_addr.s_addr = htonl(addr);
+ }
+
+ int sock = socket(AF_INET, SOCK_DGRAM, 0);
+ if (sock < 0) {
+ isc_throw(SocketConfigError, "Failed to create UDP6 socket.");
+ }
+
+#ifdef SO_BINDTODEVICE
+ if (receive_bcast) {
+ // Bind to device so as we receive traffic on a specific interface.
+ if (setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, iface.getName().c_str(),
+ iface.getName().length() + 1) < 0) {
+ close(sock);
+ isc_throw(SocketConfigError, "Failed to set SO_BINDTODEVICE option"
+ << " on socket " << sock);
+ }
+ }
+#endif
+
+ if (send_bcast) {
+ // Enable sending to broadcast address.
+ int flag = 1;
+ if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &flag, sizeof(flag)) < 0) {
+ close(sock);
+ isc_throw(SocketConfigError, "Failed to set SO_BROADCAST option"
+ << " on socket " << sock);
+ }
+ }
+
+ if (bind(sock, (struct sockaddr *)&addr4, sizeof(addr4)) < 0) {
+ close(sock);
+ isc_throw(SocketConfigError, "Failed to bind socket " << sock << " to " << addr.toText()
+ << "/port=" << port);
+ }
+
+ // if there is no support for IP_PKTINFO, we are really out of luck
+ // it will be difficult to undersand, where this packet came from
+#if defined(IP_PKTINFO)
+ int flag = 1;
+ if (setsockopt(sock, IPPROTO_IP, IP_PKTINFO, &flag, sizeof(flag)) != 0) {
+ close(sock);
+ isc_throw(SocketConfigError, "setsockopt: IP_PKTINFO: failed.");
+ }
+#endif
+
+ return (sock);
+
+}
+
+Pkt4Ptr
+PktFilterInet::receive(const Iface& iface, const SocketInfo& socket_info) {
+ struct sockaddr_in from_addr;
+ uint8_t buf[IfaceMgr::RCVBUFSIZE];
+
+ memset(&control_buf_[0], 0, control_buf_len_);
+ memset(&from_addr, 0, sizeof(from_addr));
+
+ // Initialize our message header structure.
+ struct msghdr m;
+ memset(&m, 0, sizeof(m));
+
+ // Point so we can get the from address.
+ m.msg_name = &from_addr;
+ m.msg_namelen = sizeof(from_addr);
+
+ struct iovec v;
+ v.iov_base = static_cast<void*>(buf);
+ v.iov_len = IfaceMgr::RCVBUFSIZE;
+ m.msg_iov = &v;
+ m.msg_iovlen = 1;
+
+ // Getting the interface is a bit more involved.
+ //
+ // We set up some space for a "control message". We have
+ // previously asked the kernel to give us packet
+ // information (when we initialized the interface), so we
+ // should get the destination address from that.
+ m.msg_control = &control_buf_[0];
+ m.msg_controllen = control_buf_len_;
+
+ int result = recvmsg(socket_info.sockfd_, &m, 0);
+ if (result < 0) {
+ isc_throw(SocketReadError, "failed to receive UDP4 data");
+ }
+
+ // We have all data let's create Pkt4 object.
+ Pkt4Ptr pkt = Pkt4Ptr(new Pkt4(buf, result));
+
+ pkt->updateTimestamp();
+
+ unsigned int ifindex = iface.getIndex();
+
+ IOAddress from(htonl(from_addr.sin_addr.s_addr));
+ uint16_t from_port = htons(from_addr.sin_port);
+
+ // Set receiving interface based on information, which socket was used to
+ // receive data. OS-specific info (see os_receive4()) may be more reliable,
+ // so this value may be overwritten.
+ pkt->setIndex(ifindex);
+ pkt->setIface(iface.getName());
+ pkt->setRemoteAddr(from);
+ pkt->setRemotePort(from_port);
+ pkt->setLocalPort(socket_info.port_);
+
+// In the future the OS-specific code may be abstracted to a different
+// file but for now we keep it here because there is no code yet, which
+// is specific to non-Linux systems.
+#if defined (IP_PKTINFO) && defined (OS_LINUX)
+ struct cmsghdr* cmsg;
+ struct in_pktinfo* pktinfo;
+ struct in_addr to_addr;
+
+ memset(&to_addr, 0, sizeof(to_addr));
+
+ cmsg = CMSG_FIRSTHDR(&m);
+ while (cmsg != NULL) {
+ if ((cmsg->cmsg_level == IPPROTO_IP) &&
+ (cmsg->cmsg_type == IP_PKTINFO)) {
+ pktinfo = (struct in_pktinfo*)CMSG_DATA(cmsg);
+
+ pkt->setIndex(pktinfo->ipi_ifindex);
+ pkt->setLocalAddr(IOAddress(htonl(pktinfo->ipi_addr.s_addr)));
+ break;
+
+ // This field is useful, when we are bound to unicast
+ // address e.g. 192.0.2.1 and the packet was sent to
+ // broadcast. This will return broadcast address, not
+ // the address we are bound to.
+
+ // XXX: Perhaps we should uncomment this:
+ // to_addr = pktinfo->ipi_spec_dst;
+ }
+ cmsg = CMSG_NXTHDR(&m, cmsg);
+ }
+#endif
+
+ return (pkt);
+}
+
+int
+PktFilterInet::send(uint16_t sockfd, const Pkt4Ptr& pkt) {
+ memset(&control_buf_[0], 0, control_buf_len_);
+
+ // Set the target address we're sending to.
+ sockaddr_in to;
+ memset(&to, 0, sizeof(to));
+ to.sin_family = AF_INET;
+ to.sin_port = htons(pkt->getRemotePort());
+ to.sin_addr.s_addr = htonl(pkt->getRemoteAddr());
+
+ struct msghdr m;
+ // Initialize our message header structure.
+ memset(&m, 0, sizeof(m));
+ m.msg_name = &to;
+ m.msg_namelen = sizeof(to);
+
+ // Set the data buffer we're sending. (Using this wacky
+ // "scatter-gather" stuff... we only have a single chunk
+ // of data to send, so we declare a single vector entry.)
+ struct iovec v;
+ memset(&v, 0, sizeof(v));
+ // iov_base field is of void * type. We use it for packet
+ // transmission, so this buffer will not be modified.
+ v.iov_base = const_cast<void *>(pkt->getBuffer().getData());
+ v.iov_len = pkt->getBuffer().getLength();
+ m.msg_iov = &v;
+ m.msg_iovlen = 1;
+
+// In the future the OS-specific code may be abstracted to a different
+// file but for now we keep it here because there is no code yet, which
+// is specific to non-Linux systems.
+#if defined (IP_PKTINFO) && defined (OS_LINUX)
+ // Setting the interface is a bit more involved.
+ //
+ // We have to create a "control message", and set that to
+ // define the IPv4 packet information. We could set the
+ // source address if we wanted, but we can safely let the
+ // kernel decide what that should be.
+ m.msg_control = &control_buf_[0];
+ m.msg_controllen = control_buf_len_;
+ struct cmsghdr* cmsg = CMSG_FIRSTHDR(&m);
+ cmsg->cmsg_level = IPPROTO_IP;
+ cmsg->cmsg_type = IP_PKTINFO;
+ cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo));
+ struct in_pktinfo* pktinfo =(struct in_pktinfo *)CMSG_DATA(cmsg);
+ memset(pktinfo, 0, sizeof(struct in_pktinfo));
+ pktinfo->ipi_ifindex = pkt->getIndex();
+ m.msg_controllen = cmsg->cmsg_len;
+#endif
+
+ pkt->updateTimestamp();
+
+ int result = sendmsg(sockfd, &m, 0);
+ if (result < 0) {
+ isc_throw(SocketWriteError, "pkt4 send failed");
+ }
+
+ return (result);
+}
+
+
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
diff --git a/src/lib/dhcp/pkt_filter_inet.h b/src/lib/dhcp/pkt_filter_inet.h
new file mode 100644
index 0000000..4e98612
--- /dev/null
+++ b/src/lib/dhcp/pkt_filter_inet.h
@@ -0,0 +1,76 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef PKT_FILTER_INET_H
+#define PKT_FILTER_INET_H
+
+#include <dhcp/pkt_filter.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Packet handling class using AF_INET socket family
+///
+/// This class provides methods to send and recive packet via socket using
+/// AF_INET family and SOCK_DGRAM type.
+class PktFilterInet : public PktFilter {
+public:
+
+ /// @brief Constructor
+ ///
+ /// Allocates control buffer.
+ PktFilterInet();
+
+ /// @brief Open socket.
+ ///
+ /// @param iface interface descriptor
+ /// @param addr address on the interface to be used to send packets.
+ /// @param port port number.
+ /// @param receive_bcast configure socket to receive broadcast messages
+ /// @param send_bcast configure socket to send broadcast messages.
+ ///
+ /// @return created socket's descriptor
+ virtual int openSocket(const Iface& iface,
+ const isc::asiolink::IOAddress& addr,
+ const uint16_t port,
+ const bool receive_bcast,
+ const bool send_bcast);
+
+ /// @brief Receive packet over specified socket.
+ ///
+ /// @param iface interface
+ /// @param socket_info structure holding socket information
+ ///
+ /// @return Received packet
+ virtual Pkt4Ptr receive(const Iface& iface, const SocketInfo& socket_info);
+
+ /// @brief Send packet over specified socket.
+ ///
+ /// @param sockfd socket descriptor
+ /// @param pkt packet to be sent
+ ///
+ /// @return result of sending a packet. It is 0 if successful.
+ virtual int send(uint16_t sockfd, const Pkt4Ptr& pkt);
+
+private:
+ /// Length of the control_buf_ array.
+ size_t control_buf_len_;
+ /// Control buffer, used in transmission and reception.
+ boost::scoped_array<char> control_buf_;
+};
+
+} // namespace isc::dhcp
+} // namespace isc
+
+#endif // PKT_FILTER_INET_H
diff --git a/src/lib/dhcp/pkt_filter_lpf.cc b/src/lib/dhcp/pkt_filter_lpf.cc
new file mode 100644
index 0000000..ef75426
--- /dev/null
+++ b/src/lib/dhcp/pkt_filter_lpf.cc
@@ -0,0 +1,45 @@
+// 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 <dhcp/iface_mgr.h>
+#include <dhcp/pkt4.h>
+#include <dhcp/pkt_filter_lpf.h>
+
+namespace isc {
+namespace dhcp {
+
+int
+PktFilterLPF::openSocket(const Iface&, const isc::asiolink::IOAddress&,
+ const uint16_t, const bool,
+ const bool) {
+ isc_throw(isc::NotImplemented,
+ "Linux Packet Filtering is not implemented yet");
+}
+
+Pkt4Ptr
+PktFilterLPF::receive(const Iface&, const SocketInfo&) {
+ isc_throw(isc::NotImplemented,
+ "Linux Packet Filtering is not implemented yet");
+}
+
+int
+PktFilterLPF::send(uint16_t, const Pkt4Ptr&) {
+ isc_throw(isc::NotImplemented,
+ "Linux Packet Filtering is not implemented yet");
+}
+
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
diff --git a/src/lib/dhcp/pkt_filter_lpf.h b/src/lib/dhcp/pkt_filter_lpf.h
new file mode 100644
index 0000000..67b190f
--- /dev/null
+++ b/src/lib/dhcp/pkt_filter_lpf.h
@@ -0,0 +1,72 @@
+// 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 PKT_FILTER_LPF_H
+#define PKT_FILTER_LPF_H
+
+#include <dhcp/pkt_filter.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Packet handling class using Linux Packet Filtering
+///
+/// This class provides methods to send and recive packet using raw sockets
+/// and Linux Packet Filtering.
+///
+/// @warning This class is not implemented yet. Therefore all functions
+/// currently throw isc::NotImplemented exception.
+class PktFilterLPF : public PktFilter {
+public:
+
+ /// @brief Open socket.
+ ///
+ /// @param iface interface descriptor
+ /// @param addr address on the interface to be used to send packets.
+ /// @param port port number.
+ /// @param receive_bcast configure socket to receive broadcast messages
+ /// @param send_bcast configure socket to send broadcast messages.
+ ///
+ /// @throw isc::NotImplemented always
+ /// @return created socket's descriptor
+ virtual int openSocket(const Iface& iface,
+ const isc::asiolink::IOAddress& addr,
+ const uint16_t port,
+ const bool receive_bcast,
+ const bool send_bcast);
+
+ /// @brief Receive packet over specified socket.
+ ///
+ /// @param iface interface
+ /// @param socket_info structure holding socket information
+ ///
+ /// @throw isc::NotImplemented always
+ /// @return Received packet
+ virtual Pkt4Ptr receive(const Iface& iface, const SocketInfo& socket_info);
+
+ /// @brief Send packet over specified socket.
+ ///
+ /// @param sockfd socket descriptor
+ /// @param pkt packet to be sent
+ ///
+ /// @throw isc::NotImplemented always
+ /// @return result of sending a packet. It is 0 if successful.
+ virtual int send(uint16_t sockfd, const Pkt4Ptr& pkt);
+
+};
+
+} // namespace isc::dhcp
+} // namespace isc
+
+#endif // PKT_FILTER_LPF_H
diff --git a/src/lib/dhcp/tests/duid_unittest.cc b/src/lib/dhcp/tests/duid_unittest.cc
index 44aa561..36499c2 100644
--- a/src/lib/dhcp/tests/duid_unittest.cc
+++ b/src/lib/dhcp/tests/duid_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -37,6 +37,15 @@ using boost::scoped_ptr;
namespace {
+// This is a workaround for strange linking problems with gtest:
+// libdhcp___unittests-duid_unittest.o: In function `Compare<long unsigned int, long unsigned int>':
+// ~/gtest-1.6.0/include/gtest/gtest.h:1353: undefined reference to `isc::dhcp::ClientId::MAX_CLIENT_ID_LE'N
+// collect2: ld returned 1 exit status
+
+const size_t MAX_DUID_LEN = DUID::MAX_DUID_LEN;
+const size_t MAX_CLIENT_ID_LEN = DUID::MAX_DUID_LEN;
+
+
// This test verifies if the constructors are working as expected
// and process passed parameters.
TEST(DuidTest, constructor) {
@@ -61,21 +70,20 @@ TEST(DuidTest, constructor) {
// This test verifies if DUID size restrictions are implemented
// properly.
TEST(DuidTest, size) {
- const int MAX_DUID_SIZE = 128;
- uint8_t data[MAX_DUID_SIZE + 1];
+ uint8_t data[MAX_DUID_LEN + 1];
vector<uint8_t> data2;
- for (uint8_t i = 0; i < MAX_DUID_SIZE + 1; ++i) {
+ for (uint8_t i = 0; i < MAX_DUID_LEN + 1; ++i) {
data[i] = i;
- if (i < MAX_DUID_SIZE)
+ if (i < MAX_DUID_LEN)
data2.push_back(i);
}
- ASSERT_EQ(data2.size(), MAX_DUID_SIZE);
+ ASSERT_EQ(data2.size(), MAX_DUID_LEN);
- scoped_ptr<DUID> duidmaxsize1(new DUID(data, MAX_DUID_SIZE));
+ scoped_ptr<DUID> duidmaxsize1(new DUID(data, MAX_DUID_LEN));
scoped_ptr<DUID> duidmaxsize2(new DUID(data2));
EXPECT_THROW(
- scoped_ptr<DUID> toolarge1(new DUID(data, MAX_DUID_SIZE + 1)),
+ scoped_ptr<DUID> toolarge1(new DUID(data, MAX_DUID_LEN + 1)),
OutOfRange);
// that's one too much
@@ -84,6 +92,16 @@ TEST(DuidTest, size) {
EXPECT_THROW(
scoped_ptr<DUID> toolarge2(new DUID(data2)),
OutOfRange);
+
+ // empty duids are not allowed
+ vector<uint8_t> empty;
+ EXPECT_THROW(
+ scoped_ptr<DUID> emptyDuid(new DUID(empty)),
+ OutOfRange);
+
+ EXPECT_THROW(
+ scoped_ptr<DUID> emptyDuid2(new DUID(data, 0)),
+ OutOfRange);
}
// This test verifies if the implementation supports all defined
@@ -157,6 +175,51 @@ TEST(ClientIdTest, constructor) {
EXPECT_TRUE(data2 == vecdata);
}
+// Check that client-id sizes are reasonable
+TEST(ClientIdTest, size) {
+ uint8_t data[MAX_CLIENT_ID_LEN + 1];
+ vector<uint8_t> data2;
+ for (uint8_t i = 0; i < MAX_CLIENT_ID_LEN + 1; ++i) {
+ data[i] = i;
+ if (i < MAX_CLIENT_ID_LEN)
+ data2.push_back(i);
+ }
+ ASSERT_EQ(data2.size(), MAX_CLIENT_ID_LEN);
+
+ scoped_ptr<ClientId> duidmaxsize1(new ClientId(data, MAX_CLIENT_ID_LEN));
+ scoped_ptr<ClientId> duidmaxsize2(new ClientId(data2));
+
+ EXPECT_THROW(
+ scoped_ptr<ClientId> toolarge1(new ClientId(data, MAX_CLIENT_ID_LEN + 1)),
+ OutOfRange);
+
+ // that's one too much
+ data2.push_back(128);
+
+ EXPECT_THROW(
+ scoped_ptr<ClientId> toolarge2(new ClientId(data2)),
+ OutOfRange);
+
+ // empty client-ids are not allowed
+ vector<uint8_t> empty;
+ EXPECT_THROW(
+ scoped_ptr<ClientId> empty_client_id1(new ClientId(empty)),
+ OutOfRange);
+
+ EXPECT_THROW(
+ scoped_ptr<ClientId> empty_client_id2(new ClientId(data, 0)),
+ OutOfRange);
+
+ // client-id must be at least 2 bytes long
+ vector<uint8_t> shorty(1,17); // just a single byte with value 17
+ EXPECT_THROW(
+ scoped_ptr<ClientId> too_short_client_id1(new ClientId(shorty)),
+ OutOfRange);
+ EXPECT_THROW(
+ scoped_ptr<ClientId> too_short_client_id1(new ClientId(data, 1)),
+ OutOfRange);
+}
+
// This test checks if the comparison operators are sane.
TEST(ClientIdTest, operators) {
uint8_t data1[] = {0, 1, 2, 3, 4, 5, 6};
diff --git a/src/lib/dhcp/tests/hwaddr_unittest.cc b/src/lib/dhcp/tests/hwaddr_unittest.cc
index 144d62d..bf2eb9a 100644
--- a/src/lib/dhcp/tests/hwaddr_unittest.cc
+++ b/src/lib/dhcp/tests/hwaddr_unittest.cc
@@ -40,9 +40,11 @@ TEST(HWAddrTest, constructor) {
const uint8_t data1[] = {0, 1, 2, 3, 4, 5, 6};
const uint8_t htype = HTYPE_ETHER;
-
vector<uint8_t> data2(data1, data1 + sizeof(data1));
+ // Over the limit data
+ vector<uint8_t> big_data_vector(HWAddr::MAX_HWADDR_LEN + 1, 0);
+
scoped_ptr<HWAddr> hwaddr1(new HWAddr(data1, sizeof(data1), htype));
scoped_ptr<HWAddr> hwaddr2(new HWAddr(data2, htype));
scoped_ptr<HWAddr> hwaddr3(new HWAddr());
@@ -55,6 +57,13 @@ TEST(HWAddrTest, constructor) {
EXPECT_EQ(0, hwaddr3->hwaddr_.size());
EXPECT_EQ(htype, hwaddr3->htype_);
+
+ // Check that over the limit data length throws exception
+ EXPECT_THROW(HWAddr(&big_data_vector[0], big_data_vector.size(), HTYPE_ETHER),
+ InvalidParameter);
+
+ // Check that over the limit vector throws exception
+ EXPECT_THROW(HWAddr(big_data_vector, HTYPE_ETHER), InvalidParameter);
}
// This test checks if the comparison operators are sane.
diff --git a/src/lib/dhcp/tests/iface_mgr_unittest.cc b/src/lib/dhcp/tests/iface_mgr_unittest.cc
index 6f59ddb..ede7abf 100644
--- a/src/lib/dhcp/tests/iface_mgr_unittest.cc
+++ b/src/lib/dhcp/tests/iface_mgr_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -18,6 +18,7 @@
#include <dhcp/dhcp4.h>
#include <dhcp/iface_mgr.h>
#include <dhcp/pkt6.h>
+#include <dhcp/pkt_filter.h>
#include <boost/scoped_ptr.hpp>
#include <gtest/gtest.h>
@@ -33,6 +34,7 @@ using namespace std;
using namespace isc;
using namespace isc::asiolink;
using namespace isc::dhcp;
+using boost::scoped_ptr;
namespace {
@@ -44,25 +46,71 @@ char LOOPBACK[BUF_SIZE] = "lo";
const uint16_t PORT1 = 10547; // V6 socket
const uint16_t PORT2 = 10548; // V4 socket
-// On some systems measured duration of receive6() and
-// receive4() appears to be shorter than select() timeout.
-// called by these functions. This may be the case
-// if different ime resolutions are used by these functions.
-// For such cases we set the tolerance of 0.01s.
+// On some systems measured duration of receive6() and receive4() appears to be
+// shorter than select() timeout. This may be the case if different time
+// resolutions are used by these functions. For such cases we set the
+// tolerance to 0.01s.
const uint32_t TIMEOUT_TOLERANCE = 10000;
+/// Mock object implementing PktFilter class. It is used by
+/// IfaceMgrTest::setPacketFilter to verify that IfaceMgr::setPacketFilter
+/// sets this object as a handler for opening sockets. This dummy
+/// class simply records that openSocket function was called by
+/// the IfaceMgr as expected.
+///
+/// @todo This class currently doesn't verify that send/receive functions
+/// were called. In order to test it, there is a need to supply dummy
+/// function performing select() on certain sockets. The system select()
+/// call will fail when dummy socket descriptor is provided and thus
+/// TestPktFilter::receive will never be called. The appropriate extension
+/// to IfaceMgr is planned along with implementation of other "Packet
+/// Filters" such as these supporting Linux Packet Filtering and
+/// Berkley Packet Filtering.
+class TestPktFilter : public PktFilter {
+public:
+
+ /// Constructor
+ TestPktFilter()
+ : open_socket_called_(false) {
+ }
+
+ /// Pretends to open socket. Only records a call to this function.
+ virtual int openSocket(const Iface&,
+ const isc::asiolink::IOAddress&,
+ const uint16_t,
+ const bool,
+ const bool) {
+ open_socket_called_ = true;
+ return (1024);
+ }
+
+ /// Does nothing
+ virtual Pkt4Ptr receive(const Iface&,
+ const SocketInfo&) {
+ return (Pkt4Ptr());
+ }
+
+ /// Does nothing
+ virtual int send(uint16_t, const Pkt4Ptr&) {
+ return (0);
+ }
+
+ /// Holds the information whether openSocket was called on this
+ /// object after its creation.
+ bool open_socket_called_;
+};
class NakedIfaceMgr: public IfaceMgr {
- // "naked" Interface Manager, exposes internal fields
+ // "Naked" Interface Manager, exposes internal fields
public:
NakedIfaceMgr() { }
IfaceCollection & getIfacesLst() { return ifaces_; }
};
-// dummy class for now, but this will be expanded when needed
+// Dummy class for now, but this will be expanded when needed
class IfaceMgrTest : public ::testing::Test {
public:
- // these are empty for now, but let's keep them around
+ // These are empty for now, but let's keep them around
IfaceMgrTest() {
}
@@ -71,18 +119,17 @@ public:
};
-// We need some known interface to work reliably. Loopback interface
-// is named lo on Linux and lo0 on BSD boxes. We need to find out
-// which is available. This is not a real test, but rather a workaround
-// that will go away when interface detection is implemented.
+// We need some known interface to work reliably. Loopback interface is named
+// lo on Linux and lo0 on BSD boxes. We need to find out which is available.
+// This is not a real test, but rather a workaround that will go away when
+// interface detection is implemented.
// NOTE: At this stage of development, write access to current directory
// during running tests is required.
TEST_F(IfaceMgrTest, loDetect) {
- // poor man's interface detection
- // it will go away as soon as proper interface detection
- // is implemented
+ // Poor man's interface detection. It will go away as soon as proper
+ // interface detection is implemented
if (if_nametoindex("lo") > 0) {
snprintf(LOOPBACK, BUF_SIZE - 1, "lo");
} else if (if_nametoindex("lo0") > 0) {
@@ -103,7 +150,7 @@ TEST_F(IfaceMgrTest, loDetect) {
#if 0
TEST_F(IfaceMgrTest, dhcp6Sniffer) {
- // testing socket operation in a portable way is tricky
+ // Testing socket operation in a portable way is tricky
// without interface detection implemented
unlink("interfaces.txt");
@@ -112,13 +159,13 @@ TEST_F(IfaceMgrTest, dhcp6Sniffer) {
interfaces << "eth0 fe80::21e:8cff:fe9b:7349";
interfaces.close();
- NakedIfaceMgr* ifacemgr = new NakedIfaceMgr();
+ boost::scoped_ptr<NakedIfaceMgr> ifacemgr = new NakedIfaceMgr();
- Pkt6* pkt = NULL;
+ Pkt6Ptr pkt;
int cnt = 0;
cout << "---8X-----------------------------------------" << endl;
while (true) {
- pkt = ifacemgr->receive();
+ pkt.reset(ifacemgr->receive());
cout << "// this code is autogenerated. Do NOT edit." << endl;
cout << "// Received " << pkt->data_len_ << " bytes packet:" << endl;
@@ -146,31 +193,26 @@ TEST_F(IfaceMgrTest, dhcp6Sniffer) {
cout << " return (pkt);" << endl;
cout << "}" << endl << endl;
- delete pkt;
+ pkt.reset();
}
cout << "---8X-----------------------------------------" << endl;
- // never happens. Infinite loop is infinite
- delete pkt;
- delete ifacemgr;
+ // Never happens. Infinite loop is infinite
}
#endif
TEST_F(IfaceMgrTest, basic) {
- // checks that IfaceManager can be instantiated
+ // Checks that IfaceManager can be instantiated
IfaceMgr & ifacemgr = IfaceMgr::instance();
ASSERT_TRUE(&ifacemgr != 0);
}
TEST_F(IfaceMgrTest, ifaceClass) {
- // basic tests for Iface inner class
-
- IfaceMgr::Iface* iface = new IfaceMgr::Iface("eth5", 7);
+ // Basic tests for Iface inner class
- EXPECT_STREQ("eth5/7", iface->getFullName().c_str());
-
- delete iface;
+ Iface iface("eth5", 7);
+ EXPECT_STREQ("eth5/7", iface.getFullName().c_str());
}
// TODO: Implement getPlainMac() test as soon as interface detection
@@ -178,18 +220,18 @@ TEST_F(IfaceMgrTest, ifaceClass) {
TEST_F(IfaceMgrTest, getIface) {
cout << "Interface checks. Please ignore socket binding errors." << endl;
- NakedIfaceMgr* ifacemgr = new NakedIfaceMgr();
+ scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
- // interface name, ifindex
- IfaceMgr::Iface iface1("lo1", 100);
- IfaceMgr::Iface iface2("eth9", 101);
- IfaceMgr::Iface iface3("en3", 102);
- IfaceMgr::Iface iface4("e1000g4", 103);
+ // Interface name, ifindex
+ Iface iface1("lo1", 100);
+ Iface iface2("eth9", 101);
+ Iface iface3("en3", 102);
+ Iface iface4("e1000g4", 103);
cout << "This test assumes that there are less than 100 network interfaces"
<< " in the tested system and there are no lo1, eth9, en3, e1000g4"
<< " or wifi15 interfaces present." << endl;
- // note: real interfaces may be detected as well
+ // Note: real interfaces may be detected as well
ifacemgr->getIfacesLst().push_back(iface1);
ifacemgr->getIfacesLst().push_back(iface2);
ifacemgr->getIfacesLst().push_back(iface3);
@@ -204,25 +246,22 @@ TEST_F(IfaceMgrTest, getIface) {
}
- // check that interface can be retrieved by ifindex
- IfaceMgr::Iface* tmp = ifacemgr->getIface(102);
+ // Check that interface can be retrieved by ifindex
+ Iface* tmp = ifacemgr->getIface(102);
ASSERT_TRUE(tmp != NULL);
EXPECT_EQ("en3", tmp->getName());
EXPECT_EQ(102, tmp->getIndex());
- // check that interface can be retrieved by name
+ // Check that interface can be retrieved by name
tmp = ifacemgr->getIface("lo1");
ASSERT_TRUE(tmp != NULL);
EXPECT_EQ("lo1", tmp->getName());
EXPECT_EQ(100, tmp->getIndex());
- // check that non-existing interfaces are not returned
+ // Check that non-existing interfaces are not returned
EXPECT_EQ(static_cast<void*>(NULL), ifacemgr->getIface("wifi15") );
-
- delete ifacemgr;
-
}
TEST_F(IfaceMgrTest, receiveTimeout6) {
@@ -231,7 +270,7 @@ TEST_F(IfaceMgrTest, receiveTimeout6) {
<< " Test will block for a few seconds when waiting"
<< " for timeout to occur." << std::endl;
- boost::scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+ scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
// Open socket on the lo interface.
IOAddress loAddr("::1");
int socket1 = 0;
@@ -283,7 +322,7 @@ TEST_F(IfaceMgrTest, receiveTimeout4) {
<< " Test will block for a few seconds when waiting"
<< " for timeout to occur." << std::endl;
- boost::scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+ scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
// Open socket on the lo interface.
IOAddress loAddr("127.0.0.1");
int socket1 = 0;
@@ -330,12 +369,12 @@ TEST_F(IfaceMgrTest, receiveTimeout4) {
}
TEST_F(IfaceMgrTest, multipleSockets) {
- boost::scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+ scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
- // container for initialized socket descriptors
+ // Container for initialized socket descriptors
std::list<uint16_t> init_sockets;
- // create socket #1
+ // Create socket #1
int socket1 = 0;
ASSERT_NO_THROW(
socket1 = ifacemgr->openSocketFromIface(LOOPBACK, PORT1, AF_INET);
@@ -343,7 +382,7 @@ TEST_F(IfaceMgrTest, multipleSockets) {
ASSERT_GT(socket1, 0);
init_sockets.push_back(socket1);
- // create socket #2
+ // Create socket #2
IOAddress loAddr("127.0.0.1");
int socket2 = 0;
ASSERT_NO_THROW(
@@ -354,7 +393,7 @@ TEST_F(IfaceMgrTest, multipleSockets) {
// Get loopback interface. If we don't find one we are unable to run
// this test but we don't want to fail.
- IfaceMgr::Iface* iface_ptr = ifacemgr->getIface(LOOPBACK);
+ Iface* iface_ptr = ifacemgr->getIface(LOOPBACK);
if (iface_ptr == NULL) {
cout << "Local loopback interface not found. Skipping test. " << endl;
return;
@@ -362,7 +401,7 @@ TEST_F(IfaceMgrTest, multipleSockets) {
// Once sockets have been sucessfully opened, they are supposed to
// be on the list. Here we start to test if all expected sockets
// are on the list and no other (unexpected) socket is there.
- IfaceMgr::SocketCollection sockets = iface_ptr->getSockets();
+ Iface::SocketCollection sockets = iface_ptr->getSockets();
int matched_sockets = 0;
for (std::list<uint16_t>::iterator init_sockets_it =
init_sockets.begin();
@@ -379,7 +418,7 @@ TEST_F(IfaceMgrTest, multipleSockets) {
EXPECT_EQ(EWOULDBLOCK, errno);
// Apart from the ability to use the socket we want to make
// sure that socket on the list is the one that we created.
- for (IfaceMgr::SocketCollection::const_iterator socket_it =
+ for (Iface::SocketCollection::const_iterator socket_it =
sockets.begin(); socket_it != sockets.end(); ++socket_it) {
if (*init_sockets_it == socket_it->sockfd_) {
// This socket is the one that we created.
@@ -388,7 +427,7 @@ TEST_F(IfaceMgrTest, multipleSockets) {
}
}
}
- // all created sockets have been matched if this condition works.
+ // All created sockets have been matched if this condition works.
EXPECT_EQ(sockets.size(), matched_sockets);
// closeSockets() is the other function that we want to test. It
@@ -396,7 +435,7 @@ TEST_F(IfaceMgrTest, multipleSockets) {
// them anymore communication.
ifacemgr->closeSockets();
- // closed sockets are supposed to be removed from the list
+ // Closed sockets are supposed to be removed from the list
sockets = iface_ptr->getSockets();
ASSERT_EQ(0, sockets.size());
@@ -418,27 +457,27 @@ TEST_F(IfaceMgrTest, multipleSockets) {
}
TEST_F(IfaceMgrTest, sockets6) {
- // testing socket operation in a portable way is tricky
- // without interface detection implemented
+ // Testing socket operation in a portable way is tricky
+ // without interface detection implemented.
- boost::scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+ scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
IOAddress loAddr("::1");
Pkt6 pkt6(DHCPV6_SOLICIT, 123);
pkt6.setIface(LOOPBACK);
- // bind multicast socket to port 10547
+ // Bind multicast socket to port 10547
int socket1 = ifacemgr->openSocket(LOOPBACK, loAddr, 10547);
EXPECT_GT(socket1, 0); // socket > 0
EXPECT_EQ(socket1, ifacemgr->getSocket(pkt6));
- // bind unicast socket to port 10548
+ // Bind unicast socket to port 10548
int socket2 = ifacemgr->openSocket(LOOPBACK, loAddr, 10548);
EXPECT_GT(socket2, 0);
- // removed code for binding socket twice to the same address/port
+ // Removed code for binding socket twice to the same address/port
// as it caused problems on some platforms (e.g. Mac OS X)
// Close sockets here because the following tests will want to
@@ -463,7 +502,7 @@ TEST_F(IfaceMgrTest, sockets6) {
}
TEST_F(IfaceMgrTest, socketsFromIface) {
- boost::scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+ scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
// Open v6 socket on loopback interface and bind to port
int socket1 = 0;
@@ -499,7 +538,7 @@ TEST_F(IfaceMgrTest, socketsFromIface) {
TEST_F(IfaceMgrTest, socketsFromAddress) {
- boost::scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+ scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
// Open v6 socket on loopback interface and bind to port
int socket1 = 0;
@@ -534,7 +573,7 @@ TEST_F(IfaceMgrTest, socketsFromAddress) {
}
TEST_F(IfaceMgrTest, socketsFromRemoteAddress) {
- boost::scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+ scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
// Open v6 socket to connect to remote address.
// Loopback address is the only one that we know
@@ -582,7 +621,7 @@ TEST_F(IfaceMgrTest, DISABLED_sockets6Mcast) {
// testing socket operation in a portable way is tricky
// without interface detection implemented
- NakedIfaceMgr* ifacemgr = new NakedIfaceMgr();
+ scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
IOAddress loAddr("::1");
IOAddress mcastAddr("ff02::1:2");
@@ -603,8 +642,6 @@ TEST_F(IfaceMgrTest, DISABLED_sockets6Mcast) {
close(socket1);
close(socket2);
-
- delete ifacemgr;
}
TEST_F(IfaceMgrTest, sendReceive6) {
@@ -612,7 +649,7 @@ TEST_F(IfaceMgrTest, sendReceive6) {
// testing socket operation in a portable way is tricky
// without interface detection implemented
- boost::scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+ scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
// let's assume that every supported OS have lo interface
IOAddress loAddr("::1");
@@ -673,7 +710,7 @@ TEST_F(IfaceMgrTest, sendReceive4) {
// testing socket operation in a portable way is tricky
// without interface detection implemented
- boost::scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+ scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
// let's assume that every supported OS have lo interface
IOAddress loAddr("127.0.0.1");
@@ -759,10 +796,42 @@ TEST_F(IfaceMgrTest, sendReceive4) {
EXPECT_THROW(ifacemgr->send(sendPkt), SocketWriteError);
}
+// Verifies that it is possible to set custom packet filter object
+// to handle sockets opening and send/receive operation.
+TEST_F(IfaceMgrTest, setPacketFilter) {
+
+ // Create an instance of IfaceMgr.
+ boost::scoped_ptr<NakedIfaceMgr> iface_mgr(new NakedIfaceMgr());
+ ASSERT_TRUE(iface_mgr);
+
+ // Try to set NULL packet filter object and make sure it is rejected.
+ boost::shared_ptr<TestPktFilter> custom_packet_filter;
+ EXPECT_THROW(iface_mgr->setPacketFilter(custom_packet_filter),
+ isc::dhcp::InvalidPacketFilter);
+
+ // Create valid object and check if it can be set.
+ custom_packet_filter.reset(new TestPktFilter());
+ ASSERT_TRUE(custom_packet_filter);
+ ASSERT_NO_THROW(iface_mgr->setPacketFilter(custom_packet_filter));
+
+ // Try to open socket using IfaceMgr. It should call the openSocket() function
+ // on the packet filter object we have set.
+ IOAddress loAddr("127.0.0.1");
+ int socket1 = 0;
+ EXPECT_NO_THROW(
+ socket1 = iface_mgr->openSocket(LOOPBACK, loAddr, DHCP4_SERVER_PORT + 10000);
+ );
+
+ // Check that openSocket function was called.
+ EXPECT_TRUE(custom_packet_filter->open_socket_called_);
+ // This function always returns fake socket descriptor equal to 1024.
+ EXPECT_EQ(1024, socket1);
+}
+
TEST_F(IfaceMgrTest, socket4) {
- NakedIfaceMgr* ifacemgr = new NakedIfaceMgr();
+ scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
// Let's assume that every supported OS have lo interface.
IOAddress loAddr("127.0.0.1");
@@ -782,23 +851,19 @@ TEST_F(IfaceMgrTest, socket4) {
EXPECT_EQ(socket1, ifacemgr->getSocket(pkt));
close(socket1);
-
- delete ifacemgr;
}
// Test the Iface structure itself
TEST_F(IfaceMgrTest, iface) {
- IfaceMgr::Iface* iface = NULL;
- EXPECT_NO_THROW(
- iface = new IfaceMgr::Iface("eth0",1);
- );
+ boost::scoped_ptr<Iface> iface;
+ EXPECT_NO_THROW(iface.reset(new Iface("eth0",1)));
EXPECT_EQ("eth0", iface->getName());
EXPECT_EQ(1, iface->getIndex());
EXPECT_EQ("eth0/1", iface->getFullName());
// Let's make a copy of this address collection.
- IfaceMgr::AddressCollection addrs = iface->getAddresses();
+ Iface::AddressCollection addrs = iface->getAddresses();
EXPECT_EQ(0, addrs.size());
@@ -822,19 +887,17 @@ TEST_F(IfaceMgrTest, iface) {
EXPECT_EQ(0, addrs.size());
- EXPECT_NO_THROW(
- delete iface;
- );
+ EXPECT_NO_THROW(iface.reset());
}
TEST_F(IfaceMgrTest, iface_methods) {
- IfaceMgr::Iface iface("foo", 1234);
+ Iface iface("foo", 1234);
iface.setHWType(42);
EXPECT_EQ(42, iface.getHWType());
- uint8_t mac[IfaceMgr::MAX_MAC_LEN+10];
- for (int i = 0; i < IfaceMgr::MAX_MAC_LEN + 10; i++)
+ uint8_t mac[Iface::MAX_MAC_LEN+10];
+ for (int i = 0; i < Iface::MAX_MAC_LEN + 10; i++)
mac[i] = 255 - i;
EXPECT_EQ("foo", iface.getName());
@@ -843,7 +906,7 @@ TEST_F(IfaceMgrTest, iface_methods) {
// MAC is too long. Exception should be thrown and
// MAC length should not be set.
EXPECT_THROW(
- iface.setMac(mac, IfaceMgr::MAX_MAC_LEN + 1),
+ iface.setMac(mac, Iface::MAX_MAC_LEN + 1),
OutOfRange
);
@@ -851,34 +914,34 @@ TEST_F(IfaceMgrTest, iface_methods) {
EXPECT_EQ(0, iface.getMacLen());
// Setting maximum length MAC should be ok.
- iface.setMac(mac, IfaceMgr::MAX_MAC_LEN);
+ iface.setMac(mac, Iface::MAX_MAC_LEN);
// For some reason constants cannot be used directly in EXPECT_EQ
// as this produces linking error.
- size_t len = IfaceMgr::MAX_MAC_LEN;
+ size_t len = Iface::MAX_MAC_LEN;
EXPECT_EQ(len, iface.getMacLen());
EXPECT_EQ(0, memcmp(mac, iface.getMac(), iface.getMacLen()));
}
TEST_F(IfaceMgrTest, socketInfo) {
- // check that socketinfo for IPv4 socket is functional
- IfaceMgr::SocketInfo sock1(7, IOAddress("192.0.2.56"), DHCP4_SERVER_PORT + 7);
+ // Check that socketinfo for IPv4 socket is functional
+ SocketInfo sock1(7, IOAddress("192.0.2.56"), DHCP4_SERVER_PORT + 7);
EXPECT_EQ(7, sock1.sockfd_);
EXPECT_EQ("192.0.2.56", sock1.addr_.toText());
EXPECT_EQ(AF_INET, sock1.family_);
EXPECT_EQ(DHCP4_SERVER_PORT + 7, sock1.port_);
- // check that socketinfo for IPv6 socket is functional
- IfaceMgr::SocketInfo sock2(9, IOAddress("2001:db8:1::56"), DHCP4_SERVER_PORT + 9);
+ // Check that socketinfo for IPv6 socket is functional
+ SocketInfo sock2(9, IOAddress("2001:db8:1::56"), DHCP4_SERVER_PORT + 9);
EXPECT_EQ(9, sock2.sockfd_);
EXPECT_EQ("2001:db8:1::56", sock2.addr_.toText());
EXPECT_EQ(AF_INET6, sock2.family_);
EXPECT_EQ(DHCP4_SERVER_PORT + 9, sock2.port_);
- // now let's test if IfaceMgr handles socket info properly
- NakedIfaceMgr* ifacemgr = new NakedIfaceMgr();
- IfaceMgr::Iface* loopback = ifacemgr->getIface(LOOPBACK);
+ // Now let's test if IfaceMgr handles socket info properly
+ scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+ Iface* loopback = ifacemgr->getIface(LOOPBACK);
ASSERT_TRUE(loopback);
loopback->addSocket(sock1);
loopback->addSocket(sock2);
@@ -891,14 +954,14 @@ TEST_F(IfaceMgrTest, socketInfo) {
BadValue
);
- // try to send over non-existing interface
+ // Try to send over non-existing interface
pkt6.setIface("nosuchinterface45");
EXPECT_THROW(
ifacemgr->getSocket(pkt6),
BadValue
);
- // this will work
+ // This will work
pkt6.setIface(LOOPBACK);
EXPECT_EQ(9, ifacemgr->getSocket(pkt6));
@@ -908,13 +971,13 @@ TEST_F(IfaceMgrTest, socketInfo) {
);
EXPECT_EQ(true, deleted);
- // it should throw again, there's no usable socket anymore
+ // It should throw again, there's no usable socket anymore
EXPECT_THROW(
ifacemgr->getSocket(pkt6),
Unexpected
);
- // repeat for pkt4
+ // Repeat for pkt4
Pkt4 pkt4(DHCPDISCOVER, 1);
// pkt4 does not have interface set yet.
@@ -943,8 +1006,6 @@ TEST_F(IfaceMgrTest, socketInfo) {
ifacemgr->getSocket(pkt4),
Unexpected
);
-
- delete ifacemgr;
}
#if defined(OS_LINUX)
@@ -952,10 +1013,10 @@ TEST_F(IfaceMgrTest, socketInfo) {
/// @brief parses text representation of MAC address
///
/// This function parses text representation of a MAC address and stores
-/// it in binary format. Text format is expecte to be separate with
+/// it in binary format. Text format is expected to be separate with
/// semicolons, e.g. f4:6d:04:96:58:f2
///
-/// TODO: IfaceMgr::Iface::mac_ uses uint8_t* type, should be vector<uint8_t>
+/// TODO: Iface::mac_ uses uint8_t* type, should be vector<uint8_t>
///
/// @param textMac string with MAC address to parse
/// @param mac pointer to output buffer
@@ -1045,7 +1106,7 @@ void parse_ifconfig(const std::string& textFile, IfaceMgr::IfaceCollection& ifac
string name = line.substr(0, offset);
// sadly, ifconfig does not return ifindex
- ifaces.push_back(IfaceMgr::Iface(name, 0));
+ ifaces.push_back(Iface(name, 0));
iface = ifaces.end();
--iface; // points to the last element
@@ -1057,8 +1118,8 @@ void parse_ifconfig(const std::string& textFile, IfaceMgr::IfaceCollection& ifac
mac = line.substr(offset, string::npos);
mac = mac.substr(0, mac.find_first_of(" "));
- uint8_t buf[IfaceMgr::MAX_MAC_LEN];
- int mac_len = parse_mac(mac, buf, IfaceMgr::MAX_MAC_LEN);
+ uint8_t buf[Iface::MAX_MAC_LEN];
+ int mac_len = parse_mac(mac, buf, Iface::MAX_MAC_LEN);
iface->setMac(buf, mac_len);
}
}
@@ -1070,14 +1131,17 @@ void parse_ifconfig(const std::string& textFile, IfaceMgr::IfaceCollection& ifac
// Ubuntu style format: inet6 addr: ::1/128 Scope:Host
addr = line.substr(line.find("addr:") + 6, string::npos);
} else {
- // Gentoo style format: inet6 fe80::6ef0:49ff:fe96:ba17 prefixlen 64 scopeid 0x20<link>
+ // Gentoo style format: inet6 fe80::6ef0:49ff:fe96:ba17
+ // prefixlen 64 scopeid 0x20<link>
addr = line.substr(line.find("inet6") + 6, string::npos);
}
- // handle Ubuntu format: inet6 addr: fe80::f66d:4ff:fe96:58f2/64 Scope:Link
+ // Handle Ubuntu format: inet6 addr: fe80::f66d:4ff:fe96:58f2/64
+ // Scope:Link
addr = addr.substr(0, addr.find("/"));
- // handle inet6 fe80::ca3a:35ff:fed4:8f1d prefixlen 64 scopeid 0x20<link>
+ // Handle inet6 fe80::ca3a:35ff:fed4:8f1d prefixlen 64
+ // scopeid 0x20<link>
addr = addr.substr(0, addr.find(" "));
IOAddress a(addr);
iface->addAddress(a);
@@ -1096,7 +1160,7 @@ void parse_ifconfig(const std::string& textFile, IfaceMgr::IfaceCollection& ifac
IOAddress a(addr);
iface->addAddress(a);
} else if(line.find("Metric") != string::npos) {
- // flags
+ // Flags
if (line.find("UP") != string::npos) {
iface->flag_up_ = true;
}
@@ -1117,15 +1181,16 @@ void parse_ifconfig(const std::string& textFile, IfaceMgr::IfaceCollection& ifac
}
-// This test compares implemented detection routines to output of "ifconfig -a" command.
-// It is far from perfect, but it is able to verify that interface names, flags,
-// MAC address, IPv4 and IPv6 addresses are detected properly. Interface list completeness
-// (check that each interface is reported, i.e. no missing or extra interfaces) and
-// address completeness is verified.
+// This test compares implemented detection routines to output of "ifconfig
+// -a" command. It is far from perfect, but it is able to verify that
+// interface names, flags, MAC address, IPv4 and IPv6 addresses are detected
+// properly. Interface list completeness (check that each interface is reported,
+// i.e. no missing or extra interfaces) and address completeness is verified.
//
// Things that are not tested:
// - ifindex (ifconfig does not print it out)
-// - address scopes and lifetimes (we don't need it, so it is not implemented in IfaceMgr)
+// - address scopes and lifetimes (we don't need it, so it is not implemented
+// in IfaceMgr)
// TODO: temporarily disabled, see ticket #1529
TEST_F(IfaceMgrTest, DISABLED_detectIfaces_linux) {
@@ -1139,7 +1204,7 @@ TEST_F(IfaceMgrTest, DISABLED_detectIfaces_linux) {
ASSERT_EQ(0, result);
- // list of interfaces parsed from ifconfig
+ // List of interfaces parsed from ifconfig
IfaceMgr::IfaceCollection parsedIfaces;
ASSERT_NO_THROW(
@@ -1165,15 +1230,15 @@ TEST_F(IfaceMgrTest, DISABLED_detectIfaces_linux) {
cout << " BROADCAST";
}
cout << ", addrs:";
- const IfaceMgr::AddressCollection& addrs = i->getAddresses();
- for (IfaceMgr::AddressCollection::const_iterator a= addrs.begin();
+ const Iface::AddressCollection& addrs = i->getAddresses();
+ for (Iface::AddressCollection::const_iterator a= addrs.begin();
a != addrs.end(); ++a) {
cout << a->toText() << " ";
}
cout << endl;
}
- // Ok, now we have 2 lists of interfaces. Need to compare them
+ // OK, now we have 2 lists of interfaces. Need to compare them
ASSERT_EQ(detectedIfaces.size(), parsedIfaces.size());
// TODO: This could could probably be written simple with find()
@@ -1191,14 +1256,14 @@ TEST_F(IfaceMgrTest, DISABLED_detectIfaces_linux) {
cout << "Checking interface " << detected->getName() << endl;
- // start with checking flags
+ // Start with checking flags
EXPECT_EQ(detected->flag_loopback_, i->flag_loopback_);
EXPECT_EQ(detected->flag_up_, i->flag_up_);
EXPECT_EQ(detected->flag_running_, i->flag_running_);
EXPECT_EQ(detected->flag_multicast_, i->flag_multicast_);
EXPECT_EQ(detected->flag_broadcast_, i->flag_broadcast_);
- // skip MAC comparison for loopback as netlink returns MAC
+ // Skip MAC comparison for loopback as netlink returns MAC
// 00:00:00:00:00:00 for lo
if (!detected->flag_loopback_) {
ASSERT_EQ(detected->getMacLen(), i->getMacLen());
@@ -1207,14 +1272,14 @@ TEST_F(IfaceMgrTest, DISABLED_detectIfaces_linux) {
EXPECT_EQ(detected->getAddresses().size(), i->getAddresses().size());
- // now compare addresses
- const IfaceMgr::AddressCollection& addrs = detected->getAddresses();
- for (IfaceMgr::AddressCollection::const_iterator addr = addrs.begin();
+ // Now compare addresses
+ const Iface::AddressCollection& addrs = detected->getAddresses();
+ for (Iface::AddressCollection::const_iterator addr = addrs.begin();
addr != addrs.end(); ++addr) {
bool addr_found = false;
- const IfaceMgr::AddressCollection& addrs2 = detected->getAddresses();
- for (IfaceMgr::AddressCollection::const_iterator a = addrs2.begin();
+ const Iface::AddressCollection& addrs2 = detected->getAddresses();
+ for (Iface::AddressCollection::const_iterator a = addrs2.begin();
a != addrs2.end(); ++a) {
if (*addr != *a) {
continue;
@@ -1230,7 +1295,7 @@ TEST_F(IfaceMgrTest, DISABLED_detectIfaces_linux) {
<< " matched with 'ifconfig -a' output." << endl;
}
}
- if (!found) { // corresponding interface was not found
+ if (!found) { // Corresponding interface was not found
FAIL();
}
}
@@ -1247,14 +1312,14 @@ void my_callback(void) {
}
TEST_F(IfaceMgrTest, controlSession) {
- // tests if extra control socket and its callback can be passed and
+ // Tests if extra control socket and its callback can be passed and
// it is supported properly by receive4() method.
callback_ok = false;
- NakedIfaceMgr* ifacemgr = new NakedIfaceMgr();
+ scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
- // create pipe and register it as extra socket
+ // Create pipe and register it as extra socket
int pipefd[2];
EXPECT_TRUE(pipe(pipefd) == 0);
EXPECT_NO_THROW(ifacemgr->set_session_socket(pipefd[0], my_callback));
@@ -1280,8 +1345,6 @@ TEST_F(IfaceMgrTest, controlSession) {
// There was some data, so this time callback should be called
EXPECT_TRUE(callback_ok);
- delete ifacemgr;
-
// close both pipe ends
close(pipefd[1]);
close(pipefd[0]);
diff --git a/src/lib/dhcp/tests/libdhcp++_unittest.cc b/src/lib/dhcp/tests/libdhcp++_unittest.cc
index dbab492..f924427 100644
--- a/src/lib/dhcp/tests/libdhcp++_unittest.cc
+++ b/src/lib/dhcp/tests/libdhcp++_unittest.cc
@@ -52,8 +52,7 @@ public:
/// @param buf option-buffer
static OptionPtr genericOptionFactory(Option::Universe u, uint16_t type,
const OptionBuffer& buf) {
- Option* option = new Option(u, type, buf);
- return OptionPtr(option);
+ return (OptionPtr(new Option(u, type, buf)));
}
/// @brief Test DHCPv4 option definition.
@@ -265,11 +264,11 @@ TEST_F(LibDhcpTest, packOptions6) {
OptionPtr opt4(new Option(Option::V6, 6, buf.begin() + 8, buf.begin() + 12));
OptionPtr opt5(new Option(Option::V6, 8, buf.begin() + 12, buf.begin() + 14));
- opts.insert(pair<int, OptionPtr >(opt1->getType(), opt1));
- opts.insert(pair<int, OptionPtr >(opt1->getType(), opt2));
- opts.insert(pair<int, OptionPtr >(opt1->getType(), opt3));
- opts.insert(pair<int, OptionPtr >(opt1->getType(), opt4));
- opts.insert(pair<int, OptionPtr >(opt1->getType(), opt5));
+ opts.insert(make_pair(opt1->getType(), opt1));
+ opts.insert(make_pair(opt1->getType(), opt2));
+ opts.insert(make_pair(opt1->getType(), opt3));
+ opts.insert(make_pair(opt1->getType(), opt4));
+ opts.insert(make_pair(opt1->getType(), opt5));
OutputBuffer assembled(512);
diff --git a/src/lib/dhcp/tests/option4_addrlst_unittest.cc b/src/lib/dhcp/tests/option4_addrlst_unittest.cc
index 0c1d9e6..767d690 100644
--- a/src/lib/dhcp/tests/option4_addrlst_unittest.cc
+++ b/src/lib/dhcp/tests/option4_addrlst_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -21,6 +21,7 @@
#include <util/buffer.h>
#include <gtest/gtest.h>
+#include <boost/scoped_ptr.hpp>
#include <iostream>
#include <sstream>
@@ -32,6 +33,7 @@ using namespace isc;
using namespace isc::dhcp;
using namespace isc::asiolink;
using namespace isc::util;
+using boost::scoped_ptr;
namespace {
@@ -62,7 +64,7 @@ class Option4AddrLstTest : public ::testing::Test {
protected:
Option4AddrLstTest():
- vec_(vector<uint8_t>(300,0)) // 300 bytes long filled with 0s
+ vec_(vector<uint8_t>(300, 0)) // 300 bytes long filled with 0s
{
sampleAddrs_.push_back(IOAddress("192.0.2.3"));
sampleAddrs_.push_back(IOAddress("255.255.255.0"));
@@ -79,13 +81,13 @@ TEST_F(Option4AddrLstTest, parse1) {
memcpy(&vec_[0], sampledata, sizeof(sampledata));
- // just one address
- Option4AddrLst* opt1 = 0;
+ // Just one address
+ scoped_ptr<Option4AddrLst> opt1;
EXPECT_NO_THROW(
- opt1 = new Option4AddrLst(DHO_DOMAIN_NAME_SERVERS,
- vec_.begin(),
- vec_.begin()+4);
- // use just first address (4 bytes), not the whole
+ opt1.reset(new Option4AddrLst(DHO_DOMAIN_NAME_SERVERS,
+ vec_.begin(),
+ vec_.begin()+4));
+ // Use just first address (4 bytes), not the whole
// sampledata
);
@@ -99,26 +101,21 @@ TEST_F(Option4AddrLstTest, parse1) {
EXPECT_EQ("192.0.2.3", addrs[0].toText());
- EXPECT_NO_THROW(
- delete opt1;
- opt1 = 0;
- );
-
- // 1 address
+ EXPECT_NO_THROW(opt1.reset());
}
TEST_F(Option4AddrLstTest, parse4) {
- vector<uint8_t> buffer(300,0); // 300 bytes long filled with 0s
+ vector<uint8_t> buffer(300, 0); // 300 bytes long filled with 0s
memcpy(&buffer[0], sampledata, sizeof(sampledata));
// 4 addresses
- Option4AddrLst* opt4 = 0;
+ scoped_ptr<Option4AddrLst> opt4;
EXPECT_NO_THROW(
- opt4 = new Option4AddrLst(254,
- buffer.begin(),
- buffer.begin()+sizeof(sampledata));
+ opt4.reset(new Option4AddrLst(254,
+ buffer.begin(),
+ buffer.begin()+sizeof(sampledata)));
);
EXPECT_EQ(Option::V4, opt4->getUniverse());
@@ -134,17 +131,15 @@ TEST_F(Option4AddrLstTest, parse4) {
EXPECT_EQ("0.0.0.0", addrs[2].toText());
EXPECT_EQ("127.0.0.1", addrs[3].toText());
- EXPECT_NO_THROW(
- delete opt4;
- opt4 = 0;
- );
+ EXPECT_NO_THROW(opt4.reset());
}
TEST_F(Option4AddrLstTest, assembly1) {
- Option4AddrLst* opt = 0;
+ scoped_ptr<Option4AddrLst> opt;
EXPECT_NO_THROW(
- opt = new Option4AddrLst(DHO_DOMAIN_NAME_SERVERS, IOAddress("192.0.2.3"));
+ opt.reset(new Option4AddrLst(DHO_DOMAIN_NAME_SERVERS,
+ IOAddress("192.0.2.3")));
);
EXPECT_EQ(Option::V4, opt->getUniverse());
EXPECT_EQ(DHO_DOMAIN_NAME_SERVERS, opt->getType());
@@ -163,28 +158,21 @@ TEST_F(Option4AddrLstTest, assembly1) {
EXPECT_EQ(0, memcmp(expected1, buf.getData(), 6));
- EXPECT_NO_THROW(
- delete opt;
- opt = 0;
- );
+ EXPECT_NO_THROW(opt.reset());
// This is old-fashioned option. We don't serve IPv6 types here!
EXPECT_THROW(
- opt = new Option4AddrLst(DHO_DOMAIN_NAME_SERVERS, IOAddress("2001:db8::1")),
+ opt.reset(new Option4AddrLst(DHO_DOMAIN_NAME_SERVERS,
+ IOAddress("2001:db8::1"))),
BadValue
);
- if (opt) {
- // test failed. Execption was not thrown, but option was created instead.
- delete opt;
- }
}
TEST_F(Option4AddrLstTest, assembly4) {
-
- Option4AddrLst* opt = 0;
+ scoped_ptr<Option4AddrLst> opt;
EXPECT_NO_THROW(
- opt = new Option4AddrLst(254, sampleAddrs_);
+ opt.reset(new Option4AddrLst(254, sampleAddrs_));
);
EXPECT_EQ(Option::V4, opt->getUniverse());
EXPECT_EQ(254, opt->getType());
@@ -206,27 +194,21 @@ TEST_F(Option4AddrLstTest, assembly4) {
ASSERT_EQ(0, memcmp(expected4, buf.getData(), 18));
- EXPECT_NO_THROW(
- delete opt;
- opt = 0;
- );
+ EXPECT_NO_THROW(opt.reset());
// This is old-fashioned option. We don't serve IPv6 types here!
sampleAddrs_.push_back(IOAddress("2001:db8::1"));
EXPECT_THROW(
- opt = new Option4AddrLst(DHO_DOMAIN_NAME_SERVERS, sampleAddrs_),
+ opt.reset(new Option4AddrLst(DHO_DOMAIN_NAME_SERVERS, sampleAddrs_)),
BadValue
);
- if (opt) {
- // test failed. Execption was not thrown, but option was created instead.
- delete opt;
- }
}
TEST_F(Option4AddrLstTest, setAddress) {
- Option4AddrLst* opt = 0;
+
+ scoped_ptr<Option4AddrLst> opt;
EXPECT_NO_THROW(
- opt = new Option4AddrLst(123, IOAddress("1.2.3.4"));
+ opt.reset(new Option4AddrLst(123, IOAddress("1.2.3.4")));
);
opt->setAddress(IOAddress("192.0.255.255"));
@@ -240,17 +222,15 @@ TEST_F(Option4AddrLstTest, setAddress) {
BadValue
);
- EXPECT_NO_THROW(
- delete opt;
- );
+ EXPECT_NO_THROW(opt.reset());
}
TEST_F(Option4AddrLstTest, setAddresses) {
- Option4AddrLst* opt = 0;
+ scoped_ptr<Option4AddrLst> opt;
EXPECT_NO_THROW(
- opt = new Option4AddrLst(123); // empty list
+ opt.reset(new Option4AddrLst(123)); // Empty list
);
opt->setAddresses(sampleAddrs_);
@@ -269,9 +249,7 @@ TEST_F(Option4AddrLstTest, setAddresses) {
BadValue
);
- EXPECT_NO_THROW(
- delete opt;
- );
+ EXPECT_NO_THROW(opt.reset());
}
} // namespace
diff --git a/src/lib/dhcp/tests/option6_addrlst_unittest.cc b/src/lib/dhcp/tests/option6_addrlst_unittest.cc
index 96db59c..35a7829 100644
--- a/src/lib/dhcp/tests/option6_addrlst_unittest.cc
+++ b/src/lib/dhcp/tests/option6_addrlst_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -21,6 +21,7 @@
#include <util/buffer.h>
#include <gtest/gtest.h>
+#include <boost/scoped_ptr.hpp>
#include <iostream>
#include <sstream>
@@ -32,6 +33,7 @@ using namespace isc;
using namespace isc::dhcp;
using namespace isc::asiolink;
using namespace isc::util;
+using boost::scoped_ptr;
namespace {
class Option6AddrLstTest : public ::testing::Test {
@@ -47,7 +49,7 @@ public:
TEST_F(Option6AddrLstTest, basic) {
- // Limiting tests to just a 2001:db8::/32 as is *wrong*.
+ // Limiting tests to just a 2001:db8::/32 is *wrong*.
// Good tests check corner cases as well.
// ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff checks
// for integer overflow.
@@ -110,10 +112,11 @@ TEST_F(Option6AddrLstTest, basic) {
memcpy(&buf_[0], sampledata, 48);
- // just a single address
- Option6AddrLst* opt1 = 0;
+ // Just a single address
+ scoped_ptr<Option6AddrLst> opt1;
EXPECT_NO_THROW(
- opt1 = new Option6AddrLst(D6O_NAME_SERVERS, buf_.begin(), buf_.begin() + 16 );
+ opt1.reset(new Option6AddrLst(D6O_NAME_SERVERS,
+ buf_.begin(), buf_.begin() + 16));
);
EXPECT_EQ(Option::V6, opt1->getUniverse());
@@ -125,16 +128,17 @@ TEST_F(Option6AddrLstTest, basic) {
IOAddress addr = addrs[0];
EXPECT_EQ("2001:db8:1::dead:beef", addr.toText());
- // pack this option
+ // Pack this option
opt1->pack(outBuf_);
EXPECT_EQ(20, outBuf_.getLength());
EXPECT_EQ(0, memcmp(expected1, outBuf_.getData(), 20));
- // two addresses
- Option6AddrLst* opt2 = 0;
+ // Two addresses
+ scoped_ptr<Option6AddrLst> opt2;
EXPECT_NO_THROW(
- opt2 = new Option6AddrLst(D6O_SIP_SERVERS_ADDR, buf_.begin(), buf_.begin() + 32);
+ opt2.reset(new Option6AddrLst(D6O_SIP_SERVERS_ADDR,
+ buf_.begin(), buf_.begin() + 32));
);
EXPECT_EQ(D6O_SIP_SERVERS_ADDR, opt2->getType());
EXPECT_EQ(36, opt2->len());
@@ -143,17 +147,18 @@ TEST_F(Option6AddrLstTest, basic) {
EXPECT_EQ("2001:db8:1::dead:beef", addrs[0].toText());
EXPECT_EQ("ff02::face:b00c", addrs[1].toText());
- // pack this option
+ // Pack this option
outBuf_.clear();
opt2->pack(outBuf_);
EXPECT_EQ(36, outBuf_.getLength() );
EXPECT_EQ(0, memcmp(expected2, outBuf_.getData(), 36));
- // three addresses
- Option6AddrLst* opt3 = 0;
+ // Three addresses
+ scoped_ptr<Option6AddrLst> opt3;
EXPECT_NO_THROW(
- opt3 = new Option6AddrLst(D6O_NIS_SERVERS, buf_.begin(), buf_.begin() + 48);
+ opt3.reset(new Option6AddrLst(D6O_NIS_SERVERS,
+ buf_.begin(), buf_.begin() + 48));
);
EXPECT_EQ(D6O_NIS_SERVERS, opt3->getType());
@@ -164,25 +169,23 @@ TEST_F(Option6AddrLstTest, basic) {
EXPECT_EQ("ff02::face:b00c", addrs[1].toText());
EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", addrs[2].toText());
- // pack this option
+ // Pack this option
outBuf_.clear();
opt3->pack(outBuf_);
EXPECT_EQ(52, outBuf_.getLength());
EXPECT_EQ(0, memcmp(expected3, outBuf_.getData(), 52));
- EXPECT_NO_THROW(
- delete opt1;
- delete opt2;
- delete opt3;
- );
+ EXPECT_NO_THROW(opt1.reset());
+ EXPECT_NO_THROW(opt2.reset());
+ EXPECT_NO_THROW(opt3.reset());
}
TEST_F(Option6AddrLstTest, constructors) {
- Option6AddrLst* opt1 = 0;
+ scoped_ptr<Option6AddrLst> opt1;
EXPECT_NO_THROW(
- opt1 = new Option6AddrLst(1234, IOAddress("::1"));
+ opt1.reset(new Option6AddrLst(1234, IOAddress("::1")));
);
EXPECT_EQ(Option::V6, opt1->getUniverse());
EXPECT_EQ(1234, opt1->getType());
@@ -195,9 +198,9 @@ TEST_F(Option6AddrLstTest, constructors) {
addrs.push_back(IOAddress(string("fe80::1234")));
addrs.push_back(IOAddress(string("2001:db8:1::baca")));
- Option6AddrLst* opt2 = 0;
+ scoped_ptr<Option6AddrLst> opt2;
EXPECT_NO_THROW(
- opt2 = new Option6AddrLst(5678, addrs);
+ opt2.reset(new Option6AddrLst(5678, addrs));
);
Option6AddrLst::AddressContainer check = opt2->getAddresses();
@@ -205,16 +208,14 @@ TEST_F(Option6AddrLstTest, constructors) {
EXPECT_EQ("fe80::1234", check[0].toText());
EXPECT_EQ("2001:db8:1::baca", check[1].toText());
- EXPECT_NO_THROW(
- delete opt1;
- delete opt2;
- );
+ EXPECT_NO_THROW(opt1.reset());
+ EXPECT_NO_THROW(opt2.reset());
}
TEST_F(Option6AddrLstTest, setAddress) {
- Option6AddrLst* opt1 = 0;
+ scoped_ptr<Option6AddrLst> opt1;
EXPECT_NO_THROW(
- opt1 = new Option6AddrLst(1234, IOAddress("::1"));
+ opt1.reset(new Option6AddrLst(1234, IOAddress("::1")));
);
opt1->setAddress(IOAddress("2001:db8:1::2"));
/// TODO It used to be ::2 address, but io_address represents
@@ -227,12 +228,10 @@ TEST_F(Option6AddrLstTest, setAddress) {
/// a test for IOAddress)
Option6AddrLst::AddressContainer addrs = opt1->getAddresses();
- ASSERT_EQ(1, addrs.size() );
+ ASSERT_EQ(1, addrs.size());
EXPECT_EQ("2001:db8:1::2", addrs[0].toText());
- EXPECT_NO_THROW(
- delete opt1;
- );
+ EXPECT_NO_THROW(opt1.reset());
}
} // namespace
diff --git a/src/lib/dhcp/tests/option6_ia_unittest.cc b/src/lib/dhcp/tests/option6_ia_unittest.cc
index 24f101d..071edb9 100644
--- a/src/lib/dhcp/tests/option6_ia_unittest.cc
+++ b/src/lib/dhcp/tests/option6_ia_unittest.cc
@@ -20,6 +20,7 @@
#include <dhcp/option6_iaaddr.h>
#include <util/buffer.h>
+#include <boost/scoped_ptr.hpp>
#include <gtest/gtest.h>
#include <iostream>
@@ -32,6 +33,7 @@ using namespace isc;
using namespace isc::dhcp;
using namespace isc::asiolink;
using namespace isc::util;
+using boost::scoped_ptr;
namespace {
class Option6IATest : public ::testing::Test {
@@ -61,11 +63,11 @@ TEST_F(Option6IATest, basic) {
buf_[10] = 0x02;
buf_[11] = 0x01;
- // create an option
+ // Create an option
// unpack() is called from constructor
- Option6IA* opt = new Option6IA(D6O_IA_NA,
- buf_.begin(),
- buf_.begin() + 12);
+ scoped_ptr<Option6IA> opt(new Option6IA(D6O_IA_NA,
+ buf_.begin(),
+ buf_.begin() + 12));
EXPECT_EQ(Option::V6, opt->getUniverse());
EXPECT_EQ(D6O_IA_NA, opt->getType());
@@ -73,10 +75,10 @@ TEST_F(Option6IATest, basic) {
EXPECT_EQ(0x81020304, opt->getT1());
EXPECT_EQ(0x84030201, opt->getT2());
- // pack this option again in the same buffer, but in
+ // Pack this option again in the same buffer, but in
// different place
- // test for pack()
+ // Test for pack()
opt->pack(outBuf_);
// 12 bytes header + 4 bytes content
@@ -85,31 +87,29 @@ TEST_F(Option6IATest, basic) {
EXPECT_EQ(16, outBuf_.getLength()); // lenght(IA_NA) = 16
- // check if pack worked properly:
+ // Check if pack worked properly:
InputBuffer out(outBuf_.getData(), outBuf_.getLength());
- // if option type is correct
+ // - if option type is correct
EXPECT_EQ(D6O_IA_NA, out.readUint16());
- // if option length is correct
+ // - if option length is correct
EXPECT_EQ(12, out.readUint16());
- // if iaid is correct
+ // - if iaid is correct
EXPECT_EQ(0xa1a2a3a4, out.readUint32() );
- // if T1 is correct
+ // - if T1 is correct
EXPECT_EQ(0x81020304, out.readUint32() );
- // if T1 is correct
+ // - if T1 is correct
EXPECT_EQ(0x84030201, out.readUint32() );
- EXPECT_NO_THROW(
- delete opt;
- );
+ EXPECT_NO_THROW(opt.reset());
}
TEST_F(Option6IATest, simple) {
- Option6IA* ia = new Option6IA(D6O_IA_NA, 1234);
+ scoped_ptr<Option6IA> ia(new Option6IA(D6O_IA_NA, 1234));
// Check that the values are really different than what we are about
// to set them to.
@@ -128,9 +128,7 @@ TEST_F(Option6IATest, simple) {
ia->setIAID(890);
EXPECT_EQ(890, ia->getIAID());
- EXPECT_NO_THROW(
- delete ia;
- );
+ EXPECT_NO_THROW(ia.reset());
}
@@ -140,7 +138,7 @@ TEST_F(Option6IATest, suboptions_pack) {
buf_[1] = 0xfe;
buf_[2] = 0xfc;
- Option6IA * ia = new Option6IA(D6O_IA_NA, 0x13579ace);
+ scoped_ptr<Option6IA> ia(new Option6IA(D6O_IA_NA, 0x13579ace));
ia->setT1(0x2345);
ia->setT2(0x3456);
@@ -181,9 +179,7 @@ TEST_F(Option6IATest, suboptions_pack) {
EXPECT_EQ(0, memcmp(outBuf_.getData(), expected, 48));
- EXPECT_NO_THROW(
- delete ia;
- );
+ EXPECT_NO_THROW(ia.reset());
}
@@ -213,10 +209,11 @@ TEST_F(Option6IATest, suboptions_unpack) {
memcpy(&buf_[0], expected, sizeof(expected));
- Option6IA* ia = 0;
- EXPECT_NO_THROW({
- ia = new Option6IA(D6O_IA_NA, buf_.begin() + 4, buf_.begin() + sizeof(expected));
- });
+ scoped_ptr<Option6IA> ia;
+ EXPECT_NO_THROW(
+ ia.reset(new Option6IA(D6O_IA_NA, buf_.begin() + 4,
+ buf_.begin() + sizeof(expected)));
+ );
ASSERT_TRUE(ia);
EXPECT_EQ(D6O_IA_NA, ia->getType());
@@ -227,7 +224,7 @@ TEST_F(Option6IATest, suboptions_unpack) {
OptionPtr subopt = ia->getOption(D6O_IAADDR);
ASSERT_NE(OptionPtr(), subopt); // non-NULL
- // checks for address option
+ // Checks for address option
Option6IAAddr * addr = dynamic_cast<Option6IAAddr*>(subopt.get());
ASSERT_TRUE(NULL != addr);
@@ -237,21 +234,19 @@ TEST_F(Option6IATest, suboptions_unpack) {
EXPECT_EQ(0x7000, addr->getValid());
EXPECT_EQ("2001:db8:1234:5678::abcd", addr->getAddress().toText());
- // checks for dummy option
+ // Checks for dummy option
subopt = ia->getOption(0xcafe);
ASSERT_TRUE(subopt); // should be non-NULL
EXPECT_EQ(0xcafe, subopt->getType());
EXPECT_EQ(4, subopt->len());
- // there should be no data at all
+ // There should be no data at all
EXPECT_EQ(0, subopt->getData().size());
subopt = ia->getOption(1); // get option 1
ASSERT_FALSE(subopt); // should be NULL
- EXPECT_NO_THROW(
- delete ia;
- );
+ EXPECT_NO_THROW(ia.reset());
}
}
diff --git a/src/lib/dhcp/tests/option6_iaaddr_unittest.cc b/src/lib/dhcp/tests/option6_iaaddr_unittest.cc
index 8c87d80..e28e2e0 100644
--- a/src/lib/dhcp/tests/option6_iaaddr_unittest.cc
+++ b/src/lib/dhcp/tests/option6_iaaddr_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -19,6 +19,7 @@
#include <dhcp/option6_iaaddr.h>
#include <util/buffer.h>
+#include <boost/scoped_ptr.hpp>
#include <gtest/gtest.h>
#include <iostream>
@@ -68,12 +69,12 @@ TEST_F(Option6IAAddrTest, basic) {
buf_[22] = 0x5e;
buf_[23] = 0x00; // 3,000,000,000
- // create an option (unpack content)
- Option6IAAddr* opt = new Option6IAAddr(D6O_IAADDR,
- buf_.begin(),
- buf_.begin() + 24);
+ // Create an option (unpack content)
+ boost::scoped_ptr<Option6IAAddr> opt(new Option6IAAddr(D6O_IAADDR,
+ buf_.begin(),
+ buf_.begin() + 24));
- // pack this option
+ // Pack this option
opt->pack(outBuf_);
EXPECT_EQ(28, outBuf_.getLength());
@@ -90,19 +91,19 @@ TEST_F(Option6IAAddrTest, basic) {
EXPECT_EQ(Option::OPTION6_HDR_LEN + Option6IAAddr::OPTION6_IAADDR_LEN,
opt->len());
- // check if pack worked properly:
+ // Check if pack worked properly:
const uint8_t* out = (const uint8_t*)outBuf_.getData();
- // if option type is correct
+ // - if option type is correct
EXPECT_EQ(D6O_IAADDR, out[0]*256 + out[1]);
- // if option length is correct
+ // - if option length is correct
EXPECT_EQ(24, out[2]*256 + out[3]);
- // if option content is correct
+ // - if option content is correct
EXPECT_EQ(0, memcmp(out + 4, &buf_[0], 24));
- EXPECT_NO_THROW( delete opt );
+ EXPECT_NO_THROW(opt.reset());
}
}
diff --git a/src/lib/dhcp/tests/option_custom_unittest.cc b/src/lib/dhcp/tests/option_custom_unittest.cc
index e0ae727..2a9f771 100644
--- a/src/lib/dhcp/tests/option_custom_unittest.cc
+++ b/src/lib/dhcp/tests/option_custom_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -262,7 +262,7 @@ TEST_F(OptionCustomTest, int16Data) {
// We should have just one data field.
ASSERT_EQ(1, option->getDataFieldsNum());
- // Initialize value to 0 explicitely to make sure that is
+ // Initialize value to 0 explicitly to make sure that is
// modified by readInteger function to expected -234.
int16_t value = 0;
ASSERT_NO_THROW(value = option->readInteger<int16_t>(0));
@@ -295,7 +295,7 @@ TEST_F(OptionCustomTest, int32Data) {
// We should have just one data field.
ASSERT_EQ(1, option->getDataFieldsNum());
- // Initialize value to 0 explicitely to make sure that is
+ // Initialize value to 0 explicitly to make sure that is
// modified by readInteger function to expected -234.
int32_t value = 0;
ASSERT_NO_THROW(value = option->readInteger<int32_t>(0));
@@ -766,9 +766,15 @@ TEST_F(OptionCustomTest, recordDataTruncated) {
// 2 bytes of uint16_t value and IPv6 address. Option definitions specifies
// 3 data fields for this option but the length of the data is insufficient
// to initialize 3 data field.
- EXPECT_THROW(
- option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.begin() + 18)),
- isc::OutOfRange
+
+ // @todo:
+ // Currently the code was modified to allow empty string or empty binary data
+ // Potentially change this back to EXPECT_THROW(..., OutOfRange) once we
+ // decide how to treat zero length strings and binary data (they are typically
+ // valid or invalid on a per option basis, so there likely won't be a single
+ // one answer to all)
+ EXPECT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.begin() + 18))
);
// Try to further reduce the length of the buffer to make it insufficient
diff --git a/src/lib/dhcp/tests/option_int_unittest.cc b/src/lib/dhcp/tests/option_int_unittest.cc
index 81ebcf0..96212c2 100644
--- a/src/lib/dhcp/tests/option_int_unittest.cc
+++ b/src/lib/dhcp/tests/option_int_unittest.cc
@@ -296,7 +296,7 @@ TEST_F(OptionIntTest, basicInt32V6) {
TEST_F(OptionIntTest, setValueUint8) {
boost::shared_ptr<OptionInt<uint8_t> > opt(new OptionInt<uint8_t>(Option::V6,
D6O_PREFERENCE, 123));
- // Check if constructor intitialized the option value correctly.
+ // Check if constructor initialized the option value correctly.
EXPECT_EQ(123, opt->getValue());
// Override the value.
opt->setValue(111);
@@ -310,7 +310,7 @@ TEST_F(OptionIntTest, setValueUint8) {
TEST_F(OptionIntTest, setValueInt8) {
boost::shared_ptr<OptionInt<int8_t> > opt(new OptionInt<int8_t>(Option::V6,
D6O_PREFERENCE, -123));
- // Check if constructor intitialized the option value correctly.
+ // Check if constructor initialized the option value correctly.
EXPECT_EQ(-123, opt->getValue());
// Override the value.
opt->setValue(-111);
@@ -325,7 +325,7 @@ TEST_F(OptionIntTest, setValueInt8) {
TEST_F(OptionIntTest, setValueUint16) {
boost::shared_ptr<OptionInt<uint16_t> > opt(new OptionInt<uint16_t>(Option::V6,
D6O_ELAPSED_TIME, 123));
- // Check if constructor intitialized the option value correctly.
+ // Check if constructor initialized the option value correctly.
EXPECT_EQ(123, opt->getValue());
// Override the value.
opt->setValue(0x0102);
@@ -339,7 +339,7 @@ TEST_F(OptionIntTest, setValueUint16) {
TEST_F(OptionIntTest, setValueInt16) {
boost::shared_ptr<OptionInt<int16_t> > opt(new OptionInt<int16_t>(Option::V6,
D6O_ELAPSED_TIME, -16500));
- // Check if constructor intitialized the option value correctly.
+ // Check if constructor initialized the option value correctly.
EXPECT_EQ(-16500, opt->getValue());
// Override the value.
opt->setValue(-20100);
@@ -353,7 +353,7 @@ TEST_F(OptionIntTest, setValueInt16) {
TEST_F(OptionIntTest, setValueUint32) {
boost::shared_ptr<OptionInt<uint32_t> > opt(new OptionInt<uint32_t>(Option::V6,
D6O_CLT_TIME, 123));
- // Check if constructor intitialized the option value correctly.
+ // Check if constructor initialized the option value correctly.
EXPECT_EQ(123, opt->getValue());
// Override the value.
opt->setValue(0x01020304);
@@ -367,7 +367,7 @@ TEST_F(OptionIntTest, setValueUint32) {
TEST_F(OptionIntTest, setValueInt32) {
boost::shared_ptr<OptionInt<int32_t> > opt(new OptionInt<int32_t>(Option::V6,
D6O_CLT_TIME, -120100));
- // Check if constructor intitialized the option value correctly.
+ // Check if constructor initialized the option value correctly.
EXPECT_EQ(-120100, opt->getValue());
// Override the value.
opt->setValue(-125000);
diff --git a/src/lib/dhcp/tests/option_unittest.cc b/src/lib/dhcp/tests/option_unittest.cc
index 2ce3bc6..237e73b 100644
--- a/src/lib/dhcp/tests/option_unittest.cc
+++ b/src/lib/dhcp/tests/option_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -20,6 +20,7 @@
#include <util/buffer.h>
#include <boost/shared_ptr.hpp>
+#include <boost/scoped_ptr.hpp>
#include <gtest/gtest.h>
#include <iostream>
@@ -31,6 +32,7 @@ using namespace std;
using namespace isc;
using namespace isc::dhcp;
using namespace isc::util;
+using boost::scoped_ptr;
namespace {
class OptionTest : public ::testing::Test {
@@ -44,50 +46,27 @@ public:
OutputBuffer outBuf_;
};
-// v4 is not really implemented yet. A simple test will do for now
+// Basic tests for V4 functionality
TEST_F(OptionTest, v4_basic) {
- Option* opt = 0;
- EXPECT_NO_THROW(
- opt = new Option(Option::V4, 17);
- );
+ scoped_ptr<Option> opt;
+ EXPECT_NO_THROW(opt.reset(new Option(Option::V4, 17)));
EXPECT_EQ(Option::V4, opt->getUniverse());
EXPECT_EQ(17, opt->getType());
EXPECT_EQ(0, opt->getData().size());
EXPECT_EQ(2, opt->len()); // just v4 header
- EXPECT_NO_THROW(
- delete opt;
- );
- opt = 0;
+ EXPECT_NO_THROW(opt.reset());
// V4 options have type 0...255
- EXPECT_THROW(
- opt = new Option(Option::V4, 256),
- BadValue
- );
-
- delete opt;
- opt = 0;
+ EXPECT_THROW(opt.reset(new Option(Option::V4, 256)), BadValue);
// 0 is a special PAD option
- EXPECT_THROW(
- opt = new Option(Option::V4, 0),
- BadValue
- );
-
- delete opt;
- opt = 0;
+ EXPECT_THROW(opt.reset(new Option(Option::V4, 0)), BadValue);
// 255 is a special END option
- EXPECT_THROW(
- opt = new Option(Option::V4, 255),
- BadValue
- );
-
- delete opt;
- opt = 0;
+ EXPECT_THROW(opt.reset(new Option(Option::V4, 255)), BadValue);
}
const uint8_t dummyPayload[] =
@@ -97,15 +76,12 @@ TEST_F(OptionTest, v4_data1) {
vector<uint8_t> data(dummyPayload, dummyPayload + sizeof(dummyPayload));
- Option* opt = 0;
+ scoped_ptr<Option> opt;
- // create DHCPv4 option of type 123
- // that contains 4 bytes of data
- ASSERT_NO_THROW(
- opt= new Option(Option::V4, 123, data);
- );
+ // Create DHCPv4 option of type 123 that contains 4 bytes of data.
+ ASSERT_NO_THROW(opt.reset(new Option(Option::V4, 123, data)));
- // check that content is reported properly
+ // Check that content is reported properly
EXPECT_EQ(123, opt->getType());
vector<uint8_t> optData = opt->getData();
ASSERT_EQ(optData.size(), data.size());
@@ -113,31 +89,26 @@ TEST_F(OptionTest, v4_data1) {
EXPECT_EQ(2, opt->getHeaderLen());
EXPECT_EQ(6, opt->len());
- // now store that option into a buffer
+ // Now store that option into a buffer
OutputBuffer buf(100);
- EXPECT_NO_THROW(
- opt->pack(buf);
- );
-
- // check content of that buffer
+ EXPECT_NO_THROW(opt->pack(buf));
+ // Check content of that buffer:
// 2 byte header + 4 bytes data
ASSERT_EQ(6, buf.getLength());
- // that's how this option is supposed to look like
+ // That's how this option is supposed to look like
uint8_t exp[] = { 123, 4, 1, 2, 3, 4 };
/// TODO: use vector<uint8_t> getData() when it will be implemented
EXPECT_EQ(0, memcmp(exp, buf.getData(), 6));
- // check that we can destroy that option
- EXPECT_NO_THROW(
- delete opt;
- );
+ // Check that we can destroy that option
+ EXPECT_NO_THROW(opt.reset());
}
-// this is almost the same test as v4_data1, but it uses
-// different constructor
+// This is almost the same test as v4_data1, but it uses a different
+// constructor
TEST_F(OptionTest, v4_data2) {
vector<uint8_t> data(dummyPayload, dummyPayload + sizeof(dummyPayload));
@@ -153,16 +124,16 @@ TEST_F(OptionTest, v4_data2) {
// ignored, as we pass interators to proper data. Only subset (limited by
// iterators) of the vector should be used.
// expData contains expected content (just valid data, without garbage).
-
- Option* opt = 0;
+ scoped_ptr<Option> opt;
// Create DHCPv4 option of type 123 that contains
// 4 bytes (sizeof(dummyPayload).
ASSERT_NO_THROW(
- opt= new Option(Option::V4, 123, data.begin() + 1, data.end() - 1);
+ opt.reset(new Option(Option::V4, 123, data.begin() + 1,
+ data.end() - 1));
);
- // check that content is reported properly
+ // Check that content is reported properly
EXPECT_EQ(123, opt->getType());
vector<uint8_t> optData = opt->getData();
ASSERT_EQ(optData.size(), expData.size());
@@ -170,27 +141,23 @@ TEST_F(OptionTest, v4_data2) {
EXPECT_EQ(2, opt->getHeaderLen());
EXPECT_EQ(6, opt->len());
- // now store that option into a buffer
+ // Now store that option into a buffer
OutputBuffer buf(100);
- EXPECT_NO_THROW(
- opt->pack(buf);
- );
+ EXPECT_NO_THROW(opt->pack(buf));
- // check content of that buffer
+ // Check content of that buffer
// 2 byte header + 4 bytes data
ASSERT_EQ(6, buf.getLength());
- // that's how this option is supposed to look like
+ // That's how this option is supposed to look like
uint8_t exp[] = { 123, 4, 1, 2, 3, 4 };
/// TODO: use vector<uint8_t> getData() when it will be implemented
EXPECT_EQ(0, memcmp(exp, buf.getData(), 6));
- // check that we can destroy that option
- EXPECT_NO_THROW(
- delete opt;
- );
+ // Check that we can destroy that option
+ EXPECT_NO_THROW(opt.reset());
}
TEST_F(OptionTest, v4_toText) {
@@ -205,29 +172,29 @@ TEST_F(OptionTest, v4_toText) {
EXPECT_EQ("type=253, len=3: 00:0f:ff", opt.toText());
}
-// tests simple constructor
+// Tests simple constructor
TEST_F(OptionTest, v6_basic) {
- Option* opt = new Option(Option::V6, 1);
+ scoped_ptr<Option> opt(new Option(Option::V6, 1));
EXPECT_EQ(Option::V6, opt->getUniverse());
EXPECT_EQ(1, opt->getType());
EXPECT_EQ(0, opt->getData().size());
- EXPECT_EQ(4, opt->len()); // just v6 header
+ EXPECT_EQ(4, opt->len()); // Just v6 header
- EXPECT_NO_THROW(
- delete opt;
- );
+ EXPECT_NO_THROW(opt.reset());
}
-// tests constructor used in pkt reception
-// option contains actual data
+// Tests constructor used in packet reception. Option contains actual data
TEST_F(OptionTest, v6_data1) {
- for (int i = 0; i < 32; i++)
- buf_[i] = 100+i;
- Option* opt = new Option(Option::V6, 333, //type
- buf_.begin() + 3, // begin offset
- buf_.begin() + 10); // end offset (7 bytes of data)
+ for (int i = 0; i < 32; i++) {
+ buf_[i] = 100 + i;
+ }
+
+ // Create option with seven bytes of data.
+ scoped_ptr<Option> opt(new Option(Option::V6, 333, // Type
+ buf_.begin() + 3, // Begin offset
+ buf_.begin() + 10)); // End offset
EXPECT_EQ(333, opt->getType());
ASSERT_EQ(11, opt->len());
@@ -237,23 +204,20 @@ TEST_F(OptionTest, v6_data1) {
opt->pack(outBuf_);
EXPECT_EQ(11, outBuf_.getLength());
- const uint8_t* out = (const uint8_t*)outBuf_.getData();
- EXPECT_EQ(out[0], 333/256); // type
- EXPECT_EQ(out[1], 333%256);
+ const uint8_t* out = static_cast<const uint8_t*>(outBuf_.getData());
+ EXPECT_EQ(out[0], 333 / 256); // Type
+ EXPECT_EQ(out[1], 333 % 256);
- EXPECT_EQ(out[2], 0); // len
+ EXPECT_EQ(out[2], 0); // Length
EXPECT_EQ(out[3], 7);
- // payload
- EXPECT_EQ(0, memcmp(&buf_[3], out+4, 7) );
+ // Payload
+ EXPECT_EQ(0, memcmp(&buf_[3], out + 4, 7));
- EXPECT_NO_THROW(
- delete opt;
- );
+ EXPECT_NO_THROW(opt.reset());
}
-// another text that tests the same thing, just
-// with different input parameters
+// Another test that tests the same thing, just with different input parameters.
TEST_F(OptionTest, v6_data2) {
buf_[0] = 0xa1;
@@ -261,13 +225,11 @@ TEST_F(OptionTest, v6_data2) {
buf_[2] = 0xa3;
buf_[3] = 0xa4;
- // create an option (unpack content)
- Option* opt = new Option(Option::V6,
- D6O_CLIENTID,
- buf_.begin(),
- buf_.begin() + 4);
+ // Create an option (unpack content)
+ scoped_ptr<Option> opt(new Option(Option::V6, D6O_CLIENTID,
+ buf_.begin(), buf_.begin() + 4));
- // pack this option
+ // Pack this option
opt->pack(outBuf_);
// 4 bytes header + 4 bytes content
@@ -276,35 +238,35 @@ TEST_F(OptionTest, v6_data2) {
EXPECT_EQ(8, outBuf_.getLength());
- // check if pack worked properly:
- // if option type is correct
- const uint8_t* out = (const uint8_t*)outBuf_.getData();
+ // Check if pack worked properly:
+ // If option type is correct
+ const uint8_t* out = static_cast<const uint8_t*>(outBuf_.getData());
- EXPECT_EQ(D6O_CLIENTID, out[0]*256 + out[1]);
+ EXPECT_EQ(D6O_CLIENTID, out[0] * 256 + out[1]);
- // if option length is correct
- EXPECT_EQ(4, out[2]*256 + out[3]);
+ // If option length is correct
+ EXPECT_EQ(4, out[2] * 256 + out[3]);
- // if option content is correct
+ // If option content is correct
EXPECT_EQ(0, memcmp(&buf_[0], out + 4, 4));
- EXPECT_NO_THROW(
- delete opt;
- );
+ EXPECT_NO_THROW(opt.reset());
}
-// check that an option can contain 2 suboptions:
+// Check that an option can contain 2 suboptions:
// opt1
// +----opt2
// |
// +----opt3
//
TEST_F(OptionTest, v6_suboptions1) {
- for (int i=0; i<128; i++)
- buf_[i] = 100+i;
- Option* opt1 = new Option(Option::V6, 65535, //type
- buf_.begin(), // 3 bytes of data
- buf_.begin() + 3);
+ for (int i = 0; i < 128; i++) {
+ buf_[i] = 100 + i;
+ }
+
+ scoped_ptr<Option> opt1(new Option(Option::V6, 65535, // Type
+ buf_.begin(), // 3 bytes of data
+ buf_.begin() + 3));
OptionPtr opt2(new Option(Option::V6, 13));
OptionPtr opt3(new Option(Option::V6, 7,
buf_.begin() + 3,
@@ -328,30 +290,29 @@ TEST_F(OptionTest, v6_suboptions1) {
opt1->pack(outBuf_);
EXPECT_EQ(20, outBuf_.getLength());
- // payload
+ // Payload
EXPECT_EQ(0, memcmp(outBuf_.getData(), expected, 20) );
- EXPECT_NO_THROW(
- delete opt1;
- );
+ EXPECT_NO_THROW(opt1.reset());
}
-// check that an option can contain nested suboptions:
+// Check that an option can contain nested suboptions:
// opt1
// +----opt2
// |
// +----opt3
//
TEST_F(OptionTest, v6_suboptions2) {
- for (int i=0; i<128; i++)
- buf_[i] = 100+i;
- Option* opt1 = new Option(Option::V6, 65535, //type
- buf_.begin(),
- buf_.begin() + 3);
+ for (int i = 0; i < 128; i++) {
+ buf_[i] = 100 + i;
+ }
+
+ scoped_ptr<Option> opt1(new Option(Option::V6, 65535, // Type
+ buf_.begin(), buf_.begin() + 3));
OptionPtr opt2(new Option(Option::V6, 13));
OptionPtr opt3(new Option(Option::V6, 7,
- buf_.begin() + 3,
- buf_.begin() + 8));
+ buf_.begin() + 3,
+ buf_.begin() + 8));
opt1->addOption(opt2);
opt2->addOption(opt3);
// opt3 len = 9 4(header)+5(data)
@@ -367,18 +328,18 @@ TEST_F(OptionTest, v6_suboptions2) {
opt1->pack(outBuf_);
EXPECT_EQ(20, outBuf_.getLength());
- // payload
+ // Payload
EXPECT_EQ(0, memcmp(outBuf_.getData(), expected, 20) );
- EXPECT_NO_THROW(
- delete opt1;
- );
+ EXPECT_NO_THROW(opt1.reset());
}
TEST_F(OptionTest, v6_addgetdel) {
- for (int i=0; i<128; i++)
- buf_[i] = 100+i;
- Option* parent = new Option(Option::V6, 65535); //type
+ for (int i = 0; i < 128; i++) {
+ buf_[i] = 100 + i;
+ }
+
+ scoped_ptr<Option> parent(new Option(Option::V6, 65535)); // Type
OptionPtr opt1(new Option(Option::V6, 1));
OptionPtr opt2(new Option(Option::V6, 2));
OptionPtr opt3(new Option(Option::V6, 2));
@@ -390,28 +351,26 @@ TEST_F(OptionTest, v6_addgetdel) {
EXPECT_EQ(opt1, parent->getOption(1));
EXPECT_EQ(opt2, parent->getOption(2));
- // expect NULL
+ // Expect NULL
EXPECT_EQ(OptionPtr(), parent->getOption(4));
- // now there are 2 options of type 2
+ // Now there are 2 options of type 2
parent->addOption(opt3);
- // let's delete one of them
+ // Let's delete one of them
EXPECT_EQ(true, parent->delOption(2));
- // there still should be the other option 2
+ // There still should be the other option 2
EXPECT_NE(OptionPtr(), parent->getOption(2));
- // let's delete the other option 2
+ // Let's delete the other option 2
EXPECT_EQ(true, parent->delOption(2));
- // no more options with type=2
+ // No more options with type=2
EXPECT_EQ(OptionPtr(), parent->getOption(2));
- // let's try to delete - should fail
+ // Let's try to delete - should fail
EXPECT_TRUE(false == parent->delOption(2));
-
- delete parent;
}
TEST_F(OptionTest, v6_toText) {
@@ -419,8 +378,7 @@ TEST_F(OptionTest, v6_toText) {
buf_[1] = 0xf;
buf_[2] = 0xff;
- OptionPtr opt(new Option(Option::V6, 258, buf_.begin(), buf_.begin() + 3 ));
-
+ OptionPtr opt(new Option(Option::V6, 258, buf_.begin(), buf_.begin() + 3 ));
EXPECT_EQ("type=258, len=3: 00:0f:ff", opt->toText());
}
@@ -433,7 +391,7 @@ TEST_F(OptionTest, getUintX) {
buf_[3] = 0x2;
buf_[4] = 0x1;
- // five options with varying lengths
+ // Five options with varying lengths
OptionPtr opt1(new Option(Option::V6, 258, buf_.begin(), buf_.begin() + 1));
OptionPtr opt2(new Option(Option::V6, 258, buf_.begin(), buf_.begin() + 2));
OptionPtr opt3(new Option(Option::V6, 258, buf_.begin(), buf_.begin() + 3));
@@ -456,7 +414,7 @@ TEST_F(OptionTest, getUintX) {
EXPECT_EQ(0x0504, opt4->getUint16());
EXPECT_EQ(0x05040302, opt4->getUint32());
- // the same as for 4-byte long, just get first 1,2 or 4 bytes
+ // The same as for 4-byte long, just get first 1,2 or 4 bytes
EXPECT_EQ(5, opt5->getUint8());
EXPECT_EQ(0x0504, opt5->getUint16());
EXPECT_EQ(0x05040302, opt5->getUint32());
@@ -468,7 +426,7 @@ TEST_F(OptionTest, setUintX) {
OptionPtr opt2(new Option(Option::V4, 125));
OptionPtr opt4(new Option(Option::V4, 125));
- // verify setUint8
+ // Verify setUint8
opt1->setUint8(255);
EXPECT_EQ(255, opt1->getUint8());
opt1->pack(outBuf_);
@@ -477,7 +435,7 @@ TEST_F(OptionTest, setUintX) {
uint8_t exp1[] = {125, 1, 255};
EXPECT_TRUE(0 == memcmp(exp1, outBuf_.getData(), 3));
- // verify getUint16
+ // Verify getUint16
outBuf_.clear();
opt2->setUint16(12345);
opt2->pack(outBuf_);
@@ -487,7 +445,7 @@ TEST_F(OptionTest, setUintX) {
uint8_t exp2[] = {125, 2, 12345/256, 12345%256};
EXPECT_TRUE(0 == memcmp(exp2, outBuf_.getData(), 4));
- // verify getUint32
+ // Verify getUint32
outBuf_.clear();
opt4->setUint32(0x12345678);
opt4->pack(outBuf_);
@@ -499,8 +457,8 @@ TEST_F(OptionTest, setUintX) {
}
TEST_F(OptionTest, setData) {
- // verify data override with new buffer larger than
- // initial option buffer size
+ // Verify data override with new buffer larger than initial option buffer
+ // size.
OptionPtr opt1(new Option(Option::V4, 125,
buf_.begin(), buf_.begin() + 10));
buf_.resize(20, 1);
@@ -511,8 +469,8 @@ TEST_F(OptionTest, setData) {
EXPECT_TRUE(0 == memcmp(&buf_[0], test_data + opt1->getHeaderLen(),
buf_.size()));
- // verify data override with new buffer shorter than
- // initial option buffer size
+ // Verify data override with new buffer shorter than initial option buffer
+ // size.
OptionPtr opt2(new Option(Option::V4, 125,
buf_.begin(), buf_.begin() + 10));
outBuf_.clear();
@@ -528,15 +486,15 @@ TEST_F(OptionTest, setData) {
// This test verifies that options can be compared using equal() method.
TEST_F(OptionTest, equal) {
- // five options with varying lengths
+ // Five options with varying lengths
OptionPtr opt1(new Option(Option::V6, 258, buf_.begin(), buf_.begin() + 1));
OptionPtr opt2(new Option(Option::V6, 258, buf_.begin(), buf_.begin() + 2));
OptionPtr opt3(new Option(Option::V6, 258, buf_.begin(), buf_.begin() + 3));
- // the same content as opt2, but different type
+ // The same content as opt2, but different type
OptionPtr opt4(new Option(Option::V6, 1, buf_.begin(), buf_.begin() + 2));
- // another instance with the same type and content as opt2
+ // Another instance with the same type and content as opt2
OptionPtr opt5(new Option(Option::V6, 258, buf_.begin(), buf_.begin() + 2));
EXPECT_TRUE(opt1->equal(opt1));
diff --git a/src/lib/dhcp/tests/pkt4_unittest.cc b/src/lib/dhcp/tests/pkt4_unittest.cc
index 49588e1..5c95f7d 100644
--- a/src/lib/dhcp/tests/pkt4_unittest.cc
+++ b/src/lib/dhcp/tests/pkt4_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -35,7 +35,7 @@ using namespace isc;
using namespace isc::asiolink;
using namespace isc::dhcp;
using namespace isc::util;
-// don't import the entire boost namespace. It will unexpectedly hide uint8_t
+// Don't import the entire boost namespace. It will unexpectedly hide uint8_t
// for some systems.
using boost::scoped_ptr;
@@ -44,55 +44,39 @@ namespace {
TEST(Pkt4Test, constructor) {
ASSERT_EQ(236U, static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN) );
- Pkt4* pkt = 0;
+ scoped_ptr<Pkt4> pkt;
// Just some dummy payload.
uint8_t testData[250];
for (int i = 0; i < 250; i++) {
- testData[i]=i;
+ testData[i] = i;
}
// Positive case1. Normal received packet.
- EXPECT_NO_THROW(
- pkt = new Pkt4(testData, Pkt4::DHCPV4_PKT_HDR_LEN);
- );
+ EXPECT_NO_THROW(pkt.reset(new Pkt4(testData, Pkt4::DHCPV4_PKT_HDR_LEN)));
EXPECT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN), pkt->len());
- EXPECT_NO_THROW(
- delete pkt;
- pkt = 0;
- );
+ EXPECT_NO_THROW(pkt.reset());
// Positive case2. Normal outgoing packet.
- EXPECT_NO_THROW(
- pkt = new Pkt4(DHCPDISCOVER, 0xffffffff);
- );
+ EXPECT_NO_THROW(pkt.reset(new Pkt4(DHCPDISCOVER, 0xffffffff)));
// DHCPv4 packet must be at least 236 bytes long, with Message Type
// Option taking extra 3 bytes it is 239
EXPECT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN) + 3, pkt->len());
EXPECT_EQ(DHCPDISCOVER, pkt->getType());
EXPECT_EQ(0xffffffff, pkt->getTransid());
- EXPECT_NO_THROW(
- delete pkt;
- pkt = 0;
- );
+ EXPECT_NO_THROW(pkt.reset());
// Negative case. Should drop truncated messages.
EXPECT_THROW(
- pkt = new Pkt4(testData, Pkt4::DHCPV4_PKT_HDR_LEN-1),
+ pkt.reset(new Pkt4(testData, Pkt4::DHCPV4_PKT_HDR_LEN - 1)),
OutOfRange
);
- if (pkt) {
- // Test failed. Exception should have been thrown, but
- // object was created instead. Let's clean this up.
- delete pkt;
- pkt = 0;
- }
}
-// a sample data
+// Sample data
const uint8_t dummyOp = BOOTREQUEST;
const uint8_t dummyHtype = 6;
const uint8_t dummyHlen = 6;
@@ -109,16 +93,16 @@ const IOAddress dummyGiaddr("255.255.255.255");
// a dummy MAC address
const uint8_t dummyMacAddr[] = {0, 1, 2, 3, 4, 5};
-// a dummy MAC address, padded with 0s
+// A dummy MAC address, padded with 0s
const uint8_t dummyChaddr[16] = {0, 1, 2, 3, 4, 5, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0 };
-// let's use some creative test content here (128 chars + \0)
+// Let's use some creative test content here (128 chars + \0)
const uint8_t dummyFile[] = "Lorem ipsum dolor sit amet, consectetur "
"adipiscing elit. Proin mollis placerat metus, at "
"lacinia orci ornare vitae. Mauris amet.";
-// yet another type of test content (64 chars + \0)
+// Yet another type of test content (64 chars + \0)
const uint8_t dummySname[] = "Lorem ipsum dolor sit amet, consectetur "
"adipiscing elit posuere.";
@@ -127,12 +111,11 @@ BOOST_STATIC_ASSERT(sizeof(dummySname) == Pkt4::MAX_SNAME_LEN + 1);
/// @brief Generates test packet.
///
-/// Allocates and generates test packet, with all fixed
-/// fields set to non-zero values. Content is not always
-/// reasonable.
+/// Allocates and generates test packet, with all fixed fields set to non-zero
+/// values. Content is not always reasonable.
///
-/// See generateTestPacket2() function that returns
-/// exactly the same packet in on-wire format.
+/// See generateTestPacket2() function that returns exactly the same packet in
+/// on-wire format.
///
/// @return pointer to allocated Pkt4 object.
boost::shared_ptr<Pkt4>
@@ -162,12 +145,11 @@ generateTestPacket1() {
/// @brief Generates test packet.
///
-/// Allocates and generates on-wire buffer that represents
-/// test packet, with all fixed fields set to non-zero values.
-/// Content is not always reasonable.
+/// Allocates and generates on-wire buffer that represents test packet, with all
+/// fixed fields set to non-zero values. Content is not always reasonable.
///
-/// See generateTestPacket1() function that returns
-/// exactly the same packet as Pkt4 object.
+/// See generateTestPacket1() function that returns exactly the same packet as
+/// Pkt4 object.
///
/// @return pointer to allocated Pkt4 object
// Returns a vector containing a DHCPv4 packet header.
@@ -206,7 +188,7 @@ TEST(Pkt4Test, fixedFields) {
boost::shared_ptr<Pkt4> pkt = generateTestPacket1();
- // ok, let's check packet values
+ // OK, let's check packet values
EXPECT_EQ(dummyOp, pkt->getOp());
EXPECT_EQ(dummyHtype, pkt->getHtype());
EXPECT_EQ(dummyHlen, pkt->getHlen());
@@ -244,7 +226,7 @@ TEST(Pkt4Test, fixedFieldsPack) {
// DHCP Message Type Option
ASSERT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN) + 3, pkt->len());
- // redundant but MUCH easier for debug in gdb
+ // Redundant but MUCH easier for debug in gdb
const uint8_t* exp = &expectedFormat[0];
const uint8_t* got = static_cast<const uint8_t*>(pkt->getBuffer().getData());
@@ -272,7 +254,7 @@ TEST(Pkt4Test, fixedFieldsUnpack) {
pkt->unpack()
);
- // ok, let's check packet values
+ // OK, let's check packet values
EXPECT_EQ(dummyOp, pkt->getOp());
EXPECT_EQ(dummyHtype, pkt->getHtype());
EXPECT_EQ(dummyHlen, pkt->getHlen());
@@ -298,7 +280,7 @@ TEST(Pkt4Test, fixedFieldsUnpack) {
EXPECT_EQ(DHCPDISCOVER, pkt->getType());
}
-// this test is for hardware addresses (htype, hlen and chaddr fields)
+// This test is for hardware addresses (htype, hlen and chaddr fields)
TEST(Pkt4Test, hwAddr) {
vector<uint8_t> mac;
@@ -309,7 +291,7 @@ TEST(Pkt4Test, hwAddr) {
// (growing length back to MAX_CHADDR_LEN).
mac.resize(Pkt4::MAX_CHADDR_LEN);
- Pkt4* pkt = 0;
+ scoped_ptr<Pkt4> pkt;
// let's test each hlen, from 0 till 16
for (int macLen = 0; macLen < Pkt4::MAX_CHADDR_LEN; macLen++) {
for (int i = 0; i < Pkt4::MAX_CHADDR_LEN; i++) {
@@ -322,8 +304,8 @@ TEST(Pkt4Test, hwAddr) {
}
// type and transaction doesn't matter in this test
- pkt = new Pkt4(DHCPOFFER, 1234);
- pkt->setHWAddr(255-macLen*10, // just weird htype
+ pkt.reset(new Pkt4(DHCPOFFER, 1234));
+ pkt->setHWAddr(255 - macLen * 10, // just weird htype
macLen,
mac);
EXPECT_EQ(0, memcmp(expectedChaddr, &pkt->getHWAddr()->hwaddr_[0],
@@ -335,11 +317,11 @@ TEST(Pkt4Test, hwAddr) {
// CHADDR starts at offset 28 in DHCP packet
const uint8_t* ptr =
- static_cast<const uint8_t*>(pkt->getBuffer().getData())+28;
+ static_cast<const uint8_t*>(pkt->getBuffer().getData()) + 28;
EXPECT_EQ(0, memcmp(ptr, expectedChaddr, Pkt4::MAX_CHADDR_LEN));
- delete pkt;
+ pkt.reset();
}
/// TODO: extend this test once options support is implemented. HW address
@@ -368,35 +350,28 @@ TEST(Pkt4Test, msgTypes) {
{DHCPLEASEACTIVE, BOOTREPLY}
};
- Pkt4* pkt = 0;
+ scoped_ptr<Pkt4> pkt;
for (int i = 0; i < sizeof(types) / sizeof(msgType); i++) {
-
- pkt = new Pkt4(types[i].dhcp, 0);
+ pkt.reset(new Pkt4(types[i].dhcp, 0));
EXPECT_EQ(types[i].dhcp, pkt->getType());
-
EXPECT_EQ(types[i].bootp, pkt->getOp());
-
- delete pkt;
- pkt = 0;
+ pkt.reset();
}
EXPECT_THROW(
- pkt = new Pkt4(100, 0), // there's no message type 100
+ pkt.reset(new Pkt4(100, 0)), // There's no message type 100
OutOfRange
);
- if (pkt) {
- delete pkt;
- }
}
-// this test verifies handling of sname field
+// This test verifies handling of sname field
TEST(Pkt4Test, sname) {
uint8_t sname[Pkt4::MAX_SNAME_LEN];
- Pkt4* pkt = 0;
- // let's test each sname length, from 0 till 64
- for (int snameLen=0; snameLen < Pkt4::MAX_SNAME_LEN; snameLen++) {
+ scoped_ptr<Pkt4> pkt;
+ // Let's test each sname length, from 0 till 64
+ for (int snameLen = 0; snameLen < Pkt4::MAX_SNAME_LEN; snameLen++) {
for (int i = 0; i < Pkt4::MAX_SNAME_LEN; i++) {
sname[i] = 0;
}
@@ -404,8 +379,8 @@ TEST(Pkt4Test, sname) {
sname[i] = i;
}
- // type and transaction doesn't matter in this test
- pkt = new Pkt4(DHCPOFFER, 1234);
+ // Type and transaction doesn't matter in this test
+ pkt.reset(new Pkt4(DHCPOFFER, 1234));
pkt->setSname(sname, snameLen);
EXPECT_EQ(0, memcmp(sname, &pkt->getSname()[0], Pkt4::MAX_SNAME_LEN));
@@ -416,10 +391,10 @@ TEST(Pkt4Test, sname) {
// SNAME starts at offset 44 in DHCP packet
const uint8_t* ptr =
- static_cast<const uint8_t*>(pkt->getBuffer().getData())+44;
+ static_cast<const uint8_t*>(pkt->getBuffer().getData()) + 44;
EXPECT_EQ(0, memcmp(ptr, sname, Pkt4::MAX_SNAME_LEN));
- delete pkt;
+ pkt.reset();
}
// Check that a null argument generates an exception.
@@ -432,7 +407,7 @@ TEST(Pkt4Test, file) {
uint8_t file[Pkt4::MAX_FILE_LEN];
- Pkt4* pkt = 0;
+ scoped_ptr<Pkt4> pkt;
// Let's test each file length, from 0 till 128.
for (int fileLen = 0; fileLen < Pkt4::MAX_FILE_LEN; fileLen++) {
for (int i = 0; i < Pkt4::MAX_FILE_LEN; i++) {
@@ -443,22 +418,21 @@ TEST(Pkt4Test, file) {
}
// Type and transaction doesn't matter in this test.
- pkt = new Pkt4(DHCPOFFER, 1234);
+ pkt.reset(new Pkt4(DHCPOFFER, 1234));
pkt->setFile(file, fileLen);
EXPECT_EQ(0, memcmp(file, &pkt->getFile()[0], Pkt4::MAX_FILE_LEN));
- //
EXPECT_NO_THROW(
pkt->pack();
);
// FILE starts at offset 108 in DHCP packet.
const uint8_t* ptr =
- static_cast<const uint8_t*>(pkt->getBuffer().getData())+108;
+ static_cast<const uint8_t*>(pkt->getBuffer().getData()) + 108;
EXPECT_EQ(0, memcmp(ptr, file, Pkt4::MAX_FILE_LEN));
- delete pkt;
+ pkt.reset();
}
// Check that a null argument generates an exception.
@@ -481,13 +455,13 @@ static uint8_t v4Opts[] = {
};
TEST(Pkt4Test, options) {
- Pkt4* pkt = new Pkt4(DHCPOFFER, 0);
+ scoped_ptr<Pkt4> pkt(new Pkt4(DHCPOFFER, 0));
vector<uint8_t> payload[5];
for (int i = 0; i < 5; i++) {
- payload[i].push_back(i*10);
- payload[i].push_back(i*10+1);
- payload[i].push_back(i*10+2);
+ payload[i].push_back(i * 10);
+ payload[i].push_back(i * 10 + 1);
+ payload[i].push_back(i * 10 + 2);
}
boost::shared_ptr<Option> opt1(new Option(Option::V4, 12, payload[0]));
@@ -509,7 +483,7 @@ TEST(Pkt4Test, options) {
EXPECT_TRUE(pkt->getOption(254));
EXPECT_FALSE(pkt->getOption(127)); // no such option
- // options are unique in DHCPv4. It should not be possible
+ // Options are unique in DHCPv4. It should not be possible
// to add more than one option of the same type.
EXPECT_THROW(
pkt->addOption(opt1),
@@ -521,26 +495,28 @@ TEST(Pkt4Test, options) {
);
const OutputBuffer& buf = pkt->getBuffer();
- // check that all options are stored, they should take sizeof(v4Opts),
+ // Check that all options are stored, they should take sizeof(v4Opts),
// DHCP magic cookie (4 bytes), and OPTION_END added (just one byte)
- ASSERT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN) + sizeof(DHCP_OPTIONS_COOKIE)
- + sizeof(v4Opts) + 1, buf.getLength());
+ ASSERT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN) +
+ sizeof(DHCP_OPTIONS_COOKIE) + sizeof(v4Opts) + 1,
+ buf.getLength());
- // that that this extra data actually contain our options
+ // That that this extra data actually contain our options
const uint8_t* ptr = static_cast<const uint8_t*>(buf.getData());
- ptr += Pkt4::DHCPV4_PKT_HDR_LEN + sizeof(DHCP_OPTIONS_COOKIE); // rewind to end of fixed part
+
+ // Rewind to end of fixed part.
+ ptr += Pkt4::DHCPV4_PKT_HDR_LEN + sizeof(DHCP_OPTIONS_COOKIE);
+
EXPECT_EQ(0, memcmp(ptr, v4Opts, sizeof(v4Opts)));
EXPECT_EQ(DHO_END, static_cast<uint8_t>(*(ptr + sizeof(v4Opts))));
// delOption() checks
- EXPECT_TRUE(pkt->getOption(12)); // Sanity check: option 12 is still there
- EXPECT_TRUE(pkt->delOption(12)); // We should be able to remove it
+ EXPECT_TRUE(pkt->getOption(12)); // Sanity check: option 12 is still there
+ EXPECT_TRUE(pkt->delOption(12)); // We should be able to remove it
EXPECT_FALSE(pkt->getOption(12)); // It should not be there anymore
EXPECT_FALSE(pkt->delOption(12)); // And removal should fail
- EXPECT_NO_THROW(
- delete pkt;
- );
+ EXPECT_NO_THROW(pkt.reset());
}
TEST(Pkt4Test, unpackOptions) {
@@ -576,42 +552,42 @@ TEST(Pkt4Test, unpackOptions) {
EXPECT_EQ(12, x->getType()); // this should be option 12
ASSERT_EQ(3, x->getData().size()); // it should be of length 3
EXPECT_EQ(5, x->len()); // total option length 5
- EXPECT_EQ(0, memcmp(&x->getData()[0], v4Opts+2, 3)); // data len=3
+ EXPECT_EQ(0, memcmp(&x->getData()[0], v4Opts + 2, 3)); // data len=3
x = pkt->getOption(14);
ASSERT_TRUE(x); // option 13 should exist
EXPECT_EQ(14, x->getType()); // this should be option 13
ASSERT_EQ(3, x->getData().size()); // it should be of length 3
EXPECT_EQ(5, x->len()); // total option length 5
- EXPECT_EQ(0, memcmp(&x->getData()[0], v4Opts+7, 3)); // data len=3
+ EXPECT_EQ(0, memcmp(&x->getData()[0], v4Opts + 7, 3)); // data len=3
x = pkt->getOption(60);
ASSERT_TRUE(x); // option 60 should exist
EXPECT_EQ(60, x->getType()); // this should be option 60
ASSERT_EQ(3, x->getData().size()); // it should be of length 3
EXPECT_EQ(5, x->len()); // total option length 5
- EXPECT_EQ(0, memcmp(&x->getData()[0], v4Opts+15, 3)); // data len=3
+ EXPECT_EQ(0, memcmp(&x->getData()[0], v4Opts + 15, 3)); // data len=3
x = pkt->getOption(128);
ASSERT_TRUE(x); // option 3 should exist
EXPECT_EQ(128, x->getType()); // this should be option 254
ASSERT_EQ(3, x->getData().size()); // it should be of length 3
EXPECT_EQ(5, x->len()); // total option length 5
- EXPECT_EQ(0, memcmp(&x->getData()[0], v4Opts+20, 3)); // data len=3
+ EXPECT_EQ(0, memcmp(&x->getData()[0], v4Opts + 20, 3)); // data len=3
x = pkt->getOption(254);
ASSERT_TRUE(x); // option 3 should exist
EXPECT_EQ(254, x->getType()); // this should be option 254
ASSERT_EQ(3, x->getData().size()); // it should be of length 3
EXPECT_EQ(5, x->len()); // total option length 5
- EXPECT_EQ(0, memcmp(&x->getData()[0], v4Opts+25, 3)); // data len=3
+ EXPECT_EQ(0, memcmp(&x->getData()[0], v4Opts + 25, 3)); // data len=3
}
// This test verifies methods that are used for manipulating meta fields
// i.e. fields that are not part of DHCPv4 (e.g. interface name).
TEST(Pkt4Test, metaFields) {
- Pkt4* pkt = new Pkt4(DHCPOFFER, 1234);
+ scoped_ptr<Pkt4> pkt(new Pkt4(DHCPOFFER, 1234));
pkt->setIface("loooopback");
pkt->setIndex(42);
pkt->setRemoteAddr(IOAddress("1.2.3.4"));
@@ -621,8 +597,6 @@ TEST(Pkt4Test, metaFields) {
EXPECT_EQ(42, pkt->getIndex());
EXPECT_EQ("1.2.3.4", pkt->getRemoteAddr().toText());
EXPECT_EQ("4.3.2.1", pkt->getLocalAddr().toText());
-
- delete pkt;
}
TEST(Pkt4Test, Timestamp) {
diff --git a/src/lib/dhcp/tests/pkt6_unittest.cc b/src/lib/dhcp/tests/pkt6_unittest.cc
index cdaad3b..d4907d6 100644
--- a/src/lib/dhcp/tests/pkt6_unittest.cc
+++ b/src/lib/dhcp/tests/pkt6_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -17,9 +17,16 @@
#include <asiolink/io_address.h>
#include <dhcp/dhcp6.h>
#include <dhcp/option.h>
+#include <dhcp/option_custom.h>
+#include <dhcp/option6_ia.h>
+#include <dhcp/option_int.h>
+#include <dhcp/option_int_array.h>
#include <dhcp/pkt6.h>
+#include <util/range_utilities.h>
#include <boost/date_time/posix_time/posix_time.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <util/encode/hex.h>
#include <gtest/gtest.h>
#include <iostream>
@@ -31,6 +38,7 @@ using namespace std;
using namespace isc;
using namespace isc::asiolink;
using namespace isc::dhcp;
+using boost::scoped_ptr;
namespace {
// empty class for now, but may be extended once Addr6 becomes bigger
@@ -38,16 +46,26 @@ class Pkt6Test : public ::testing::Test {
public:
Pkt6Test() {
}
+
+ /// @brief generates an option with given code (and length) and random content
+ ///
+ /// @param code option code
+ /// @param len data length (data will be randomized)
+ ///
+ /// @return pointer to the new option
+ OptionPtr generateRandomOption(uint16_t code, size_t len = 10) {
+ OptionBuffer data(len);
+ util::fillRandom(data.begin(), data.end());
+ return OptionPtr(new Option(Option::V6, code, data));
+ }
};
TEST_F(Pkt6Test, constructor) {
uint8_t data[] = { 0, 1, 2, 3, 4, 5 };
- Pkt6 * pkt1 = new Pkt6(data, sizeof(data) );
+ scoped_ptr<Pkt6> pkt1(new Pkt6(data, sizeof(data)));
EXPECT_EQ(6, pkt1->getData().size());
EXPECT_EQ(0, memcmp( &pkt1->getData()[0], data, sizeof(data)));
-
- delete pkt1;
}
/// @brief returns captured actual SOLICIT packet
@@ -99,41 +117,99 @@ Pkt6* capture1() {
return (pkt);
}
+/// @brief creates doubly relayed solicit message
+///
+/// This is a traffic capture exported from wireshark. It includes a SOLICIT
+/// message that passed through two relays. Each relay include interface-id,
+/// remote-id and relay-forw encapsulation. It is especially interesting,
+/// because of the following properties:
+/// - double encapsulation
+/// - first relay inserts relay-msg before extra options
+/// - second relay inserts relay-msg after extra options
+/// - both relays are from different vendors
+/// - interface-id are different for each relay
+/// - first relay inserts valid remote-id
+/// - second relay inserts remote-id with empty vendor data
+/// - the solicit message requests for custom options in ORO
+/// - there are option types in RELAY-FORW that do not appear in SOLICIT
+/// - there are option types in SOLICT that do not appear in RELAY-FORW
+///
+/// RELAY-FORW
+/// - relay message option
+/// - RELAY-FORW
+/// - interface-id option
+/// - remote-id option
+/// - RELAY-FORW
+/// SOLICIT
+/// - client-id option
+/// - ia_na option
+/// - elapsed time
+/// - ORO
+/// - interface-id option
+/// - remote-id option
+///
+/// The original capture was posted to dibbler users mailing list.
+///
+/// @return created double relayed SOLICIT message
+Pkt6* capture2() {
+
+ // string exported from Wireshark
+ string hex_string =
+ "0c01200108880db800010000000000000000fe80000000000000020021fffe5c18a900"
+ "09007d0c0000000000000000000000000000000000fe80000000000000020021fffe5c"
+ "18a9001200154953414d3134342065746820312f312f30352f30310025000400000de9"
+ "00090036016b4fe20001000e0001000118b033410000215c18a90003000c00000001ff"
+ "ffffffffffffff00080002000000060006001700f200f30012001c4953414d3134347c"
+ "3239397c697076367c6e743a76703a313a313130002500120000197f0001000118b033"
+ "410000215c18a9";
+
+ std::vector<uint8_t> bin;
+
+ // Decode the hex string and store it in bin (which happens
+ // to be OptionBuffer format)
+ isc::util::encode::decodeHex(hex_string, bin);
+
+ Pkt6* pkt = new Pkt6(&bin[0], bin.size());
+ pkt->setRemotePort(547);
+ pkt->setRemoteAddr(IOAddress("fe80::1234"));
+ pkt->setLocalPort(547);
+ pkt->setLocalAddr(IOAddress("ff05::1:3"));
+ pkt->setIndex(2);
+ pkt->setIface("eth0");
+ return (pkt);
+}
TEST_F(Pkt6Test, unpack_solicit1) {
- Pkt6* sol = capture1();
+ scoped_ptr<Pkt6> sol(capture1());
ASSERT_EQ(true, sol->unpack());
- // check for length
+ // Check for length
EXPECT_EQ(98, sol->len() );
- // check for type
+ // Check for type
EXPECT_EQ(DHCPV6_SOLICIT, sol->getType() );
- // check that all present options are returned
+ // Check that all present options are returned
EXPECT_TRUE(sol->getOption(D6O_CLIENTID)); // client-id is present
EXPECT_TRUE(sol->getOption(D6O_IA_NA)); // IA_NA is present
EXPECT_TRUE(sol->getOption(D6O_ELAPSED_TIME)); // elapsed is present
EXPECT_TRUE(sol->getOption(D6O_NAME_SERVERS));
EXPECT_TRUE(sol->getOption(D6O_ORO));
- // let's check that non-present options are not returned
+ // Let's check that non-present options are not returned
EXPECT_FALSE(sol->getOption(D6O_SERVERID)); // server-id is missing
EXPECT_FALSE(sol->getOption(D6O_IA_TA));
EXPECT_FALSE(sol->getOption(D6O_IAADDR));
-
- delete sol;
}
TEST_F(Pkt6Test, packUnpack) {
-
- Pkt6* parent = new Pkt6(DHCPV6_SOLICIT, 0x020304);
+ scoped_ptr<Pkt6> parent(new Pkt6(DHCPV6_SOLICIT, 0x020304));
OptionPtr opt1(new Option(Option::V6, 1));
OptionPtr opt2(new Option(Option::V6, 2));
OptionPtr opt3(new Option(Option::V6, 100));
- // let's not use zero-length option type 3 as it is IA_NA
+ // Let's not use zero-length option type 3 as it is IA_NA
parent->addOption(opt1);
parent->addOption(opt2);
@@ -141,7 +217,7 @@ TEST_F(Pkt6Test, packUnpack) {
EXPECT_EQ(DHCPV6_SOLICIT, parent->getType());
- // calculated length should be 16
+ // Calculated length should be 16
EXPECT_EQ(Pkt6::DHCPV6_PKT_HDR_LEN + 3 * Option::OPTION6_HDR_LEN,
parent->len());
@@ -150,30 +226,28 @@ TEST_F(Pkt6Test, packUnpack) {
EXPECT_EQ(Pkt6::DHCPV6_PKT_HDR_LEN + 3 * Option::OPTION6_HDR_LEN,
parent->len());
- // create second packet,based on assembled data from the first one
- Pkt6* clone = new Pkt6(static_cast<const uint8_t*>(parent->getBuffer().getData()),
- parent->getBuffer().getLength());
+ // Create second packet,based on assembled data from the first one
+ scoped_ptr<Pkt6> clone(new Pkt6(
+ static_cast<const uint8_t*>(parent->getBuffer().getData()),
+ parent->getBuffer().getLength()));
- // now recreate options list
+ // Now recreate options list
EXPECT_TRUE( clone->unpack() );
// transid, message-type should be the same as before
EXPECT_EQ(parent->getTransid(), parent->getTransid());
EXPECT_EQ(DHCPV6_SOLICIT, clone->getType());
- EXPECT_TRUE( clone->getOption(1));
- EXPECT_TRUE( clone->getOption(2));
- EXPECT_TRUE( clone->getOption(100));
- EXPECT_FALSE( clone->getOption(4));
-
- delete parent;
- delete clone;
+ EXPECT_TRUE(clone->getOption(1));
+ EXPECT_TRUE(clone->getOption(2));
+ EXPECT_TRUE(clone->getOption(100));
+ EXPECT_FALSE(clone->getOption(4));
}
// This test verifies that options can be added (addOption()), retrieved
// (getOption(), getOptions()) and deleted (delOption()).
TEST_F(Pkt6Test, addGetDelOptions) {
- Pkt6* parent = new Pkt6(DHCPV6_SOLICIT, random() );
+ scoped_ptr<Pkt6> parent(new Pkt6(DHCPV6_SOLICIT, random()));
OptionPtr opt1(new Option(Option::V6, 1));
OptionPtr opt2(new Option(Option::V6, 2));
@@ -186,16 +260,16 @@ TEST_F(Pkt6Test, addGetDelOptions) {
EXPECT_EQ(opt1, parent->getOption(1));
EXPECT_EQ(opt2, parent->getOption(2));
- // expect NULL
+ // Expect NULL
EXPECT_EQ(OptionPtr(), parent->getOption(4));
- // now there are 2 options of type 2
+ // Now there are 2 options of type 2
parent->addOption(opt3);
Option::OptionCollection options = parent->getOptions(2);
EXPECT_EQ(2, options.size()); // there should be 2 instances
- // both options must be of type 2 and there must not be
+ // Both options must be of type 2 and there must not be
// any other type returned
for (Option::OptionCollection::const_iterator x= options.begin();
x != options.end(); ++x) {
@@ -211,26 +285,24 @@ TEST_F(Pkt6Test, addGetDelOptions) {
EXPECT_EQ(1, (*options.begin()).second->getType());
EXPECT_EQ(opt1, options.begin()->second);
- // let's delete one of them
+ // Let's delete one of them
EXPECT_EQ(true, parent->delOption(2));
- // there still should be the other option 2
+ // There still should be the other option 2
EXPECT_NE(OptionPtr(), parent->getOption(2));
- // let's delete the other option 2
+ // Let's delete the other option 2
EXPECT_EQ(true, parent->delOption(2));
- // no more options with type=2
+ // No more options with type=2
EXPECT_EQ(OptionPtr(), parent->getOption(2));
- // let's try to delete - should fail
+ // Let's try to delete - should fail
EXPECT_TRUE(false == parent->delOption(2));
// Finally try to get a non-existent option
options = parent->getOptions(1234);
EXPECT_EQ(0, options.size());
-
- delete parent;
}
TEST_F(Pkt6Test, Timestamp) {
@@ -306,5 +378,292 @@ TEST_F(Pkt6Test, getName) {
}
}
+// This test verifies that a fancy solicit that passed through two
+// relays can be parsed properly. See capture2() method description
+// for details regarding the packet.
+TEST_F(Pkt6Test, relayUnpack) {
+ boost::scoped_ptr<Pkt6> msg(capture2());
+
+ EXPECT_NO_THROW(msg->unpack());
+
+ EXPECT_EQ(DHCPV6_SOLICIT, msg->getType());
+ EXPECT_EQ(217, msg->len());
+
+ ASSERT_EQ(2, msg->relay_info_.size());
+
+ OptionPtr opt;
+
+ // Part 1: Check options inserted by the first relay
+
+ // There should be 2 options in first relay
+ EXPECT_EQ(2, msg->relay_info_[0].options_.size());
+
+ // There should be interface-id option
+ ASSERT_TRUE(opt = msg->getRelayOption(D6O_INTERFACE_ID, 0));
+ OptionBuffer data = opt->getData();
+ EXPECT_EQ(32, opt->len()); // 28 bytes of data + 4 bytes header
+ EXPECT_EQ(data.size(), 28);
+ // That's a strange interface-id, but this is a real life example
+ EXPECT_TRUE(0 == memcmp("ISAM144|299|ipv6|nt:vp:1:110", &data[0], 28));
+
+ // Get the remote-id option
+ ASSERT_TRUE(opt = msg->getRelayOption(D6O_REMOTE_ID, 0));
+ EXPECT_EQ(22, opt->len()); // 18 bytes of data + 4 bytes header
+ boost::shared_ptr<OptionCustom> custom = boost::dynamic_pointer_cast<OptionCustom>(opt);
+
+ uint32_t vendor_id = custom->readInteger<uint32_t>(0);
+ EXPECT_EQ(6527, vendor_id); // 6527 = Panthera Networks
+
+ uint8_t expected_remote_id[] = { 0x00, 0x01, 0x00, 0x01, 0x18, 0xb0, 0x33, 0x41, 0x00,
+ 0x00, 0x21, 0x5c, 0x18, 0xa9 };
+ OptionBuffer remote_id = custom->readBinary(1);
+ ASSERT_EQ(sizeof(expected_remote_id), remote_id.size());
+ ASSERT_EQ(0, memcmp(expected_remote_id, &remote_id[0], remote_id.size()));
+
+ // Part 2: Check options inserted by the second relay
+
+ // Get the interface-id from the second relay
+ ASSERT_TRUE(opt = msg->getRelayOption(D6O_INTERFACE_ID, 1));
+ data = opt->getData();
+ EXPECT_EQ(25, opt->len()); // 21 bytes + 4 bytes header
+ EXPECT_EQ(data.size(), 21);
+ EXPECT_TRUE(0 == memcmp("ISAM144 eth 1/1/05/01", &data[0], 21));
+
+ // Get the remote-id option
+ ASSERT_TRUE(opt = msg->getRelayOption(D6O_REMOTE_ID, 1));
+ EXPECT_EQ(8, opt->len());
+ custom = boost::dynamic_pointer_cast<OptionCustom>(opt);
+
+ vendor_id = custom->readInteger<uint32_t>(0);
+ EXPECT_EQ(3561, vendor_id); // 3561 = Broadband Forum
+ // @todo: See if we can validate empty remote-id field
+
+ // Let's check if there is no leak between options stored in
+ // the SOLICIT message and the relay.
+ EXPECT_FALSE(opt = msg->getRelayOption(D6O_IA_NA, 1));
+
+
+ // Part 3: Let's check options in the message itself
+ // This is not redundant compared to other direct messages tests,
+ // as we parsed it differently
+ EXPECT_EQ(DHCPV6_SOLICIT, msg->getType());
+ EXPECT_EQ(0x6b4fe2, msg->getTransid());
+
+ ASSERT_TRUE(opt = msg->getOption(D6O_CLIENTID));
+ EXPECT_EQ(18, opt->len()); // 14 bytes of data + 4 bytes of header
+ uint8_t expected_client_id[] = { 0x00, 0x01, 0x00, 0x01, 0x18, 0xb0, 0x33, 0x41, 0x00,
+ 0x00, 0x21, 0x5c, 0x18, 0xa9 };
+ data = opt->getData();
+ ASSERT_EQ(data.size(), sizeof(expected_client_id));
+ ASSERT_EQ(0, memcmp(&data[0], expected_client_id, data.size()));
+
+ ASSERT_TRUE(opt = msg->getOption(D6O_IA_NA));
+ boost::shared_ptr<Option6IA> ia =
+ boost::dynamic_pointer_cast<Option6IA>(opt);
+ ASSERT_TRUE(ia);
+ EXPECT_EQ(1, ia->getIAID());
+ EXPECT_EQ(0xffffffff, ia->getT1());
+ EXPECT_EQ(0xffffffff, ia->getT2());
+
+ ASSERT_TRUE(opt = msg->getOption(D6O_ELAPSED_TIME));
+ EXPECT_EQ(6, opt->len()); // 2 bytes of data + 4 bytes of header
+ boost::shared_ptr<OptionInt<uint16_t> > elapsed =
+ boost::dynamic_pointer_cast<OptionInt<uint16_t> > (opt);
+ ASSERT_TRUE(elapsed);
+ EXPECT_EQ(0, elapsed->getValue());
+
+ ASSERT_TRUE(opt = msg->getOption(D6O_ORO));
+ boost::shared_ptr<OptionIntArray<uint16_t> > oro =
+ boost::dynamic_pointer_cast<OptionIntArray<uint16_t> > (opt);
+ const std::vector<uint16_t> oro_list = oro->getValues();
+ EXPECT_EQ(3, oro_list.size());
+ EXPECT_EQ(23, oro_list[0]);
+ EXPECT_EQ(242, oro_list[1]);
+ EXPECT_EQ(243, oro_list[2]);
+}
+
+// This test verified that message with relay information can be
+// packed and then unpacked.
+TEST_F(Pkt6Test, relayPack) {
+
+ scoped_ptr<Pkt6> parent(new Pkt6(DHCPV6_ADVERTISE, 0x020304));
+
+ Pkt6::RelayInfo relay1;
+ relay1.msg_type_ = DHCPV6_RELAY_REPL;
+ relay1.hop_count_ = 17; // not very miningful, but useful for testing
+ relay1.linkaddr_ = IOAddress("2001:db8::1");
+ relay1.peeraddr_ = IOAddress("fe80::abcd");
+
+ uint8_t relay_opt_data[] = { 1, 2, 3, 4, 5, 6, 7, 8};
+ vector<uint8_t> relay_data(relay_opt_data,
+ relay_opt_data + sizeof(relay_opt_data));
+
+ OptionPtr optRelay1(new Option(Option::V6, 200, relay_data));
+
+ relay1.options_.insert(make_pair(optRelay1->getType(), optRelay1));
+
+ OptionPtr opt1(new Option(Option::V6, 100));
+ OptionPtr opt2(new Option(Option::V6, 101));
+ OptionPtr opt3(new Option(Option::V6, 102));
+ // Let's not use zero-length option type 3 as it is IA_NA
+
+ parent->addRelayInfo(relay1);
+
+ parent->addOption(opt1);
+ parent->addOption(opt2);
+ parent->addOption(opt3);
+
+ EXPECT_EQ(DHCPV6_ADVERTISE, parent->getType());
+
+ EXPECT_TRUE(parent->pack());
+
+ EXPECT_EQ(Pkt6::DHCPV6_PKT_HDR_LEN + 3 * Option::OPTION6_HDR_LEN // ADVERTISE
+ + Pkt6::DHCPV6_RELAY_HDR_LEN // Relay header
+ + Option::OPTION6_HDR_LEN // Relay-msg
+ + optRelay1->len(),
+ parent->len());
+
+ // Create second packet,based on assembled data from the first one
+ scoped_ptr<Pkt6> clone(new Pkt6(static_cast<const uint8_t*>(
+ parent->getBuffer().getData()),
+ parent->getBuffer().getLength()));
+
+ // Now recreate options list
+ EXPECT_TRUE( clone->unpack() );
+
+ // transid, message-type should be the same as before
+ EXPECT_EQ(parent->getTransid(), parent->getTransid());
+ EXPECT_EQ(DHCPV6_ADVERTISE, clone->getType());
+
+ EXPECT_TRUE( clone->getOption(100));
+ EXPECT_TRUE( clone->getOption(101));
+ EXPECT_TRUE( clone->getOption(102));
+ EXPECT_FALSE(clone->getOption(103));
+
+ // Now check relay info
+ ASSERT_EQ(1, clone->relay_info_.size());
+ EXPECT_EQ(DHCPV6_RELAY_REPL, clone->relay_info_[0].msg_type_);
+ EXPECT_EQ(17, clone->relay_info_[0].hop_count_);
+ EXPECT_EQ("2001:db8::1", clone->relay_info_[0].linkaddr_.toText());
+ EXPECT_EQ("fe80::abcd", clone->relay_info_[0].peeraddr_.toText());
+
+ // There should be exactly one option
+ EXPECT_EQ(1, clone->relay_info_[0].options_.size());
+ OptionPtr opt = clone->getRelayOption(200, 0);
+ EXPECT_TRUE(opt);
+ EXPECT_EQ(opt->getType() , optRelay1->getType());
+ EXPECT_EQ(opt->len(), optRelay1->len());
+ OptionBuffer data = opt->getData();
+ ASSERT_EQ(data.size(), sizeof(relay_opt_data));
+ EXPECT_EQ(0, memcmp(relay_opt_data, relay_opt_data, sizeof(relay_opt_data)));
+}
+
+
+// This test verified that options added by relays to the message can be
+// accessed and retrieved properly
+TEST_F(Pkt6Test, getAnyRelayOption) {
+
+ boost::scoped_ptr<Pkt6> msg(new Pkt6(DHCPV6_ADVERTISE, 0x020304));
+ msg->addOption(generateRandomOption(300));
+
+ // generate options for relay1
+ Pkt6::RelayInfo relay1;
+
+ // generate 3 options with code 200,201,202 and random content
+ OptionPtr relay1_opt1(generateRandomOption(200));
+ OptionPtr relay1_opt2(generateRandomOption(201));
+ OptionPtr relay1_opt3(generateRandomOption(202));
+
+ relay1.options_.insert(make_pair(200, relay1_opt1));
+ relay1.options_.insert(make_pair(201, relay1_opt2));
+ relay1.options_.insert(make_pair(202, relay1_opt3));
+ msg->addRelayInfo(relay1);
+
+ // generate options for relay2
+ Pkt6::RelayInfo relay2;
+ OptionPtr relay2_opt1(new Option(Option::V6, 100));
+ OptionPtr relay2_opt2(new Option(Option::V6, 101));
+ OptionPtr relay2_opt3(new Option(Option::V6, 102));
+ OptionPtr relay2_opt4(new Option(Option::V6, 200)); // the same code as relay1_opt3
+ relay2.options_.insert(make_pair(100, relay2_opt1));
+ relay2.options_.insert(make_pair(101, relay2_opt2));
+ relay2.options_.insert(make_pair(102, relay2_opt3));
+ relay2.options_.insert(make_pair(200, relay2_opt4));
+ msg->addRelayInfo(relay2);
+
+ // generate options for relay3
+ Pkt6::RelayInfo relay3;
+ OptionPtr relay3_opt1(generateRandomOption(200, 7));
+ relay3.options_.insert(make_pair(200, relay3_opt1));
+ msg->addRelayInfo(relay3);
+
+ // Ok, so we now have a packet that traversed the following network:
+ // client---relay3---relay2---relay1---server
+
+ // First check that the getAnyRelayOption does not confuse client options
+ // and relay options
+ // 300 is a client option, present in the message itself.
+ OptionPtr opt = msg->getAnyRelayOption(300, Pkt6::RELAY_SEARCH_FROM_CLIENT);
+ EXPECT_FALSE(opt);
+ opt = msg->getAnyRelayOption(300, Pkt6::RELAY_SEARCH_FROM_SERVER);
+ EXPECT_FALSE(opt);
+ opt = msg->getAnyRelayOption(300, Pkt6::RELAY_GET_FIRST);
+ EXPECT_FALSE(opt);
+ opt = msg->getAnyRelayOption(300, Pkt6::RELAY_GET_LAST);
+ EXPECT_FALSE(opt);
+
+ // Option 200 is added in every relay.
+
+ // We want to get that one inserted by relay3 (first match, starting from
+ // closest to the client.
+ opt = msg->getAnyRelayOption(200, Pkt6::RELAY_SEARCH_FROM_CLIENT);
+ ASSERT_TRUE(opt);
+ EXPECT_TRUE(opt->equal(relay3_opt1));
+
+ // We want to ge that one inserted by relay1 (first match, starting from
+ // closest to the server.
+ opt = msg->getAnyRelayOption(200, Pkt6::RELAY_SEARCH_FROM_SERVER);
+ ASSERT_TRUE(opt);
+ EXPECT_TRUE(opt->equal(relay1_opt1));
+
+ // We just want option from the first relay (closest to the client)
+ opt = msg->getAnyRelayOption(200, Pkt6::RELAY_GET_FIRST);
+ ASSERT_TRUE(opt);
+ EXPECT_TRUE(opt->equal(relay3_opt1));
+
+ // We just want option from the last relay (closest to the server)
+ opt = msg->getAnyRelayOption(200, Pkt6::RELAY_GET_LAST);
+ ASSERT_TRUE(opt);
+ EXPECT_TRUE(opt->equal(relay1_opt1));
+
+ // Let's try to ask for something that is inserted by the middle relay
+ // only.
+ opt = msg->getAnyRelayOption(100, Pkt6::RELAY_SEARCH_FROM_SERVER);
+ ASSERT_TRUE(opt);
+ EXPECT_TRUE(opt->equal(relay2_opt1));
+
+ opt = msg->getAnyRelayOption(100, Pkt6::RELAY_SEARCH_FROM_CLIENT);
+ ASSERT_TRUE(opt);
+ EXPECT_TRUE(opt->equal(relay2_opt1));
+
+ opt = msg->getAnyRelayOption(100, Pkt6::RELAY_GET_FIRST);
+ EXPECT_FALSE(opt);
+
+ opt = msg->getAnyRelayOption(100, Pkt6::RELAY_GET_LAST);
+ EXPECT_FALSE(opt);
+
+ // Finally, try to get an option that does not exist
+ opt = msg->getAnyRelayOption(500, Pkt6::RELAY_GET_FIRST);
+ EXPECT_FALSE(opt);
+
+ opt = msg->getAnyRelayOption(500, Pkt6::RELAY_GET_LAST);
+ EXPECT_FALSE(opt);
+
+ opt = msg->getAnyRelayOption(500, Pkt6::RELAY_SEARCH_FROM_SERVER);
+ EXPECT_FALSE(opt);
+
+ opt = msg->getAnyRelayOption(500, Pkt6::RELAY_SEARCH_FROM_CLIENT);
+ EXPECT_FALSE(opt);
+}
}
diff --git a/src/lib/dhcpsrv/Makefile.am b/src/lib/dhcpsrv/Makefile.am
index db82513..29e8c2f 100644
--- a/src/lib/dhcpsrv/Makefile.am
+++ b/src/lib/dhcpsrv/Makefile.am
@@ -1,6 +1,8 @@
SUBDIRS = . tests
-AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib -DDHCP_DATA_DIR="\"$(localstatedir)\""
+dhcp_data_dir = @localstatedir@/@PACKAGE@
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib -DDHCP_DATA_DIR="\"$(dhcp_data_dir)\""
AM_CPPFLAGS += $(BOOST_INCLUDES)
if HAVE_MYSQL
AM_CPPFLAGS += $(MYSQL_CPPFLAGS)
@@ -37,6 +39,7 @@ libb10_dhcpsrv_la_SOURCES += dbaccess_parser.cc dbaccess_parser.h
libb10_dhcpsrv_la_SOURCES += dhcpsrv_log.cc dhcpsrv_log.h
libb10_dhcpsrv_la_SOURCES += cfgmgr.cc cfgmgr.h
libb10_dhcpsrv_la_SOURCES += dhcp_config_parser.h
+libb10_dhcpsrv_la_SOURCES += dhcp_parsers.cc dhcp_parsers.h
libb10_dhcpsrv_la_SOURCES += key_from_key.h
libb10_dhcpsrv_la_SOURCES += lease_mgr.cc lease_mgr.h
libb10_dhcpsrv_la_SOURCES += lease_mgr_factory.cc lease_mgr_factory.h
@@ -57,6 +60,7 @@ libb10_dhcpsrv_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
libb10_dhcpsrv_la_LIBADD = $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
libb10_dhcpsrv_la_LIBADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
libb10_dhcpsrv_la_LIBADD += $(top_builddir)/src/lib/util/libb10-util.la
+libb10_dhcpsrv_la_LIBADD += $(top_builddir)/src/lib/cc/libb10-cc.la
libb10_dhcpsrv_la_LDFLAGS = -no-undefined -version-info 3:0:0
if HAVE_MYSQL
libb10_dhcpsrv_la_LDFLAGS += $(MYSQL_LIBS)
@@ -74,3 +78,8 @@ EXTRA_DIST = dhcpsrv_messages.mes
# Distribute MySQL schema creation script and backend documentation
EXTRA_DIST += dhcpdb_create.mysql database_backends.dox libdhcpsrv.dox
dist_pkgdata_DATA = dhcpdb_create.mysql
+
+install-data-local:
+ $(mkinstalldirs) $(DESTDIR)$(dhcp_data_dir)
+
+
diff --git a/src/lib/dhcpsrv/alloc_engine.cc b/src/lib/dhcpsrv/alloc_engine.cc
index a3df8e1..9c5bdeb 100644
--- a/src/lib/dhcpsrv/alloc_engine.cc
+++ b/src/lib/dhcpsrv/alloc_engine.cc
@@ -177,6 +177,14 @@ AllocEngine::allocateAddress6(const Subnet6Ptr& subnet,
isc_throw(InvalidOperation, "No allocator selected");
}
+ if (!subnet) {
+ isc_throw(InvalidOperation, "Subnet is required for allocation");
+ }
+
+ if (!duid) {
+ isc_throw(InvalidOperation, "DUID is mandatory for allocation");
+ }
+
// check if there's existing lease for that subnet/duid/iaid combination.
Lease6Ptr existing = LeaseMgrFactory::instance().getLease6(*duid, iaid, subnet->getID());
if (existing) {
@@ -284,10 +292,17 @@ AllocEngine::allocateAddress4(const SubnetPtr& subnet,
isc_throw(InvalidOperation, "No allocator selected");
}
+ if (!subnet) {
+ isc_throw(InvalidOperation, "Can't allocate IPv4 address without subnet");
+ }
+
+ if (!hwaddr) {
+ isc_throw(InvalidOperation, "HWAddr must be defined");
+ }
+
// Check if there's existing lease for that subnet/clientid/hwaddr combination.
Lease4Ptr existing = LeaseMgrFactory::instance().getLease4(*hwaddr, subnet->getID());
if (existing) {
- std::cout << "Got lease using HWADdr" << std::endl;
// We have a lease already. This is a returning client, probably after
// its reboot.
existing = renewLease4(subnet, clientid, hwaddr, existing, fake_allocation);
@@ -302,7 +317,6 @@ AllocEngine::allocateAddress4(const SubnetPtr& subnet,
if (clientid) {
existing = LeaseMgrFactory::instance().getLease4(*clientid, subnet->getID());
if (existing) {
- std::cout << "Got lease using Clientid" << std::endl;
// we have a lease already. This is a returning client, probably after
// its reboot.
existing = renewLease4(subnet, clientid, hwaddr, existing, fake_allocation);
diff --git a/src/lib/dhcpsrv/cfgmgr.cc b/src/lib/dhcpsrv/cfgmgr.cc
index b5e83e3..592efb7 100644
--- a/src/lib/dhcpsrv/cfgmgr.cc
+++ b/src/lib/dhcpsrv/cfgmgr.cc
@@ -40,8 +40,7 @@ CfgMgr::addOptionSpace4(const OptionSpacePtr& space) {
isc_throw(InvalidOptionSpace, "option space " << space->getName()
<< " already added.");
}
- spaces4_.insert(std::pair<std::string,
- OptionSpacePtr>(space->getName(), space));
+ spaces4_.insert(make_pair(space->getName(), space));
}
void
@@ -55,8 +54,7 @@ CfgMgr::addOptionSpace6(const OptionSpacePtr& space) {
isc_throw(InvalidOptionSpace, "option space " << space->getName()
<< " already added.");
}
- spaces6_.insert(std::pair<std::string,
- OptionSpacePtr>(space->getName(), space));
+ spaces6_.insert(make_pair(space->getName(), space));
}
void
@@ -147,7 +145,7 @@ CfgMgr::getSubnet6(const isc::asiolink::IOAddress& hint) {
// If there's only one subnet configured, let's just use it
// The idea is to keep small deployments easy. In a small network - one
- // router that also runs DHCPv6 server. Users specifies a single pool and
+ // router that also runs DHCPv6 server. User specifies a single pool and
// expects it to just work. Without this, the server would complain that it
// doesn't have IP address on its interfaces that matches that
// configuration. Such requirement makes sense in IPv4, but not in IPv6.
@@ -178,14 +176,30 @@ CfgMgr::getSubnet6(const isc::asiolink::IOAddress& hint) {
return (Subnet6Ptr());
}
-Subnet6Ptr CfgMgr::getSubnet6(OptionPtr /*interfaceId*/) {
- /// @todo: Implement get subnet6 by interface-id (for relayed traffic)
- isc_throw(NotImplemented, "Relayed DHCPv6 traffic is not supported yet.");
+Subnet6Ptr CfgMgr::getSubnet6(OptionPtr iface_id_option) {
+ if (!iface_id_option) {
+ return (Subnet6Ptr());
+ }
+
+ // Let's iterate over all subnets and for those that have interface-id
+ // defined, check if the interface-id is equal to what we are looking for
+ for (Subnet6Collection::iterator subnet = subnets6_.begin();
+ subnet != subnets6_.end(); ++subnet) {
+ if ( (*subnet)->getInterfaceId() &&
+ ((*subnet)->getInterfaceId()->equal(iface_id_option))) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+ DHCPSRV_CFGMGR_SUBNET6_IFACE_ID)
+ .arg((*subnet)->toText());
+ return (*subnet);
+ }
+ }
+ return (Subnet6Ptr());
}
void CfgMgr::addSubnet6(const Subnet6Ptr& subnet) {
/// @todo: Check that this new subnet does not cross boundaries of any
/// other already defined subnet.
+ /// @todo: Check that there is no subnet with the same interface-id
LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_ADD_SUBNET6)
.arg(subnet->toText());
subnets6_.push_back(subnet);
diff --git a/src/lib/dhcpsrv/cfgmgr.h b/src/lib/dhcpsrv/cfgmgr.h
index 00f7e25..05c1752 100644
--- a/src/lib/dhcpsrv/cfgmgr.h
+++ b/src/lib/dhcpsrv/cfgmgr.h
@@ -174,7 +174,6 @@ public:
/// @param interface_id content of interface-id option returned by a relay
///
/// @return a subnet object
- /// @todo This method is not currently supported.
Subnet6Ptr getSubnet6(OptionPtr interface_id);
/// @brief adds an IPv6 subnet
diff --git a/src/lib/dhcpsrv/dhcp_config_parser.h b/src/lib/dhcpsrv/dhcp_config_parser.h
index cb419f8..77e46c8 100644
--- a/src/lib/dhcpsrv/dhcp_config_parser.h
+++ b/src/lib/dhcpsrv/dhcp_config_parser.h
@@ -15,6 +15,12 @@
#ifndef DHCP_CONFIG_PARSER_H
#define DHCP_CONFIG_PARSER_H
+#include <exceptions/exceptions.h>
+#include <cc/data.h>
+#include <stdint.h>
+#include <string>
+#include <map>
+
namespace isc {
namespace dhcp {
@@ -122,38 +128,10 @@ public:
/// This method is expected to be called after @c build(), and only once.
/// The result is undefined otherwise.
virtual void commit() = 0;
-
-protected:
-
- /// @brief Return the parsed entry from the provided storage.
- ///
- /// This method returns the parsed entry from the provided
- /// storage. If the entry is not found, then exception is
- /// thrown.
- ///
- /// @param param_id name of the configuration entry.
- /// @param storage storage where the entry should be searched.
- /// @tparam ReturnType type of the returned value.
- /// @tparam StorageType type of the storage.
- ///
- /// @throw DhcpConfigError if the entry has not been found
- /// in the storage.
- template<typename ReturnType, typename StorageType>
- static ReturnType getParam(const std::string& param_id,
- const StorageType& storage) {
- typename StorageType::const_iterator param = storage.find(param_id);
- if (param == storage.end()) {
- isc_throw(DhcpConfigError, "missing parameter '"
- << param_id << "'");
- }
- ReturnType value = param->second;
- return (value);
- }
-
};
-
-} // end of isc::dhcp namespace
-} // end of isc namespace
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
#endif // DHCP_CONFIG_PARSER_H
+
diff --git a/src/lib/dhcpsrv/dhcp_parsers.cc b/src/lib/dhcpsrv/dhcp_parsers.cc
new file mode 100644
index 0000000..d21538b
--- /dev/null
+++ b/src/lib/dhcpsrv/dhcp_parsers.cc
@@ -0,0 +1,957 @@
+// 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 <dhcp/iface_mgr.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcpsrv/cfgmgr.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 <boost/lexical_cast.hpp>
+
+#include <string>
+#include <map>
+
+using namespace std;
+using namespace isc::data;
+
+namespace isc {
+namespace dhcp {
+
+// *********************** ParserContext *************************
+
+ParserContext::ParserContext(Option::Universe universe):
+ boolean_values_(new BooleanStorage()),
+ uint32_values_(new Uint32Storage()),
+ string_values_(new StringStorage()),
+ options_(new OptionStorage()),
+ option_defs_(new OptionDefStorage()),
+ universe_(universe) {
+ }
+
+ParserContext::ParserContext(const ParserContext& rhs):
+ boolean_values_(new BooleanStorage(*(rhs.boolean_values_))),
+ uint32_values_(new Uint32Storage(*(rhs.uint32_values_))),
+ string_values_(new StringStorage(*(rhs.string_values_))),
+ options_(new OptionStorage(*(rhs.options_))),
+ option_defs_(new OptionDefStorage(*(rhs.option_defs_))),
+ universe_(rhs.universe_) {
+ }
+
+ParserContext&
+ParserContext::operator=(const ParserContext& rhs) {
+ if (this != &rhs) {
+ boolean_values_ =
+ BooleanStoragePtr(new BooleanStorage(*(rhs.boolean_values_)));
+ uint32_values_ =
+ Uint32StoragePtr(new Uint32Storage(*(rhs.uint32_values_)));
+ string_values_ =
+ StringStoragePtr(new StringStorage(*(rhs.string_values_)));
+ options_ = OptionStoragePtr(new OptionStorage(*(rhs.options_)));
+ option_defs_ =
+ OptionDefStoragePtr(new OptionDefStorage(*(rhs.option_defs_)));
+ universe_ = rhs.universe_;
+ }
+ return (*this);
+ }
+
+
+// **************************** DebugParser *************************
+
+DebugParser::DebugParser(const std::string& param_name)
+ :param_name_(param_name) {
+}
+
+void
+DebugParser::build(ConstElementPtr new_config) {
+ std::cout << "Build for token: [" << param_name_ << "] = ["
+ << value_->str() << "]" << std::endl;
+ value_ = new_config;
+}
+
+void
+DebugParser::commit() {
+ // Debug message. The whole DebugParser class is used only for parser
+ // debugging, and is not used in production code. It is very convenient
+ // to keep it around. Please do not turn this cout into logger calls.
+ std::cout << "Commit for token: [" << param_name_ << "] = ["
+ << value_->str() << "]" << std::endl;
+}
+
+// **************************** BooleanParser *************************
+
+template<> void ValueParser<bool>::build(isc::data::ConstElementPtr value) {
+ // The Config Manager checks if user specified a
+ // valid value for a boolean parameter: True or False.
+ // We should have a boolean Element, use value directly
+ try {
+ value_ = value->boolValue();
+ } catch (const isc::data::TypeError &) {
+ isc_throw(BadValue, " Wrong value type for " << param_name_
+ << " : build called with a non-boolean element.");
+ }
+}
+
+// **************************** Uin32Parser *************************
+
+template<> void ValueParser<uint32_t>::build(ConstElementPtr value) {
+ int64_t check;
+ string x = value->str();
+ try {
+ check = boost::lexical_cast<int64_t>(x);
+ } catch (const boost::bad_lexical_cast &) {
+ isc_throw(BadValue, "Failed to parse value " << value->str()
+ << " as unsigned 32-bit integer.");
+ }
+ if (check > std::numeric_limits<uint32_t>::max()) {
+ isc_throw(BadValue, "Value " << value->str() << "is too large"
+ << " for unsigned 32-bit integer.");
+ }
+ if (check < 0) {
+ isc_throw(BadValue, "Value " << value->str() << "is negative."
+ << " Only 0 or larger are allowed for unsigned 32-bit integer.");
+ }
+
+ // value is small enough to fit
+ value_ = static_cast<uint32_t>(check);
+}
+
+// **************************** StringParser *************************
+
+template <> void ValueParser<std::string>::build(ConstElementPtr value) {
+ value_ = value->str();
+ boost::erase_all(value_, "\"");
+}
+
+// ******************** InterfaceListConfigParser *************************
+
+InterfaceListConfigParser::InterfaceListConfigParser(const std::string&
+ param_name) {
+ if (param_name != "interface") {
+ isc_throw(BadValue, "Internal error. Interface configuration "
+ "parser called for the wrong parameter: " << param_name);
+ }
+}
+
+void
+InterfaceListConfigParser::build(ConstElementPtr value) {
+ BOOST_FOREACH(ConstElementPtr iface, value->listValue()) {
+ interfaces_.push_back(iface->str());
+ }
+}
+
+void
+InterfaceListConfigParser::commit() {
+ /// @todo: Implement per interface listening. Currently always listening
+ /// on all interfaces.
+}
+
+// **************************** OptionDataParser *************************
+OptionDataParser::OptionDataParser(const std::string&, OptionStoragePtr options,
+ ParserContextPtr global_context)
+ : boolean_values_(new BooleanStorage()),
+ string_values_(new StringStorage()), uint32_values_(new Uint32Storage()),
+ options_(options), option_descriptor_(false),
+ global_context_(global_context) {
+ if (!options_) {
+ isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
+ << "options storage may not be NULL");
+ }
+
+ if (!global_context_) {
+ isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
+ << "context may may not be NULL");
+ }
+}
+
+void
+OptionDataParser::build(ConstElementPtr option_data_entries) {
+ BOOST_FOREACH(ConfigPair param, option_data_entries->mapValue()) {
+ ParserPtr parser;
+ if (param.first == "name" || param.first == "data" ||
+ param.first == "space") {
+ StringParserPtr name_parser(new StringParser(param.first,
+ string_values_));
+ parser = name_parser;
+ } else if (param.first == "code") {
+ Uint32ParserPtr code_parser(new Uint32Parser(param.first,
+ uint32_values_));
+ parser = code_parser;
+ } else if (param.first == "csv-format") {
+ BooleanParserPtr value_parser(new BooleanParser(param.first,
+ boolean_values_));
+ parser = value_parser;
+ } else {
+ isc_throw(DhcpConfigError,
+ "Parser error: option-data parameter not supported: "
+ << param.first);
+ }
+
+ parser->build(param.second);
+ // Before we can create an option we need to get the data from
+ // the child parsers. The only way to do it is to invoke commit
+ // on them so as they store the values in appropriate storages
+ // that this class provided to them. Note that this will not
+ // modify values stored in the global storages so the configuration
+ // will remain consistent even parsing fails somewhere further on.
+ parser->commit();
+ }
+
+ // Try to create the option instance.
+ createOption();
+}
+
+void
+OptionDataParser::commit() {
+ if (!option_descriptor_.option) {
+ // Before we can commit the new option should be configured. If it is
+ // not than somebody must have called commit() before build().
+ isc_throw(isc::InvalidOperation,
+ "parser logic error: no option has been configured and"
+ " thus there is nothing to commit. Has build() been called?");
+ }
+
+ uint16_t opt_type = option_descriptor_.option->getType();
+ Subnet::OptionContainerPtr options = options_->getItems(option_space_);
+ // The getItems() should never return NULL pointer. If there are no
+ // options configured for the particular option space a pointer
+ // to an empty container should be returned.
+ assert(options);
+ Subnet::OptionContainerTypeIndex& idx = options->get<1>();
+ // Try to find options with the particular option code in the main
+ // storage. If found, remove these options because they will be
+ // replaced with new one.
+ Subnet::OptionContainerTypeRange range = idx.equal_range(opt_type);
+ if (std::distance(range.first, range.second) > 0) {
+ idx.erase(range.first, range.second);
+ }
+
+ // Append new option to the main storage.
+ options_->addItem(option_descriptor_, option_space_);
+}
+
+void
+OptionDataParser::createOption() {
+ // Option code is held in the uint32_t storage but is supposed to
+ // be uint16_t value. We need to check that value in the configuration
+ // does not exceed range of uint8_t and is not zero.
+ uint32_t option_code = uint32_values_->getParam("code");
+ if (option_code == 0) {
+ isc_throw(DhcpConfigError, "option code must not be zero."
+ << " Option code '0' is reserved in DHCPv4.");
+ } else if (option_code > std::numeric_limits<uint8_t>::max()) {
+ isc_throw(DhcpConfigError, "invalid option code '" << option_code
+ << "', it must not exceed '"
+ << std::numeric_limits<uint8_t>::max() << "'");
+ }
+
+ // Check that the option name has been specified, is non-empty and does not
+ // contain spaces
+ std::string option_name = string_values_->getParam("name");
+ if (option_name.empty()) {
+ isc_throw(DhcpConfigError, "name of the option with code '"
+ << option_code << "' is empty");
+ } else if (option_name.find(" ") != std::string::npos) {
+ isc_throw(DhcpConfigError, "invalid option name '" << option_name
+ << "', space character is not allowed");
+ }
+
+ std::string option_space = string_values_->getParam("space");
+ if (!OptionSpace::validateName(option_space)) {
+ isc_throw(DhcpConfigError, "invalid option space name '"
+ << option_space << "' specified for option '"
+ << option_name << "' (code '" << option_code
+ << "')");
+ }
+
+ // Find the Option Definition for the option by its option code.
+ // findOptionDefinition will throw if not found, no need to test.
+ OptionDefinitionPtr def;
+ if (!(def = findServerSpaceOptionDefinition(option_space, option_code))) {
+ // If we are not dealing with a standard option then we
+ // need to search for its definition among user-configured
+ // options. They are expected to be in the global storage
+ // already.
+ OptionDefContainerPtr defs =
+ global_context_->option_defs_->getItems(option_space);
+
+ // The getItems() should never return the NULL pointer. If there are
+ // no option definitions for the particular option space a pointer
+ // to an empty container should be returned.
+ assert(defs);
+ const OptionDefContainerTypeIndex& idx = defs->get<1>();
+ OptionDefContainerTypeRange range = idx.equal_range(option_code);
+ if (std::distance(range.first, range.second) > 0) {
+ def = *range.first;
+ }
+ if (!def) {
+ isc_throw(DhcpConfigError, "definition for the option '"
+ << option_space << "." << option_name
+ << "' having code '" << option_code
+ << "' does not exist");
+ }
+ }
+
+ // Get option data from the configuration database ('data' field).
+ const std::string option_data = string_values_->getParam("data");
+ const bool csv_format = boolean_values_->getParam("csv-format");
+
+ // Transform string of hexadecimal digits into binary format.
+ std::vector<uint8_t> binary;
+ std::vector<std::string> data_tokens;
+
+ if (csv_format) {
+ // If the option data is specified as a string of comma
+ // separated values then we need to split this string into
+ // individual values - each value will be used to initialize
+ // one data field of an option.
+ data_tokens = isc::util::str::tokens(option_data, ",");
+ } else {
+ // Otherwise, the option data is specified as a string of
+ // hexadecimal digits that we have to turn into binary format.
+ try {
+ util::encode::decodeHex(option_data, binary);
+ } catch (...) {
+ isc_throw(DhcpConfigError, "option data is not a valid"
+ << " string of hexadecimal digits: " << option_data);
+ }
+ }
+
+ OptionPtr option;
+ if (!def) {
+ if (csv_format) {
+ isc_throw(DhcpConfigError, "the CSV option data format can be"
+ " used to specify values for an option that has a"
+ " definition. The option with code " << option_code
+ << " does not have a definition.");
+ }
+
+ // @todo We have a limited set of option definitions intiialized at
+ // the moment. In the future we want to initialize option definitions
+ // for all options. Consequently an error will be issued if an option
+ // definition does not exist for a particular option code. For now it is
+ // ok to create generic option if definition does not exist.
+ OptionPtr option(new Option(global_context_->universe_,
+ static_cast<uint16_t>(option_code), binary));
+ // The created option is stored in option_descriptor_ class member
+ // until the commit stage when it is inserted into the main storage.
+ // If an option with the same code exists in main storage already the
+ // old option is replaced.
+ option_descriptor_.option = option;
+ option_descriptor_.persistent = false;
+ } else {
+
+ // Option name should match the definition. The option name
+ // may seem to be redundant but in the future we may want
+ // to reference options and definitions using their names
+ // and/or option codes so keeping the option name in the
+ // definition of option value makes sense.
+ if (def->getName() != option_name) {
+ isc_throw(DhcpConfigError, "specified option name '"
+ << option_name << "' does not match the "
+ << "option definition: '" << option_space
+ << "." << def->getName() << "'");
+ }
+
+ // Option definition has been found so let's use it to create
+ // an instance of our option.
+ try {
+ OptionPtr option = csv_format ?
+ def->optionFactory(global_context_->universe_,
+ option_code, data_tokens) :
+ def->optionFactory(global_context_->universe_,
+ option_code, binary);
+ Subnet::OptionDescriptor desc(option, false);
+ option_descriptor_.option = option;
+ option_descriptor_.persistent = false;
+ } catch (const isc::Exception& ex) {
+ isc_throw(DhcpConfigError, "option data does not match"
+ << " option definition (space: " << option_space
+ << ", code: " << option_code << "): "
+ << ex.what());
+ }
+ }
+
+ // All went good, so we can set the option space name.
+ option_space_ = option_space;
+}
+
+// **************************** OptionDataListParser *************************
+OptionDataListParser::OptionDataListParser(const std::string&,
+ OptionStoragePtr options, ParserContextPtr global_context,
+ OptionDataParserFactory* optionDataParserFactory)
+ : options_(options), local_options_(new OptionStorage()),
+ global_context_(global_context),
+ optionDataParserFactory_(optionDataParserFactory) {
+ if (!options_) {
+ isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
+ << "options storage may not be NULL");
+ }
+
+ if (!options_) {
+ isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
+ << "context may not be NULL");
+ }
+
+ if (!optionDataParserFactory_) {
+ isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
+ << "option data parser factory may not be NULL");
+ }
+}
+
+void
+OptionDataListParser::build(ConstElementPtr option_data_list) {
+ BOOST_FOREACH(ConstElementPtr option_value, option_data_list->listValue()) {
+ boost::shared_ptr<OptionDataParser>
+ parser((*optionDataParserFactory_)("option-data",
+ local_options_, global_context_));
+
+ // options_ member will hold instances of all options thus
+ // each OptionDataParser takes it as a storage.
+ // Build the instance of a single option.
+ parser->build(option_value);
+ // Store a parser as it will be used to commit.
+ parsers_.push_back(parser);
+ }
+}
+
+void
+OptionDataListParser::commit() {
+ BOOST_FOREACH(ParserPtr parser, parsers_) {
+ parser->commit();
+ }
+
+ // Parsing was successful and we have all configured
+ // options in local storage. We can now replace old values
+ // with new values.
+ std::swap(*local_options_, *options_);
+}
+
+// ******************************** OptionDefParser ****************************
+OptionDefParser::OptionDefParser(const std::string&,
+ OptionDefStoragePtr storage)
+ : storage_(storage), boolean_values_(new BooleanStorage()),
+ string_values_(new StringStorage()), uint32_values_(new Uint32Storage()) {
+ if (!storage_) {
+ isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
+ << "options storage may not be NULL");
+ }
+}
+
+void
+OptionDefParser::build(ConstElementPtr option_def) {
+ // Parse the elements that make up the option definition.
+ BOOST_FOREACH(ConfigPair param, option_def->mapValue()) {
+ std::string entry(param.first);
+ ParserPtr parser;
+ if (entry == "name" || entry == "type" || entry == "record-types"
+ || entry == "space" || entry == "encapsulate") {
+ StringParserPtr str_parser(new StringParser(entry,
+ string_values_));
+ parser = str_parser;
+ } else if (entry == "code") {
+ Uint32ParserPtr code_parser(new Uint32Parser(entry,
+ uint32_values_));
+ parser = code_parser;
+ } else if (entry == "array") {
+ BooleanParserPtr array_parser(new BooleanParser(entry,
+ boolean_values_));
+ parser = array_parser;
+ } else {
+ isc_throw(DhcpConfigError, "invalid parameter: " << entry);
+ }
+
+ parser->build(param.second);
+ parser->commit();
+ }
+
+ // Create an instance of option definition.
+ createOptionDef();
+
+ // Get all items we collected so far for the particular option space.
+ OptionDefContainerPtr defs = storage_->getItems(option_space_name_);
+
+ // Check if there are any items with option code the same as the
+ // one specified for the definition we are now creating.
+ const OptionDefContainerTypeIndex& idx = defs->get<1>();
+ const OptionDefContainerTypeRange& range =
+ idx.equal_range(option_definition_->getCode());
+
+ // If there are any items with this option code already we need
+ // to issue an error because we don't allow duplicates for
+ // option definitions within an option space.
+ if (std::distance(range.first, range.second) > 0) {
+ isc_throw(DhcpConfigError, "duplicated option definition for"
+ << " code '" << option_definition_->getCode() << "'");
+ }
+}
+
+void
+OptionDefParser::commit() {
+ if (storage_ && option_definition_ &&
+ OptionSpace::validateName(option_space_name_)) {
+ storage_->addItem(option_definition_, option_space_name_);
+ }
+}
+
+void
+OptionDefParser::createOptionDef() {
+ // Get the option space name and validate it.
+ std::string space = string_values_->getParam("space");
+ if (!OptionSpace::validateName(space)) {
+ isc_throw(DhcpConfigError, "invalid option space name '"
+ << space << "'");
+ }
+
+ // Get other parameters that are needed to create the
+ // option definition.
+ std::string name = string_values_->getParam("name");
+ uint32_t code = uint32_values_->getParam("code");
+ std::string type = string_values_->getParam("type");
+ bool array_type = boolean_values_->getParam("array");
+ std::string encapsulates = string_values_->getParam("encapsulate");
+
+ // Create option definition.
+ OptionDefinitionPtr def;
+ // We need to check if user has set encapsulated option space
+ // name. If so, different constructor will be used.
+ if (!encapsulates.empty()) {
+ // Arrays can't be used together with sub-options.
+ if (array_type) {
+ isc_throw(DhcpConfigError, "option '" << space << "."
+ << "name" << "', comprising an array of data"
+ << " fields may not encapsulate any option space");
+
+ } else if (encapsulates == space) {
+ isc_throw(DhcpConfigError, "option must not encapsulate"
+ << " an option space it belongs to: '"
+ << space << "." << name << "' is set to"
+ << " encapsulate '" << space << "'");
+
+ } else {
+ def.reset(new OptionDefinition(name, code, type,
+ encapsulates.c_str()));
+ }
+
+ } else {
+ def.reset(new OptionDefinition(name, code, type, array_type));
+
+ }
+
+ // The record-types field may carry a list of comma separated names
+ // of data types that form a record.
+ std::string record_types = string_values_->getParam("record-types");
+
+ // Split the list of record types into tokens.
+ std::vector<std::string> record_tokens =
+ isc::util::str::tokens(record_types, ",");
+ // Iterate over each token and add a record type into
+ // option definition.
+ BOOST_FOREACH(std::string record_type, record_tokens) {
+ try {
+ boost::trim(record_type);
+ if (!record_type.empty()) {
+ def->addRecordField(record_type);
+ }
+ } catch (const Exception& ex) {
+ isc_throw(DhcpConfigError, "invalid record type values"
+ << " specified for the option definition: "
+ << ex.what());
+ }
+ }
+
+ // Check the option definition parameters are valid.
+ try {
+ def->validate();
+ } catch (const isc::Exception& ex) {
+ isc_throw(DhcpConfigError, "invalid option definition"
+ << " parameters: " << ex.what());
+ }
+
+ // Option definition has been created successfully.
+ option_space_name_ = space;
+ option_definition_ = def;
+}
+
+// ******************************** OptionDefListParser ************************
+OptionDefListParser::OptionDefListParser(const std::string&,
+ OptionDefStoragePtr storage) :storage_(storage) {
+ if (!storage_) {
+ isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
+ << "storage may not be NULL");
+ }
+}
+
+void
+OptionDefListParser::build(ConstElementPtr option_def_list) {
+ // Clear existing items in the storage.
+ // We are going to replace all of them.
+ storage_->clearItems();
+
+ if (!option_def_list) {
+ isc_throw(DhcpConfigError, "parser error: a pointer to a list of"
+ << " option definitions is NULL");
+ }
+
+ BOOST_FOREACH(ConstElementPtr option_def, option_def_list->listValue()) {
+ boost::shared_ptr<OptionDefParser>
+ parser(new OptionDefParser("single-option-def", storage_));
+ parser->build(option_def);
+ parser->commit();
+ }
+}
+
+void
+OptionDefListParser::commit() {
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+ cfg_mgr.deleteOptionDefs();
+
+ // We need to move option definitions from the temporary
+ // storage to the storage.
+ std::list<std::string> space_names =
+ storage_->getOptionSpaceNames();
+
+ BOOST_FOREACH(std::string space_name, space_names) {
+ BOOST_FOREACH(OptionDefinitionPtr def,
+ *(storage_->getItems(space_name))) {
+ // All option definitions should be initialized to non-NULL
+ // values. The validation is expected to be made by the
+ // OptionDefParser when creating an option definition.
+ assert(def);
+ cfg_mgr.addOptionDef(def, space_name);
+ }
+ }
+}
+
+//****************************** PoolParser ********************************
+PoolParser::PoolParser(const std::string&, PoolStoragePtr pools)
+ :pools_(pools) {
+
+ if (!pools_) {
+ isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
+ << "storage may not be NULL");
+ }
+}
+
+void
+PoolParser::build(ConstElementPtr pools_list) {
+ BOOST_FOREACH(ConstElementPtr text_pool, pools_list->listValue()) {
+ // That should be a single pool representation. It should contain
+ // text is form prefix/len or first - last. Note that spaces
+ // are allowed
+ string txt = text_pool->stringValue();
+
+ // first let's remove any whitespaces
+ boost::erase_all(txt, " "); // space
+ boost::erase_all(txt, "\t"); // tabulation
+
+ // Is this prefix/len notation?
+ size_t pos = txt.find("/");
+ if (pos != string::npos) {
+ isc::asiolink::IOAddress addr("::");
+ uint8_t len = 0;
+ try {
+ addr = isc::asiolink::IOAddress(txt.substr(0, pos));
+
+ // start with the first character after /
+ string prefix_len = txt.substr(pos + 1);
+
+ // It is lexical cast to int and then downcast to uint8_t.
+ // Direct cast to uint8_t (which is really an unsigned char)
+ // will result in interpreting the first digit as output
+ // value and throwing exception if length is written on two
+ // digits (because there are extra characters left over).
+
+ // No checks for values over 128. Range correctness will
+ // be checked in Pool4 constructor.
+ len = boost::lexical_cast<int>(prefix_len);
+ } catch (...) {
+ isc_throw(DhcpConfigError, "Failed to parse pool "
+ "definition: " << text_pool->stringValue());
+ }
+
+ PoolPtr pool(poolMaker(addr, len));
+ local_pools_.push_back(pool);
+ continue;
+ }
+
+ // Is this min-max notation?
+ pos = txt.find("-");
+ if (pos != string::npos) {
+ // using min-max notation
+ isc::asiolink::IOAddress min(txt.substr(0,pos));
+ isc::asiolink::IOAddress max(txt.substr(pos + 1));
+
+ PoolPtr pool(poolMaker(min, max));
+ local_pools_.push_back(pool);
+ continue;
+ }
+
+ isc_throw(DhcpConfigError, "Failed to parse pool definition:"
+ << text_pool->stringValue() <<
+ ". Does not contain - (for min-max) nor / (prefix/len)");
+ }
+}
+
+void
+PoolParser::commit() {
+ if (pools_) {
+ // local_pools_ holds the values produced by the build function.
+ // At this point parsing should have completed successfuly so
+ // we can append new data to the supplied storage.
+ pools_->insert(pools_->end(), local_pools_.begin(), local_pools_.end());
+ }
+}
+
+//****************************** SubnetConfigParser *************************
+
+SubnetConfigParser::SubnetConfigParser(const std::string&,
+ ParserContextPtr global_context)
+ : uint32_values_(new Uint32Storage()), string_values_(new StringStorage()),
+ pools_(new PoolStorage()), options_(new OptionStorage()),
+ global_context_(global_context) {
+ // The first parameter should always be "subnet", but we don't check
+ // against that here in case some wants to reuse this parser somewhere.
+ if (!global_context_) {
+ isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
+ << "context storage may not be NULL");
+ }
+}
+
+void
+SubnetConfigParser::build(ConstElementPtr subnet) {
+ BOOST_FOREACH(ConfigPair param, subnet->mapValue()) {
+ ParserPtr parser(createSubnetConfigParser(param.first));
+ parser->build(param.second);
+ parsers_.push_back(parser);
+ }
+
+ // In order to create new subnet we need to get the data out
+ // of the child parsers first. The only way to do it is to
+ // invoke commit on them because it will make them write
+ // parsed data into storages we have supplied.
+ // Note that triggering commits on child parsers does not
+ // affect global data because we supplied pointers to storages
+ // local to this object. Thus, even if this method fails
+ // later on, the configuration remains consistent.
+ BOOST_FOREACH(ParserPtr parser, parsers_) {
+ parser->commit();
+ }
+
+ // Create a subnet.
+ createSubnet();
+}
+
+void
+SubnetConfigParser::appendSubOptions(const std::string& option_space,
+ OptionPtr& option) {
+ // Only non-NULL options are stored in option container.
+ // If this option pointer is NULL this is a serious error.
+ assert(option);
+
+ OptionDefinitionPtr def;
+ if (isServerStdOption(option_space, option->getType())) {
+ def = getServerStdOptionDefinition(option->getType());
+ // Definitions for some of the standard options hasn't been
+ // implemented so it is ok to leave here.
+ if (!def) {
+ return;
+ }
+ } else {
+ const OptionDefContainerPtr defs =
+ global_context_->option_defs_->getItems(option_space);
+
+ const OptionDefContainerTypeIndex& idx = defs->get<1>();
+ const OptionDefContainerTypeRange& range =
+ idx.equal_range(option->getType());
+ // There is no definition so we have to leave.
+ if (std::distance(range.first, range.second) == 0) {
+ return;
+ }
+
+ def = *range.first;
+
+ // If the definition exists, it must be non-NULL.
+ // Otherwise it is a programming error.
+ assert(def);
+ }
+
+ // We need to get option definition for the particular option space
+ // and code. This definition holds the information whether our
+ // option encapsulates any option space.
+ // Get the encapsulated option space name.
+ std::string encapsulated_space = def->getEncapsulatedSpace();
+ // If option space name is empty it means that our option does not
+ // encapsulate any option space (does not include sub-options).
+ if (!encapsulated_space.empty()) {
+ // Get the sub-options that belong to the encapsulated
+ // option space.
+ const Subnet::OptionContainerPtr sub_opts =
+ global_context_->options_->getItems(encapsulated_space);
+ // Append sub-options to the option.
+ BOOST_FOREACH(Subnet::OptionDescriptor desc, *sub_opts) {
+ if (desc.option) {
+ option->addOption(desc.option);
+ }
+ }
+ }
+}
+
+void
+SubnetConfigParser::createSubnet() {
+ std::string subnet_txt;
+ try {
+ subnet_txt = string_values_->getParam("subnet");
+ } catch (const DhcpConfigError &) {
+ // rethrow with precise error
+ isc_throw(DhcpConfigError,
+ "Mandatory subnet definition in subnet missing");
+ }
+
+ // Remove any spaces or tabs.
+ boost::erase_all(subnet_txt, " ");
+ boost::erase_all(subnet_txt, "\t");
+
+ // The subnet format is prefix/len. We are going to extract
+ // the prefix portion of a subnet string to create IOAddress
+ // object from it. IOAddress will be passed to the Subnet's
+ // constructor later on. In order to extract the prefix we
+ // need to get all characters preceding "/".
+ size_t pos = subnet_txt.find("/");
+ if (pos == string::npos) {
+ isc_throw(DhcpConfigError,
+ "Invalid subnet syntax (prefix/len expected):" << subnet_txt);
+ }
+
+ // Try to create the address object. It also validates that
+ // the address syntax is ok.
+ isc::asiolink::IOAddress addr(subnet_txt.substr(0, pos));
+ uint8_t len = boost::lexical_cast<unsigned int>(subnet_txt.substr(pos + 1));
+
+ // Call the subclass's method to instantiate the subnet
+ initSubnet(addr, len);
+
+ // Add pools to it.
+ for (PoolStorage::iterator it = pools_->begin(); it != pools_->end();
+ ++it) {
+ subnet_->addPool(*it);
+ }
+
+ // Configure interface, if defined
+
+ // Get interface name. If it is defined, then the subnet is available
+ // directly over specified network interface.
+ std::string iface;
+ try {
+ iface = string_values_->getParam("interface");
+ } catch (const DhcpConfigError &) {
+ // iface not mandatory so swallow the exception
+ }
+
+ if (!iface.empty()) {
+ if (!IfaceMgr::instance().getIface(iface)) {
+ isc_throw(DhcpConfigError, "Specified interface name " << iface
+ << " for subnet " << subnet_->toText()
+ << " is not present" << " in the system.");
+ }
+
+ subnet_->setIface(iface);
+ }
+
+ // We are going to move configured options to the Subnet object.
+ // Configured options reside in the container where options
+ // are grouped by space names. Thus we need to get all space names
+ // and iterate over all options that belong to them.
+ std::list<std::string> space_names = options_->getOptionSpaceNames();
+ BOOST_FOREACH(std::string option_space, space_names) {
+ // Get all options within a particular option space.
+ BOOST_FOREACH(Subnet::OptionDescriptor desc,
+ *options_->getItems(option_space)) {
+ // The pointer should be non-NULL. The validation is expected
+ // to be performed by the OptionDataParser before adding an
+ // option descriptor to the container.
+ assert(desc.option);
+ // We want to check whether an option with the particular
+ // option code has been already added. If so, we want
+ // to issue a warning.
+ Subnet::OptionDescriptor existing_desc =
+ subnet_->getOptionDescriptor("option_space",
+ desc.option->getType());
+ if (existing_desc.option) {
+ duplicate_option_warning(desc.option->getType(), addr);
+ }
+ // Add sub-options (if any).
+ appendSubOptions(option_space, desc.option);
+ // In any case, we add the option to the subnet.
+ subnet_->addOption(desc.option, false, option_space);
+ }
+ }
+
+ // Check all global options and add them to the subnet object if
+ // they have been configured in the global scope. If they have been
+ // configured in the subnet scope we don't add global option because
+ // the one configured in the subnet scope always takes precedence.
+ space_names = global_context_->options_->getOptionSpaceNames();
+ BOOST_FOREACH(std::string option_space, space_names) {
+ // Get all global options for the particular option space.
+ BOOST_FOREACH(Subnet::OptionDescriptor desc,
+ *(global_context_->options_->getItems(option_space))) {
+ // The pointer should be non-NULL. The validation is expected
+ // to be performed by the OptionDataParser before adding an
+ // option descriptor to the container.
+ assert(desc.option);
+ // Check if the particular option has been already added.
+ // This would mean that it has been configured in the
+ // subnet scope. Since option values configured in the
+ // subnet scope take precedence over globally configured
+ // values we don't add option from the global storage
+ // if there is one already.
+ Subnet::OptionDescriptor existing_desc =
+ subnet_->getOptionDescriptor(option_space,
+ desc.option->getType());
+ if (!existing_desc.option) {
+ // Add sub-options (if any).
+ appendSubOptions(option_space, desc.option);
+ subnet_->addOption(desc.option, false, option_space);
+ }
+ }
+ }
+}
+
+isc::dhcp::Triplet<uint32_t>
+SubnetConfigParser::getParam(const std::string& name) {
+ uint32_t value = 0;
+ try {
+ // look for local value
+ value = uint32_values_->getParam(name);
+ } catch (const DhcpConfigError &) {
+ try {
+ // no local, use global value
+ value = global_context_->uint32_values_->getParam(name);
+ } catch (const DhcpConfigError &) {
+ isc_throw(DhcpConfigError, "Mandatory parameter " << name
+ << " missing (no global default and no subnet-"
+ << "specific value)");
+ }
+ }
+
+ return (Triplet<uint32_t>(value));
+}
+
+}; // namespace dhcp
+}; // namespace isc
diff --git a/src/lib/dhcpsrv/dhcp_parsers.h b/src/lib/dhcpsrv/dhcp_parsers.h
new file mode 100644
index 0000000..e453204
--- /dev/null
+++ b/src/lib/dhcpsrv/dhcp_parsers.h
@@ -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.
+
+#ifndef DHCP_PARSERS_H
+#define DHCP_PARSERS_H
+
+#include <asiolink/io_address.h>
+#include <cc/data.h>
+#include <dhcp/option_definition.h>
+#include <dhcpsrv/dhcp_config_parser.h>
+#include <dhcpsrv/option_space_container.h>
+#include <dhcpsrv/subnet.h>
+#include <exceptions/exceptions.h>
+
+#include <stdint.h>
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Storage for option definitions.
+typedef OptionSpaceContainer<OptionDefContainer,
+ OptionDefinitionPtr> OptionDefStorage;
+
+/// @brief Shared pointer to option definitions storage.
+typedef boost::shared_ptr<OptionDefStorage> OptionDefStoragePtr;
+
+/// Collection of containers holding option spaces. Each container within
+/// a particular option space holds so-called option descriptors.
+typedef OptionSpaceContainer<Subnet::OptionContainer,
+ Subnet::OptionDescriptor> OptionStorage;
+/// @brief Shared pointer to option storage.
+typedef boost::shared_ptr<OptionStorage> OptionStoragePtr;
+
+/// @brief A template class that stores named elements of a given data type.
+///
+/// This template class is provides data value storage for configuration parameters
+/// of a given data type. The values are stored by parameter name and as instances
+/// of type "ValueType".
+///
+/// @param ValueType is the data type of the elements to store.
+template<typename ValueType>
+class ValueStorage {
+ public:
+ /// @brief Stores the the parameter and its value in the store.
+ ///
+ /// If the parameter does not exist in the store, then it will be added,
+ /// otherwise its data value will be updated with the given value.
+ ///
+ /// @param name is the name of the paramater to store.
+ /// @param value is the data value to store.
+ void setParam(const std::string& name, const ValueType& value) {
+ values_[name] = value;
+ }
+
+ /// @brief Returns the data value for the given parameter.
+ ///
+ /// Finds and returns the data value for the given parameter.
+ /// @param name is the name of the parameter for which the data
+ /// value is desired.
+ ///
+ /// @return The paramater's data value of type <ValueType>.
+ /// @throw DhcpConfigError if the parameter is not found.
+ ValueType getParam(const std::string& name) const {
+ typename std::map<std::string, ValueType>::const_iterator param
+ = values_.find(name);
+
+ if (param == values_.end()) {
+ isc_throw(DhcpConfigError, "Missing parameter '"
+ << name << "'");
+ }
+
+ return (param->second);
+ }
+
+ /// @brief Remove the parameter from the store.
+ ///
+ /// Deletes the entry for the given parameter from the store if it
+ /// exists.
+ ///
+ /// @param name is the name of the paramater to delete.
+ void delParam(const std::string& name) {
+ values_.erase(name);
+ }
+
+ /// @brief Deletes all of the entries from the store.
+ ///
+ void clear() {
+ values_.clear();
+ }
+
+
+ private:
+ /// @brief An std::map of the data values, keyed by parameter names.
+ std::map<std::string, ValueType> values_;
+};
+
+
+/// @brief a collection of elements that store uint32 values
+typedef ValueStorage<uint32_t> Uint32Storage;
+typedef boost::shared_ptr<Uint32Storage> Uint32StoragePtr;
+
+/// @brief a collection of elements that store string values
+typedef ValueStorage<std::string> StringStorage;
+typedef boost::shared_ptr<StringStorage> StringStoragePtr;
+
+/// @brief Storage for parsed boolean values.
+typedef ValueStorage<bool> BooleanStorage;
+typedef boost::shared_ptr<BooleanStorage> BooleanStoragePtr;
+
+/// @brief Container for the current parsing context. It provides a
+/// single enclosure for the storage of configuration parameters,
+/// options, option definitions, and other context specific information
+/// that needs to be accessible throughout the parsing and parsing
+/// constructs.
+class ParserContext {
+public:
+ /// @brief Constructor
+ ///
+ /// @param universe is the Option::Universe value of this
+ /// context.
+ ParserContext(Option::Universe universe);
+
+ /// @brief Copy constructor
+ ParserContext(const ParserContext& rhs);
+
+ /// @brief Storage for boolean parameters.
+ BooleanStoragePtr boolean_values_;
+
+ /// @brief Storage for uint32 parameters.
+ Uint32StoragePtr uint32_values_;
+
+ /// @brief Storage for string parameters.
+ StringStoragePtr string_values_;
+
+ /// @brief Storage for options.
+ OptionStoragePtr options_;
+
+ /// @brief Storage for option definitions.
+ OptionDefStoragePtr option_defs_;
+
+ /// @brief The parsing universe of this context.
+ Option::Universe universe_;
+
+ /// @brief Assignment operator
+ ParserContext& operator=(const ParserContext& rhs);
+};
+
+/// @brief Pointer to various parser context.
+typedef boost::shared_ptr<ParserContext> ParserContextPtr;
+
+/// @brief Simple data-type parser template class
+///
+/// This is the template class for simple data-type parsers. It supports
+/// parsing a configuration parameter with specific data-type for its
+/// possible values. It provides a common constructor, commit, and templated
+/// data storage. The "build" method implementation must be provided by a
+/// declaring type.
+/// @param ValueType is the data type of the configuration paramater value
+/// the parser should handle.
+template<typename ValueType>
+class ValueParser : public DhcpConfigParser {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param param_name name of the parameter.
+ /// @param storage is a pointer to the storage container where the parsed
+ /// value be stored upon commit.
+ /// @throw isc::dhcp::DhcpConfigError if a provided parameter's
+ /// name is empty.
+ /// @throw isc::dhcp::DhcpConfigError if storage is null.
+ ValueParser(const std::string& param_name,
+ boost::shared_ptr<ValueStorage<ValueType> > storage)
+ : storage_(storage), param_name_(param_name), value_() {
+ // Empty parameter name is invalid.
+ if (param_name_.empty()) {
+ isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
+ << "empty parameter name provided");
+ }
+
+ // NUll storage is invalid.
+ if (!storage_) {
+ isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
+ << "storage may not be NULL");
+ }
+ }
+
+
+ /// @brief Parse a given element into a value of type <ValueType>
+ ///
+ /// @param value a value to be parsed.
+ ///
+ /// @throw isc::BadValue Typically the implementing type will throw
+ /// a BadValue exception when given an invalid Element to parse.
+ void build(isc::data::ConstElementPtr value);
+
+ /// @brief Put a parsed value to the storage.
+ void commit() {
+ // If a given parameter already exists in the storage we override
+ // its value. If it doesn't we insert a new element.
+ storage_->setParam(param_name_, value_);
+ }
+
+private:
+ /// Pointer to the storage where committed value is stored.
+ boost::shared_ptr<ValueStorage<ValueType> > storage_;
+
+ /// Name of the parameter which value is parsed with this parser.
+ std::string param_name_;
+
+ /// Parsed value.
+ ValueType value_;
+};
+
+/// @brief typedefs for simple data type parsers
+typedef ValueParser<bool> BooleanParser;
+typedef ValueParser<uint32_t> Uint32Parser;
+typedef ValueParser<std::string> StringParser;
+
+/// @brief a dummy configuration parser
+///
+/// It is a debugging parser. It does not configure anything,
+/// will accept any configuration and will just print it out
+/// on commit. Useful for debugging existing configurations and
+/// adding new ones.
+class DebugParser : public DhcpConfigParser {
+public:
+
+ /// @brief Constructor
+ ///
+ /// See @ref DhcpConfigParser class for details.
+ ///
+ /// @param param_name name of the parsed parameter
+ DebugParser(const std::string& param_name);
+
+ /// @brief builds parameter value
+ ///
+ /// See @ref DhcpConfigParser class for details.
+ ///
+ /// @param new_config pointer to the new configuration
+ virtual void build(isc::data::ConstElementPtr new_config);
+
+ /// @brief pretends to apply the configuration
+ ///
+ /// This is a method required by base class. It pretends to apply the
+ /// configuration, but in fact it only prints the parameter out.
+ ///
+ /// See @ref DhcpConfigParser class for details.
+ virtual void commit();
+
+private:
+ /// name of the parsed parameter
+ std::string param_name_;
+
+ /// pointer to the actual value of the parameter
+ isc::data::ConstElementPtr value_;
+
+};
+
+/// @brief parser for interface list definition
+///
+/// This parser handles Dhcp4/interface entry.
+/// It contains a list of network interfaces that the server listens on.
+/// In particular, it can contain an entry called "all" or "any" that
+/// designates all interfaces.
+///
+/// It is useful for parsing Dhcp4/interface parameter.
+class InterfaceListConfigParser : public DhcpConfigParser {
+public:
+
+ /// @brief constructor
+ ///
+ /// As this is a dedicated parser, it must be used to parse
+ /// "interface" parameter only. All other types will throw exception.
+ ///
+ /// @param param_name name of the configuration parameter being parsed
+ /// @throw BadValue if supplied parameter name is not "interface"
+ InterfaceListConfigParser(const std::string& param_name);
+
+ /// @brief parses parameters value
+ ///
+ /// Parses configuration entry (list of parameters) and adds each element
+ /// to the interfaces list.
+ ///
+ /// @param value pointer to the content of parsed values
+ virtual void build(isc::data::ConstElementPtr value);
+
+ /// @brief commits interfaces list configuration
+ virtual void commit();
+
+private:
+ /// contains list of network interfaces
+ std::vector<std::string> interfaces_;
+};
+
+
+/// @brief Parser for option data value.
+///
+/// This parser parses configuration entries that specify value of
+/// a single option. These entries include option name, option code
+/// and data carried by the option. The option data can be specified
+/// in one of the two available formats: binary value represented as
+/// a string of hexadecimal digits or a list of comma separated values.
+/// The format being used is controlled by csv-format configuration
+/// parameter. When setting this value to True, the latter format is
+/// used. The subsequent values in the CSV format apply to relevant
+/// option data fields in the configured option. For example the
+/// configuration: "data" : "192.168.2.0, 56, hello world" can be
+/// used to set values for the option comprising IPv4 address,
+/// integer and string data field. Note that order matters. If the
+/// order of values does not match the order of data fields within
+/// an option the configuration will not be accepted. If parsing
+/// is successful then an instance of an option is created and
+/// added to the storage provided by the calling class.
+class OptionDataParser : public DhcpConfigParser {
+public:
+ /// @brief Constructor.
+ ///
+ /// @param dummy first argument is ignored, all Parser constructors
+ /// accept string as first argument.
+ /// @param options is the option storage in which to store the parsed option
+ /// upon "commit".
+ /// @param global_context is a pointer to the global context which
+ /// stores global scope parameters, options, option defintions.
+ /// @throw isc::dhcp::DhcpConfigError if options or global_context are null.
+ OptionDataParser(const std::string&, OptionStoragePtr options,
+ ParserContextPtr global_context);
+
+ /// @brief Parses the single option data.
+ ///
+ /// This method parses the data of a single option from the configuration.
+ /// The option data includes option name, option code and data being
+ /// carried by this option. Eventually it creates the instance of the
+ /// option.
+ ///
+ /// @param option_data_entries collection of entries that define value
+ /// for a particular option.
+ /// @throw DhcpConfigError if invalid parameter specified in
+ /// the configuration.
+ /// @throw isc::InvalidOperation if failed to set storage prior to
+ /// calling build.
+ virtual void build(isc::data::ConstElementPtr option_data_entries);
+
+ /// @brief Commits option value.
+ ///
+ /// This function adds a new option to the storage or replaces an existing
+ /// option with the same code.
+ ///
+ /// @throw isc::InvalidOperation if failed to set pointer to storage or
+ /// failed
+ /// to call build() prior to commit. If that happens data in the storage
+ /// remain un-modified.
+ virtual void commit();
+
+ /// @brief virtual destructor to ensure orderly destruction of derivations.
+ virtual ~OptionDataParser(){};
+
+protected:
+ /// @brief Finds an option definition within the server's option space
+ ///
+ /// Given an option space and an option code, find the correpsonding
+ /// option defintion within the server's option defintion storage. This
+ /// method is pure virtual requiring derivations to manage which option
+ /// space(s) is valid for search.
+ ///
+ /// @param option_space name of the parameter option space
+ /// @param option_code numeric value of the parameter to find
+ /// @return OptionDefintionPtr of the option defintion or an
+ /// empty OptionDefinitionPtr if not found.
+ /// @throw DhcpConfigError if the option space requested is not valid
+ /// for this server.
+ virtual OptionDefinitionPtr findServerSpaceOptionDefinition (
+ std::string& option_space, uint32_t option_code) = 0;
+
+private:
+
+ /// @brief Create option instance.
+ ///
+ /// Creates an instance of an option and adds it to the provided
+ /// options storage. If the option data parsed by \ref build function
+ /// are invalid or insufficient this function emits an exception.
+ ///
+ /// @warning this function does not check if options_ storage pointer
+ /// is intitialized but this check is not needed here because it is done
+ /// in the \ref build function.
+ ///
+ /// @throw DhcpConfigError if parameters provided in the configuration
+ /// are invalid.
+ void createOption();
+
+ /// Storage for boolean values.
+ BooleanStoragePtr boolean_values_;
+
+ /// Storage for string values (e.g. option name or data).
+ StringStoragePtr string_values_;
+
+ /// Storage for uint32 values (e.g. option code).
+ Uint32StoragePtr uint32_values_;
+
+ /// Pointer to options storage. This storage is provided by
+ /// the calling class and is shared by all OptionDataParser objects.
+ OptionStoragePtr options_;
+
+ /// Option descriptor holds newly configured option.
+ Subnet::OptionDescriptor option_descriptor_;
+
+ /// Option space name where the option belongs to.
+ std::string option_space_;
+
+ /// Parsing context which contains global values, options and option
+ /// definitions.
+ ParserContextPtr global_context_;
+};
+
+///@brief Function pointer for OptionDataParser factory methods
+typedef OptionDataParser *OptionDataParserFactory(const std::string&,
+ OptionStoragePtr options, ParserContextPtr global_context);
+
+/// @brief Parser for option data values within a subnet.
+///
+/// This parser iterates over all entries that define options
+/// data for a particular subnet and creates a collection of options.
+/// If parsing is successful, all these options are added to the Subnet
+/// object.
+class OptionDataListParser : public DhcpConfigParser {
+public:
+ /// @brief Constructor.
+ ///
+ /// @param string& nominally would be param name, this is always ignored.
+ /// @param options parsed option storage for options in this list
+ /// @param global_context is a pointer to the global context which
+ /// stores global scope parameters, options, option defintions.
+ /// @param optionDataParserFactory factory method for creating individual
+ /// option parsers
+ /// @throw isc::dhcp::DhcpConfigError if options or global_context are null.
+ OptionDataListParser(const std::string&, OptionStoragePtr options,
+ ParserContextPtr global_context,
+ OptionDataParserFactory *optionDataParserFactory);
+
+ /// @brief Parses entries that define options' data for a subnet.
+ ///
+ /// This method iterates over all entries that define option data
+ /// for options within a single subnet and creates options' instances.
+ ///
+ /// @param option_data_list pointer to a list of options' data sets.
+ /// @throw DhcpConfigError if option parsing failed.
+ void build(isc::data::ConstElementPtr option_data_list);
+
+ /// @brief Commit all option values.
+ ///
+ /// This function invokes commit for all option values.
+ void commit();
+
+private:
+ /// Pointer to options instances storage.
+ OptionStoragePtr options_;
+
+ /// Intermediate option storage. This storage is used by
+ /// lower level parsers to add new options. Values held
+ /// in this storage are assigned to main storage (options_)
+ /// if overall parsing was successful.
+ OptionStoragePtr local_options_;
+
+ /// Collection of parsers;
+ ParserCollection parsers_;
+
+ /// Parsing context which contains global values, options and option
+ /// definitions.
+ ParserContextPtr global_context_;
+
+ /// Factory to create server-specific option data parsers
+ OptionDataParserFactory *optionDataParserFactory_;
+};
+
+
+/// @brief Parser for a single option definition.
+///
+/// This parser creates an instance of a single option definition.
+class OptionDefParser : public DhcpConfigParser {
+public:
+ /// @brief Constructor.
+ ///
+ /// @param dummy first argument is ignored, all Parser constructors
+ /// accept string as first argument.
+ /// @param storage is the definition storage in which to store the parsed
+ /// definition upon "commit".
+ /// @throw isc::dhcp::DhcpConfigError if storage is null.
+ OptionDefParser(const std::string&, OptionDefStoragePtr storage);
+
+ /// @brief Parses an entry that describes single option definition.
+ ///
+ /// @param option_def a configuration entry to be parsed.
+ ///
+ /// @throw DhcpConfigError if parsing was unsuccessful.
+ void build(isc::data::ConstElementPtr option_def);
+
+ /// @brief Stores the parsed option definition in a storage.
+ void commit();
+
+private:
+
+ /// @brief Create option definition from the parsed parameters.
+ void createOptionDef();
+
+ /// Instance of option definition being created by this parser.
+ OptionDefinitionPtr option_definition_;
+ /// Name of the space the option definition belongs to.
+ std::string option_space_name_;
+
+ /// Pointer to a storage where the option definition will be
+ /// added when \ref commit is called.
+ OptionDefStoragePtr storage_;
+
+ /// Storage for boolean values.
+ BooleanStoragePtr boolean_values_;
+
+ /// Storage for string values.
+ StringStoragePtr string_values_;
+
+ /// Storage for uint32 values.
+ Uint32StoragePtr uint32_values_;
+};
+
+/// @brief Parser for a list of option definitions.
+///
+/// This parser iterates over all configuration entries that define
+/// option definitions and creates instances of these definitions.
+/// If the parsing is successful, the collection of created definitions
+/// is put into the provided storage.
+class OptionDefListParser : public DhcpConfigParser {
+public:
+ /// @brief Constructor.
+ ///
+ /// @param dummy first argument is ignored, all Parser constructors
+ /// accept string as first argument.
+ /// @param storage is the definition storage in which to store the parsed
+ /// definitions in this list
+ /// @throw isc::dhcp::DhcpConfigError if storage is null.
+ OptionDefListParser(const std::string&, OptionDefStoragePtr storage);
+
+ /// @brief Parse configuration entries.
+ ///
+ /// This function parses configuration entries and creates instances
+ /// of option definitions.
+ ///
+ /// @param option_def_list pointer to an element that holds entries
+ /// that define option definitions.
+ /// @throw DhcpConfigError if configuration parsing fails.
+ void build(isc::data::ConstElementPtr option_def_list);
+
+ /// @brief Stores option definitions in the CfgMgr.
+ void commit();
+
+private:
+ /// @brief storage for option definitions.
+ OptionDefStoragePtr storage_;
+};
+
+/// @brief a collection of pools
+///
+/// That type is used as intermediate storage, when pools are parsed, but there is
+/// no subnet object created yet to store them.
+typedef std::vector<PoolPtr> PoolStorage;
+typedef boost::shared_ptr<PoolStorage> PoolStoragePtr;
+
+/// @brief parser for pool definition
+///
+/// This abstract parser handles pool definitions, i.e. a list of entries of one
+/// of two syntaxes: min-max and prefix/len. Pool objects are created
+/// and stored in chosen PoolStorage container.
+///
+/// It is useful for parsing Dhcp<4/6>/subnet<4/6>[X]/pool parameters.
+class PoolParser : public DhcpConfigParser {
+public:
+
+ /// @brief constructor.
+
+
+ /// @param dummy first argument is ignored, all Parser constructors
+ /// accept string as first argument.
+ /// @param pools is the storage in which to store the parsed pool
+ /// upon "commit".
+ /// @throw isc::dhcp::DhcpConfigError if storage is null.
+ PoolParser(const std::string&, PoolStoragePtr pools);
+
+ /// @brief parses the actual list
+ ///
+ /// This method parses the actual list of interfaces.
+ /// No validation is done at this stage, everything is interpreted as
+ /// interface name.
+ /// @param pools_list list of pools defined for a subnet
+ /// @throw isc::dhcp::DhcpConfigError when pool parsing fails
+ virtual void build(isc::data::ConstElementPtr pools_list);
+
+ /// @brief Stores the parsed values in a storage provided
+ /// by an upper level parser.
+ virtual void commit();
+
+protected:
+ /// @brief Creates a Pool object given a IPv4 prefix and the prefix length.
+ ///
+ /// @param addr is the IP prefix of the pool.
+ /// @param len is the prefix length.
+ /// @param ignored dummy parameter to provide symmetry between
+ /// @return returns a PoolPtr to the new Pool object.
+ virtual PoolPtr poolMaker(isc::asiolink::IOAddress &addr, uint32_t len,
+ int32_t ptype=0) = 0;
+
+ /// @brief Creates a Pool object given starting and ending IP addresses.
+ ///
+ /// @param min is the first IP address in the pool.
+ /// @param max is the last IP address in the pool.
+ /// @param ptype is the type of pool to create (not used by all derivations)
+ /// @return returns a PoolPtr to the new Pool object.
+ virtual PoolPtr poolMaker(isc::asiolink::IOAddress &min,
+ isc::asiolink::IOAddress &max, int32_t ptype=0) = 0;
+
+ /// @brief pointer to the actual Pools storage
+ ///
+ /// That is typically a storage somewhere in Subnet parser
+ /// (an upper level parser).
+ PoolStoragePtr pools_;
+
+ /// A temporary storage for pools configuration. It is a
+ /// storage where pools are stored by build function.
+ PoolStorage local_pools_;
+};
+
+/// @brief this class parses a single subnet
+///
+/// This class parses the whole subnet definition. It creates parsers
+/// for received configuration parameters as needed.
+class SubnetConfigParser : public DhcpConfigParser {
+public:
+
+ /// @brief constructor
+ SubnetConfigParser(const std::string&, ParserContextPtr global_context);
+
+ /// @brief parses parameter value
+ ///
+ /// @param subnet pointer to the content of subnet definition
+ ///
+ /// @throw isc::DhcpConfigError if subnet configuration parsing failed.
+ virtual void build(isc::data::ConstElementPtr subnet);
+
+ /// @brief Adds the created subnet to a server's configuration.
+ virtual void commit() = 0;
+
+protected:
+ /// @brief creates parsers for entries in subnet definition
+ ///
+ /// @param config_id name od the entry
+ ///
+ /// @return parser object for specified entry name
+ /// @throw isc::dhcp::DhcpConfigError if trying to create a parser
+ /// for unknown config element
+ virtual DhcpConfigParser* createSubnetConfigParser(
+ const std::string& config_id) = 0;
+
+ /// @brief Determines if the given option space name and code describe
+ /// a standard option for the server.
+ ///
+ /// @param option_space is the name of the option space to consider
+ /// @param code is the numeric option code to consider
+ /// @return returns true if the space and code are part of the server's
+ /// standard options.
+ virtual bool isServerStdOption(std::string option_space, uint32_t code) = 0;
+
+ /// @brief Returns the option definition for a given option code from
+ /// the server's standard set of options.
+ /// @param code is the numeric option code of the desired option definition.
+ /// @return returns a pointer the option definition
+ virtual OptionDefinitionPtr getServerStdOptionDefinition (
+ uint32_t code) = 0;
+
+ /// @brief Issues a server specific warning regarding duplicate subnet
+ /// options.
+ ///
+ /// @param code is the numeric option code of the duplicate option
+ /// @param addr is the subnet address
+ /// @todo a means to know the correct logger and perhaps a common
+ /// message would allow this method to be emitted by the base class.
+ virtual void duplicate_option_warning(uint32_t code,
+ isc::asiolink::IOAddress& addr) = 0;
+
+ /// @brief Instantiates the subnet based on a given IP prefix and prefix
+ /// length.
+ ///
+ /// @param addr is the IP prefix of the subnet.
+ /// @param len is the prefix length
+ virtual void initSubnet(isc::asiolink::IOAddress addr, uint8_t len) = 0;
+
+ /// @brief Returns value for a given parameter (after using inheritance)
+ ///
+ /// This method implements inheritance. For a given parameter name, it first
+ /// checks if there is a global value for it and overwrites it with specific
+ /// value if such value was defined in subnet.
+ ///
+ /// @param name name of the parameter
+ /// @return triplet with the parameter name
+ /// @throw DhcpConfigError when requested parameter is not present
+ isc::dhcp::Triplet<uint32_t> getParam(const std::string& name);
+
+private:
+
+ /// @brief Append sub-options to an option.
+ ///
+ /// @param option_space a name of the encapsulated option space.
+ /// @param option option instance to append sub-options to.
+ void appendSubOptions(const std::string& option_space, OptionPtr& option);
+
+ /// @brief Create a new subnet using a data from child parsers.
+ ///
+ /// @throw isc::dhcp::DhcpConfigError if subnet configuration parsing
+ /// failed.
+ void createSubnet();
+
+protected:
+
+ /// Storage for subnet-specific integer values.
+ Uint32StoragePtr uint32_values_;
+
+ /// Storage for subnet-specific string values.
+ StringStoragePtr string_values_;
+
+ /// Storage for pools belonging to this subnet.
+ PoolStoragePtr pools_;
+
+ /// Storage for options belonging to this subnet.
+ OptionStoragePtr options_;
+
+ /// Parsers are stored here.
+ ParserCollection parsers_;
+
+ /// Pointer to the created subnet object.
+ isc::dhcp::SubnetPtr subnet_;
+
+ /// Parsing context which contains global values, options and option
+ /// definitions.
+ ParserContextPtr global_context_;
+};
+
+// Pointers to various parser objects.
+typedef boost::shared_ptr<BooleanParser> BooleanParserPtr;
+typedef boost::shared_ptr<StringParser> StringParserPtr;
+typedef boost::shared_ptr<Uint32Parser> Uint32ParserPtr;
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
+
+#endif // DHCP_PARSERS_H
+
diff --git a/src/lib/dhcpsrv/dhcpsrv_messages.mes b/src/lib/dhcpsrv/dhcpsrv_messages.mes
index 9b8a0ce..b2a7807 100644
--- a/src/lib/dhcpsrv/dhcpsrv_messages.mes
+++ b/src/lib/dhcpsrv/dhcpsrv_messages.mes
@@ -105,7 +105,14 @@ This is a debug message reporting that the DHCP configuration manager
has returned the specified IPv6 subnet for a packet received over
given interface. This particular subnet was selected, because it
was specified as being directly reachable over given interface. (see
-'interface' parameter in subnet6 definition).
+'interface' parameter in the subnet6 definition).
+
+% DHCPSRV_CFGMGR_SUBNET6_IFACE_ID selected subnet %1 (interface-id match) for incoming packet
+This is a debug message reporting that the DHCP configuration manager
+has returned the specified IPv6 subnet for a received packet. This particular
+subnet was selected, because value of interface-id option matched what was
+configured in server's interface-id option for that selected subnet6.
+(see 'interface-id' parameter in the subnet6 definition).
% DHCPSRV_CLOSE_DB closing currently open %1 database
This is a debug message, issued when the DHCP server closes the currently
diff --git a/src/lib/dhcpsrv/lease_mgr.h b/src/lib/dhcpsrv/lease_mgr.h
index b6f7001..02e517e 100644
--- a/src/lib/dhcpsrv/lease_mgr.h
+++ b/src/lib/dhcpsrv/lease_mgr.h
@@ -211,8 +211,6 @@ struct Lease {
/// would be required. As this is a critical part of the code that will be used
/// extensively, direct access is warranted.
struct Lease4 : public Lease {
- /// @brief Maximum size of a hardware address
- static const size_t HWADDR_MAX = 20;
/// @brief Address extension
///
@@ -249,8 +247,10 @@ struct Lease4 : public Lease {
const uint8_t* clientid, size_t clientid_len, uint32_t valid_lft,
uint32_t t1, uint32_t t2, time_t cltt, uint32_t subnet_id)
: Lease(addr, t1, t2, valid_lft, subnet_id, cltt),
- ext_(0), hwaddr_(hwaddr, hwaddr + hwaddr_len),
- client_id_(new ClientId(clientid, clientid_len)) {
+ ext_(0), hwaddr_(hwaddr, hwaddr + hwaddr_len) {
+ if (clientid_len) {
+ client_id_.reset(new ClientId(clientid, clientid_len));
+ }
}
/// @brief Default constructor
diff --git a/src/lib/dhcpsrv/mysql_lease_mgr.cc b/src/lib/dhcpsrv/mysql_lease_mgr.cc
index 6b6cde5..9828085 100644
--- a/src/lib/dhcpsrv/mysql_lease_mgr.cc
+++ b/src/lib/dhcpsrv/mysql_lease_mgr.cc
@@ -21,7 +21,7 @@
#include <dhcpsrv/mysql_lease_mgr.h>
#include <boost/static_assert.hpp>
-#include <mysql/mysqld_error.h>
+#include <mysqld_error.h>
#include <iostream>
#include <iomanip>
@@ -94,9 +94,6 @@ namespace {
/// colon separators.
const size_t ADDRESS6_TEXT_MAX_LEN = 39;
-/// @brief Maximum size of a hardware address.
-const size_t HWADDR_MAX_LEN = 20;
-
/// @brief MySQL True/False constants
///
/// Declare typed values so as to avoid problems of data conversion. These
@@ -289,7 +286,7 @@ public:
memset(hwaddr_buffer_, 0, sizeof(hwaddr_buffer_));
memset(client_id_buffer_, 0, sizeof(client_id_buffer_));
std::fill(&error_[0], &error_[LEASE_COLUMNS], MLM_FALSE);
-
+
// Set the column names (for error messages)
columns_[0] = "address";
columns_[1] = "hwaddr";
@@ -315,6 +312,10 @@ public:
lease_ = lease;
// Initialize prior to constructing the array of MYSQL_BIND structures.
+ // It sets all fields, including is_null, to zero, so we need to set
+ // is_null only if it should be true. This gives up minor performance
+ // benefit while being safe approach. For improved readability, the
+ // code that explicitly sets is_null is there, but is commented out.
memset(bind_, 0, sizeof(bind_));
// Set up the structures for the various components of the lease4
@@ -327,6 +328,8 @@ public:
bind_[0].buffer_type = MYSQL_TYPE_LONG;
bind_[0].buffer = reinterpret_cast<char*>(&addr4_);
bind_[0].is_unsigned = MLM_TRUE;
+ // bind_[0].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
// hwaddr: varbinary(128)
// For speed, we avoid copying the data into temporary storage and
@@ -336,6 +339,8 @@ public:
bind_[1].buffer = reinterpret_cast<char*>(&(lease_->hwaddr_[0]));
bind_[1].buffer_length = hwaddr_length_;
bind_[1].length = &hwaddr_length_;
+ // bind_[1].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
// client_id: varbinary(128)
if (lease_->client_id_) {
@@ -345,6 +350,8 @@ public:
bind_[2].buffer = reinterpret_cast<char*>(&client_id_[0]);
bind_[2].buffer_length = client_id_length_;
bind_[2].length = &client_id_length_;
+ // bind_[2].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
} else {
bind_[2].buffer_type = MYSQL_TYPE_NULL;
@@ -353,15 +360,17 @@ public:
// fields doesn't matter if type is set to MYSQL_TYPE_NULL,
// but let's set them to some sane values in case earlier versions
// didn't have that assumption.
- static my_bool no_clientid = MLM_TRUE;
+ client_id_null_ = MLM_TRUE;
bind_[2].buffer = NULL;
- bind_[2].is_null = &no_clientid;
+ bind_[2].is_null = &client_id_null_;
}
// valid lifetime: unsigned int
bind_[3].buffer_type = MYSQL_TYPE_LONG;
bind_[3].buffer = reinterpret_cast<char*>(&lease_->valid_lft_);
bind_[3].is_unsigned = MLM_TRUE;
+ // bind_[3].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
// expire: timestamp
// The lease structure holds the client last transmission time (cltt_)
@@ -377,12 +386,16 @@ public:
bind_[4].buffer_type = MYSQL_TYPE_TIMESTAMP;
bind_[4].buffer = reinterpret_cast<char*>(&expire_);
bind_[4].buffer_length = sizeof(expire_);
+ // bind_[4].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
// subnet_id: unsigned int
// Can use lease_->subnet_id_ directly as it is of type uint32_t.
bind_[5].buffer_type = MYSQL_TYPE_LONG;
bind_[5].buffer = reinterpret_cast<char*>(&lease_->subnet_id_);
bind_[5].is_unsigned = MLM_TRUE;
+ // bind_[5].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
// Add the error flags
setErrorIndicators(bind_, error_, LEASE_COLUMNS);
@@ -404,12 +417,18 @@ public:
std::vector<MYSQL_BIND> createBindForReceive() {
// Initialize MYSQL_BIND array.
+ // It sets all fields, including is_null, to zero, so we need to set
+ // is_null only if it should be true. This gives up minor performance
+ // benefit while being safe approach. For improved readability, the
+ // code that explicitly sets is_null is there, but is commented out.
memset(bind_, 0, sizeof(bind_));
// address: uint32_t
bind_[0].buffer_type = MYSQL_TYPE_LONG;
bind_[0].buffer = reinterpret_cast<char*>(&addr4_);
bind_[0].is_unsigned = MLM_TRUE;
+ // bind_[0].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
// hwaddr: varbinary(20)
hwaddr_length_ = sizeof(hwaddr_buffer_);
@@ -417,6 +436,8 @@ public:
bind_[1].buffer = reinterpret_cast<char*>(hwaddr_buffer_);
bind_[1].buffer_length = hwaddr_length_;
bind_[1].length = &hwaddr_length_;
+ // bind_[1].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
// client_id: varbinary(128)
client_id_length_ = sizeof(client_id_buffer_);
@@ -424,21 +445,30 @@ public:
bind_[2].buffer = reinterpret_cast<char*>(client_id_buffer_);
bind_[2].buffer_length = client_id_length_;
bind_[2].length = &client_id_length_;
+ bind_[2].is_null = &client_id_null_;
+ // bind_[2].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
// lease_time: unsigned int
bind_[3].buffer_type = MYSQL_TYPE_LONG;
bind_[3].buffer = reinterpret_cast<char*>(&valid_lifetime_);
bind_[3].is_unsigned = MLM_TRUE;
+ // bind_[3].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
// expire: timestamp
bind_[4].buffer_type = MYSQL_TYPE_TIMESTAMP;
bind_[4].buffer = reinterpret_cast<char*>(&expire_);
bind_[4].buffer_length = sizeof(expire_);
+ // bind_[4].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
// subnet_id: unsigned int
bind_[5].buffer_type = MYSQL_TYPE_LONG;
bind_[5].buffer = reinterpret_cast<char*>(&subnet_id_);
bind_[5].is_unsigned = MLM_TRUE;
+ // bind_[5].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
// Add the error flags
setErrorIndicators(bind_, error_, LEASE_COLUMNS);
@@ -465,6 +495,11 @@ public:
time_t cltt = 0;
MySqlLeaseMgr::convertFromDatabaseTime(expire_, valid_lifetime_, cltt);
+ if (client_id_null_==MLM_TRUE) {
+ // There's no client-id, so we pass client-id_length_ set to 0
+ client_id_length_ = 0;
+ }
+
// note that T1 and T2 are not stored
return (Lease4Ptr(new Lease4(addr4_, hwaddr_buffer_, hwaddr_length_,
client_id_buffer_, client_id_length_,
@@ -495,13 +530,15 @@ private:
std::string columns_[LEASE_COLUMNS];///< Column names
my_bool error_[LEASE_COLUMNS]; ///< Error array
std::vector<uint8_t> hwaddr_; ///< Hardware address
- uint8_t hwaddr_buffer_[HWADDR_MAX_LEN];
+ uint8_t hwaddr_buffer_[HWAddr::MAX_HWADDR_LEN];
///< Hardware address buffer
unsigned long hwaddr_length_; ///< Hardware address length
std::vector<uint8_t> client_id_; ///< Client identification
uint8_t client_id_buffer_[ClientId::MAX_CLIENT_ID_LEN];
///< Client ID buffer
unsigned long client_id_length_; ///< Client ID address length
+ my_bool client_id_null_; ///< Is Client ID null?
+
MYSQL_TIME expire_; ///< Lease expiry time
Lease4Ptr lease_; ///< Pointer to lease object
uint32_t subnet_id_; ///< Subnet identification
@@ -536,7 +573,7 @@ public:
memset(addr6_buffer_, 0, sizeof(addr6_buffer_));
memset(duid_buffer_, 0, sizeof(duid_buffer_));
std::fill(&error_[0], &error_[LEASE_COLUMNS], MLM_FALSE);
-
+
// Set the column names (for error messages)
columns_[0] = "address";
columns_[1] = "duid";
@@ -564,6 +601,10 @@ public:
// Ensure bind_ array clear for constructing the MYSQL_BIND structures
// for this lease.
+ // It sets all fields, including is_null, to zero, so we need to set
+ // is_null only if it should be true. This gives up minor performance
+ // benefit while being safe approach. For improved readability, the
+ // code that explicitly sets is_null is there, but is commented out.
memset(bind_, 0, sizeof(bind_));
// address: varchar(39)
@@ -588,6 +629,8 @@ public:
bind_[0].buffer = const_cast<char*>(addr6_.c_str());
bind_[0].buffer_length = addr6_length_;
bind_[0].length = &addr6_length_;
+ // bind_[0].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
// duid: varchar(128)
duid_ = lease_->duid_->getDuid();
@@ -597,11 +640,15 @@ public:
bind_[1].buffer = reinterpret_cast<char*>(&(duid_[0]));
bind_[1].buffer_length = duid_length_;
bind_[1].length = &duid_length_;
+ // bind_[1].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
// valid lifetime: unsigned int
bind_[2].buffer_type = MYSQL_TYPE_LONG;
bind_[2].buffer = reinterpret_cast<char*>(&lease_->valid_lft_);
bind_[2].is_unsigned = MLM_TRUE;
+ // bind_[2].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
// expire: timestamp
// The lease structure holds the client last transmission time (cltt_)
@@ -616,18 +663,24 @@ public:
bind_[3].buffer_type = MYSQL_TYPE_TIMESTAMP;
bind_[3].buffer = reinterpret_cast<char*>(&expire_);
bind_[3].buffer_length = sizeof(expire_);
+ // bind_[3].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
// subnet_id: unsigned int
// Can use lease_->subnet_id_ directly as it is of type uint32_t.
bind_[4].buffer_type = MYSQL_TYPE_LONG;
bind_[4].buffer = reinterpret_cast<char*>(&lease_->subnet_id_);
bind_[4].is_unsigned = MLM_TRUE;
+ // bind_[4].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
// pref_lifetime: unsigned int
// Can use lease_->preferred_lft_ directly as it is of type uint32_t.
bind_[5].buffer_type = MYSQL_TYPE_LONG;
bind_[5].buffer = reinterpret_cast<char*>(&lease_->preferred_lft_);
bind_[5].is_unsigned = MLM_TRUE;
+ // bind_[5].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
// lease_type: tinyint
// Must convert to uint8_t as lease_->type_ is a LeaseType variable.
@@ -635,18 +688,24 @@ public:
bind_[6].buffer_type = MYSQL_TYPE_TINY;
bind_[6].buffer = reinterpret_cast<char*>(&lease_type_);
bind_[6].is_unsigned = MLM_TRUE;
+ // bind_[6].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
// iaid: unsigned int
// Can use lease_->iaid_ directly as it is of type uint32_t.
bind_[7].buffer_type = MYSQL_TYPE_LONG;
bind_[7].buffer = reinterpret_cast<char*>(&lease_->iaid_);
bind_[7].is_unsigned = MLM_TRUE;
+ // bind_[7].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
// prefix_len: unsigned tinyint
// Can use lease_->prefixlen_ directly as it is uint32_t.
bind_[8].buffer_type = MYSQL_TYPE_TINY;
bind_[8].buffer = reinterpret_cast<char*>(&lease_->prefixlen_);
bind_[8].is_unsigned = MLM_TRUE;
+ // bind_[8].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
// Add the error flags
setErrorIndicators(bind_, error_, LEASE_COLUMNS);
@@ -670,6 +729,10 @@ public:
std::vector<MYSQL_BIND> createBindForReceive() {
// Initialize MYSQL_BIND array.
+ // It sets all fields, including is_null, to zero, so we need to set
+ // is_null only if it should be true. This gives up minor performance
+ // benefit while being safe approach. For improved readability, the
+ // code that explicitly sets is_null is there, but is commented out.
memset(bind_, 0, sizeof(bind_));
// address: varchar(39)
@@ -681,6 +744,8 @@ public:
bind_[0].buffer = addr6_buffer_;
bind_[0].buffer_length = addr6_length_;
bind_[0].length = &addr6_length_;
+ // bind_[0].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
// client_id: varbinary(128)
duid_length_ = sizeof(duid_buffer_);
@@ -688,41 +753,57 @@ public:
bind_[1].buffer = reinterpret_cast<char*>(duid_buffer_);
bind_[1].buffer_length = duid_length_;
bind_[1].length = &duid_length_;
+ // bind_[1].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
// lease_time: unsigned int
bind_[2].buffer_type = MYSQL_TYPE_LONG;
bind_[2].buffer = reinterpret_cast<char*>(&valid_lifetime_);
bind_[2].is_unsigned = MLM_TRUE;
+ // bind_[2].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
// expire: timestamp
bind_[3].buffer_type = MYSQL_TYPE_TIMESTAMP;
bind_[3].buffer = reinterpret_cast<char*>(&expire_);
bind_[3].buffer_length = sizeof(expire_);
+ // bind_[3].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
// subnet_id: unsigned int
bind_[4].buffer_type = MYSQL_TYPE_LONG;
bind_[4].buffer = reinterpret_cast<char*>(&subnet_id_);
bind_[4].is_unsigned = MLM_TRUE;
+ // bind_[4].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
// pref_lifetime: unsigned int
bind_[5].buffer_type = MYSQL_TYPE_LONG;
bind_[5].buffer = reinterpret_cast<char*>(&pref_lifetime_);
bind_[5].is_unsigned = MLM_TRUE;
+ // bind_[5].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
// lease_type: tinyint
bind_[6].buffer_type = MYSQL_TYPE_TINY;
bind_[6].buffer = reinterpret_cast<char*>(&lease_type_);
bind_[6].is_unsigned = MLM_TRUE;
+ // bind_[6].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
// iaid: unsigned int
bind_[7].buffer_type = MYSQL_TYPE_LONG;
bind_[7].buffer = reinterpret_cast<char*>(&iaid_);
bind_[7].is_unsigned = MLM_TRUE;
+ // bind_[7].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
// prefix_len: unsigned tinyint
bind_[8].buffer_type = MYSQL_TYPE_TINY;
bind_[8].buffer = reinterpret_cast<char*>(&prefixlen_);
bind_[8].is_unsigned = MLM_TRUE;
+ // bind_[8].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
// Add the error flags
setErrorIndicators(bind_, error_, LEASE_COLUMNS);
@@ -809,7 +890,7 @@ private:
// schema.
// Note: arrays are declared fixed length for speed of creation
std::string addr6_; ///< String form of address
- char addr6_buffer_[ADDRESS6_TEXT_MAX_LEN + 1]; ///< Character
+ char addr6_buffer_[ADDRESS6_TEXT_MAX_LEN + 1]; ///< Character
///< array form of V6 address
unsigned long addr6_length_; ///< Length of the address
MYSQL_BIND bind_[LEASE_COLUMNS]; ///< Bind array
@@ -831,7 +912,7 @@ private:
/// @brief Fetch and Release MySQL Results
///
-/// When a MySQL statement is exected, to fetch the results the function
+/// When a MySQL statement is expected, to fetch the results the function
/// mysql_stmt_fetch() must be called. As well as getting data, this
/// allocates internal state. Subsequent calls to mysql_stmt_fetch can be
/// made, but when all the data is retrieved, mysql_stmt_free_result must be
@@ -870,17 +951,10 @@ private:
MYSQL_STMT* statement_; ///< Statement for which results are freed
};
-
// MySqlLeaseMgr Constructor and Destructor
-MySqlLeaseMgr::MySqlLeaseMgr(const LeaseMgr::ParameterMap& parameters)
- : LeaseMgr(parameters), mysql_(NULL) {
-
- // Allocate context for MySQL - it is destroyed in the destructor.
- mysql_ = mysql_init(NULL);
- if (mysql_ == NULL) {
- isc_throw(DbOpenError, "unable to initialize MySQL");
- }
+MySqlLeaseMgr::MySqlLeaseMgr(const LeaseMgr::ParameterMap& parameters)
+ : LeaseMgr(parameters) {
// Open the database.
openDatabase();
@@ -916,9 +990,8 @@ MySqlLeaseMgr::~MySqlLeaseMgr() {
}
}
- // Close the database
- mysql_close(mysql_);
- mysql_ = NULL;
+ // There is no need to close the database in this destructor: it is
+ // closed in the destructor of the mysql_ member variable.
}
@@ -970,7 +1043,7 @@ MySqlLeaseMgr::convertFromDatabaseTime(const MYSQL_TIME& expire,
expire_tm.tm_hour = expire.hour;
expire_tm.tm_min = expire.minute;
expire_tm.tm_sec = expire.second;
- expire_tm.tm_isdst = -1; // Let the system work out about DST
+ expire_tm.tm_isdst = -1; // Let the system work out about DST
// Convert to local time
cltt = mktime(&expire_tm) - valid_lifetime;
@@ -1022,7 +1095,7 @@ MySqlLeaseMgr::openDatabase() {
}
// Set options for the connection:
- //
+ //
// Automatic reconnection: after a period of inactivity, the client will
// disconnect from the database. This option causes it to automatically
// reconnect when another operation is about to be done.
@@ -1033,6 +1106,17 @@ MySqlLeaseMgr::openDatabase() {
mysql_error(mysql_));
}
+ // Set SQL mode options for the connection: SQL mode governs how what
+ // constitutes insertable data for a given column, and how to handle
+ // invalid data. We want to ensure we get the strictest behavior and
+ // to reject invalid data with an error.
+ const char *sql_mode = "SET SESSION sql_mode ='STRICT_ALL_TABLES'";
+ result = mysql_options(mysql_, MYSQL_INIT_COMMAND, sql_mode);
+ if (result != 0) {
+ isc_throw(DbOpenError, "unable to set SQL mode options: " <<
+ mysql_error(mysql_));
+ }
+
// Open the database.
//
// The option CLIENT_FOUND_ROWS is specified so that in an UPDATE,
@@ -1088,7 +1172,7 @@ MySqlLeaseMgr::prepareStatements() {
// Allocate space for all statements
statements_.clear();
statements_.resize(NUM_STATEMENTS, NULL);
-
+
text_statements_.clear();
text_statements_.resize(NUM_STATEMENTS, std::string(""));
diff --git a/src/lib/dhcpsrv/mysql_lease_mgr.h b/src/lib/dhcpsrv/mysql_lease_mgr.h
index 48370e1..6d8eb8c 100644
--- a/src/lib/dhcpsrv/mysql_lease_mgr.h
+++ b/src/lib/dhcpsrv/mysql_lease_mgr.h
@@ -19,13 +19,62 @@
#include <dhcpsrv/lease_mgr.h>
#include <boost/scoped_ptr.hpp>
-#include <mysql/mysql.h>
+#include <boost/utility.hpp>
+#include <mysql.h>
#include <time.h>
namespace isc {
namespace dhcp {
+/// @brief MySQL Handle Holder
+///
+/// Small RAII object for safer initialization, will close the database
+/// connection upon destruction. This means that if an exception is thrown
+/// during database initialization, resources allocated to the database are
+/// guaranteed to be freed.
+///
+/// It makes no sense to copy an object of this class. After the copy, both
+/// objects would contain pointers to the same MySql context object. The
+/// destruction of one would invalid the context in the remaining object.
+/// For this reason, the class is declared noncopyable.
+class MySqlHolder : public boost::noncopyable {
+public:
+
+ /// @brief Constructor
+ ///
+ /// Initialize MySql and store the associated context object.
+ ///
+ /// @throw DbOpenError Unable to initialize MySql handle.
+ MySqlHolder() : mysql_(mysql_init(NULL)) {
+ if (mysql_ == NULL) {
+ isc_throw(DbOpenError, "unable to initialize MySQL");
+ }
+ }
+
+ /// @brief Destructor
+ ///
+ /// Frees up resources allocated by the initialization of MySql.
+ ~MySqlHolder() {
+ if (mysql_ != NULL) {
+ mysql_close(mysql_);
+ }
+ // The library itself shouldn't be needed anymore
+ mysql_library_end();
+ }
+
+ /// @brief Conversion Operator
+ ///
+ /// Allows the MySqlHolder object to be passed as the context argument to
+ /// mysql_xxx functions.
+ operator MYSQL*() const {
+ return (mysql_);
+ }
+
+private:
+ MYSQL* mysql_; ///< Initialization context
+};
+
// Define the current database schema values
const uint32_t CURRENT_VERSION_VERSION = 1;
@@ -379,7 +428,7 @@ public:
/// @param cltt Reference to location where client last transmit time
/// is put.
static
- void convertFromDatabaseTime(const MYSQL_TIME& expire,
+ void convertFromDatabaseTime(const MYSQL_TIME& expire,
uint32_t valid_lifetime, time_t& cltt);
///@}
@@ -616,7 +665,7 @@ private:
/// declare them as "mutable".)
boost::scoped_ptr<MySqlLease4Exchange> exchange4_; ///< Exchange object
boost::scoped_ptr<MySqlLease6Exchange> exchange6_; ///< Exchange object
- MYSQL* mysql_; ///< MySQL context object
+ MySqlHolder mysql_;
std::vector<MYSQL_STMT*> statements_; ///< Prepared statements
std::vector<std::string> text_statements_; ///< Raw text of statements
};
diff --git a/src/lib/dhcpsrv/pool.cc b/src/lib/dhcpsrv/pool.cc
index 7b8a4ea..7104c61 100644
--- a/src/lib/dhcpsrv/pool.cc
+++ b/src/lib/dhcpsrv/pool.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -64,7 +64,7 @@ Pool4::Pool4(const isc::asiolink::IOAddress& prefix,
Pool6::Pool6(Pool6Type type, const isc::asiolink::IOAddress& first,
const isc::asiolink::IOAddress& last)
- :Pool(first, last), type_(type), prefix_len_(0) {
+ :Pool(first, last), type_(type) {
// check if specified address boundaries are sane
if (!first.isV6() || !last.isV6()) {
@@ -95,16 +95,15 @@ Pool6::Pool6(Pool6Type type, const isc::asiolink::IOAddress& first,
Pool6::Pool6(Pool6Type type, const isc::asiolink::IOAddress& prefix,
uint8_t prefix_len)
:Pool(prefix, IOAddress("::")),
- type_(type), prefix_len_(prefix_len) {
+ type_(type) {
// check if the prefix is sane
if (!prefix.isV6()) {
isc_throw(BadValue, "Invalid Pool6 address boundaries: not IPv6");
}
- // check if the prefix length is sane (we use the member variable only
- // for silencing some compilers; see #2705 and #2789).
- if (prefix_len_ == 0 || prefix_len_ > 128) {
+ // check if the prefix length is sane
+ if (prefix_len == 0 || prefix_len > 128) {
isc_throw(BadValue, "Invalid prefix length");
}
diff --git a/src/lib/dhcpsrv/pool.h b/src/lib/dhcpsrv/pool.h
index e8dc3e3..e0a6f3c 100644
--- a/src/lib/dhcpsrv/pool.h
+++ b/src/lib/dhcpsrv/pool.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -168,9 +168,6 @@ private:
/// @brief defines a pool type
Pool6Type type_;
- /// @brief prefix length
- /// used by TYPE_PD only (zeroed for other types)
- uint8_t prefix_len_;
};
/// @brief a pointer an IPv6 Pool
diff --git a/src/lib/dhcpsrv/subnet.cc b/src/lib/dhcpsrv/subnet.cc
index daf3f9e..50a0fee 100644
--- a/src/lib/dhcpsrv/subnet.cc
+++ b/src/lib/dhcpsrv/subnet.cc
@@ -33,11 +33,13 @@ Subnet::Subnet(const isc::asiolink::IOAddress& prefix, uint8_t len,
last_allocated_(lastAddrInPrefix(prefix, len)) {
if ((prefix.isV6() && len > 128) ||
(prefix.isV4() && len > 32)) {
- isc_throw(BadValue, "Invalid prefix length specified for subnet: " << len);
+ isc_throw(BadValue,
+ "Invalid prefix length specified for subnet: " << len);
}
}
-bool Subnet::inRange(const isc::asiolink::IOAddress& addr) const {
+bool
+Subnet::inRange(const isc::asiolink::IOAddress& addr) const {
IOAddress first = firstAddrInPrefix(prefix_, prefix_len_);
IOAddress last = lastAddrInPrefix(prefix_, prefix_len_);
@@ -84,7 +86,8 @@ Subnet::getOptionDescriptor(const std::string& option_space,
return (*range.first);
}
-std::string Subnet::toText() const {
+std::string
+Subnet::toText() const {
std::stringstream tmp;
tmp << prefix_.toText() << "/" << static_cast<unsigned int>(prefix_len_);
return (tmp.str());
@@ -101,12 +104,14 @@ Subnet4::Subnet4(const isc::asiolink::IOAddress& prefix, uint8_t length,
}
}
-void Subnet::addPool(const PoolPtr& pool) {
+void
+Subnet::addPool(const PoolPtr& pool) {
IOAddress first_addr = pool->getFirstAddress();
IOAddress last_addr = pool->getLastAddress();
if (!inRange(first_addr) || !inRange(last_addr)) {
- isc_throw(BadValue, "Pool (" << first_addr.toText() << "-" << last_addr.toText()
+ isc_throw(BadValue, "Pool (" << first_addr.toText() << "-"
+ << last_addr.toText()
<< " does not belong in this (" << prefix_.toText() << "/"
<< static_cast<int>(prefix_len_) << ") subnet4");
}
@@ -119,15 +124,16 @@ void Subnet::addPool(const PoolPtr& pool) {
PoolPtr Subnet::getPool(isc::asiolink::IOAddress hint) {
PoolPtr candidate;
- for (PoolCollection::iterator pool = pools_.begin(); pool != pools_.end(); ++pool) {
+ for (PoolCollection::iterator pool = pools_.begin();
+ pool != pools_.end(); ++pool) {
- // if we won't find anything better, then let's just use the first pool
+ // If we won't find anything better, then let's just use the first pool
if (!candidate) {
candidate = *pool;
}
- // if the client provided a pool and there's a pool that hint is valid in,
- // then let's use that pool
+ // If the client provided a pool and there's a pool that hint is valid
+ // in, then let's use that pool
if ((*pool)->inRange(hint)) {
return (*pool);
}
@@ -135,29 +141,44 @@ PoolPtr Subnet::getPool(isc::asiolink::IOAddress hint) {
return (candidate);
}
+void
+Subnet::setIface(const std::string& iface_name) {
+ iface_ = iface_name;
+}
+
+std::string
+Subnet::getIface() const {
+ return (iface_);
+}
+
+
void
Subnet4::validateOption(const OptionPtr& option) const {
if (!option) {
- isc_throw(isc::BadValue, "option configured for subnet must not be NULL");
+ isc_throw(isc::BadValue,
+ "option configured for subnet must not be NULL");
} else if (option->getUniverse() != Option::V4) {
- isc_throw(isc::BadValue, "expected V4 option to be added to the subnet");
+ isc_throw(isc::BadValue,
+ "expected V4 option to be added to the subnet");
}
}
-bool Subnet::inPool(const isc::asiolink::IOAddress& addr) const {
+bool
+Subnet::inPool(const isc::asiolink::IOAddress& addr) const {
// Let's start with checking if it even belongs to that subnet.
if (!inRange(addr)) {
return (false);
}
- for (PoolCollection::const_iterator pool = pools_.begin(); pool != pools_.end(); ++pool) {
+ for (PoolCollection::const_iterator pool = pools_.begin();
+ pool != pools_.end(); ++pool) {
if ((*pool)->inRange(addr)) {
return (true);
}
}
- // there's no pool that address belongs to
+ // There's no pool that address belongs to
return (false);
}
@@ -177,21 +198,13 @@ Subnet6::Subnet6(const isc::asiolink::IOAddress& prefix, uint8_t length,
void
Subnet6::validateOption(const OptionPtr& option) const {
if (!option) {
- isc_throw(isc::BadValue, "option configured for subnet must not be NULL");
+ isc_throw(isc::BadValue,
+ "option configured for subnet must not be NULL");
} else if (option->getUniverse() != Option::V6) {
- isc_throw(isc::BadValue, "expected V6 option to be added to the subnet");
+ isc_throw(isc::BadValue,
+ "expected V6 option to be added to the subnet");
}
}
-
-void Subnet6::setIface(const std::string& iface_name) {
- iface_ = iface_name;
-}
-
-std::string Subnet6::getIface() const {
- return (iface_);
-}
-
-
} // end of isc::dhcp namespace
} // end of isc namespace
diff --git a/src/lib/dhcpsrv/subnet.h b/src/lib/dhcpsrv/subnet.h
index 5a16647..0ac5109 100644
--- a/src/lib/dhcpsrv/subnet.h
+++ b/src/lib/dhcpsrv/subnet.h
@@ -47,7 +47,7 @@ namespace dhcp {
/// @todo: Implement support for options here
-/// @brief Unique indentifier for a subnet (both v4 and v6)
+/// @brief Unique identifier for a subnet (both v4 and v6)
typedef uint32_t SubnetID;
class Subnet {
@@ -299,7 +299,18 @@ public:
return pools_;
}
- /// @brief returns textual representation of the subnet (e.g. "2001:db8::/64")
+ /// @brief sets name of the network interface for directly attached networks
+ ///
+ /// @param iface_name name of the interface
+ void setIface(const std::string& iface_name);
+
+ /// @brief network interface name used to reach subnet (or "" for remote
+ /// subnets)
+ /// @return network interface name for directly attached subnets or ""
+ std::string getIface() const;
+
+ /// @brief returns textual representation of the subnet (e.g.
+ /// "2001:db8::/64")
///
/// @return textual representation
virtual std::string toText() const;
@@ -451,17 +462,18 @@ public:
return (preferred_);
}
- /// @brief sets name of the network interface for directly attached networks
+ /// @brief sets interface-id option (if defined)
///
- /// A subnet may be reachable directly (not via relays). In DHCPv6 it is not
- /// possible to decide that based on addresses assigned to network interfaces,
- /// as DHCPv6 operates on link-local (and site local) addresses.
- /// @param iface_name name of the interface
- void setIface(const std::string& iface_name);
+ /// @param ifaceid pointer to interface-id option
+ void setInterfaceId(const OptionPtr& ifaceid) {
+ interface_id_ = ifaceid;
+ }
- /// @brief network interface name used to reach subnet (or "" for remote subnets)
- /// @return network interface name for directly attached subnets or ""
- std::string getIface() const;
+ /// @brief returns interface-id value (if specified)
+ /// @return interface-id option (if defined)
+ OptionPtr getInterfaceId() const {
+ return interface_id_;
+ }
protected:
@@ -478,6 +490,9 @@ protected:
return (isc::asiolink::IOAddress("::"));
}
+ /// @brief specifies optional interface-id
+ OptionPtr interface_id_;
+
/// @brief collection of pools in that list
Pool6Collection pools_;
diff --git a/src/lib/dhcpsrv/tests/Makefile.am b/src/lib/dhcpsrv/tests/Makefile.am
index e19fd87..9a81ef6 100644
--- a/src/lib/dhcpsrv/tests/Makefile.am
+++ b/src/lib/dhcpsrv/tests/Makefile.am
@@ -34,6 +34,7 @@ libdhcpsrv_unittests_SOURCES += dbaccess_parser_unittest.cc
libdhcpsrv_unittests_SOURCES += lease_mgr_factory_unittest.cc
libdhcpsrv_unittests_SOURCES += lease_mgr_unittest.cc
libdhcpsrv_unittests_SOURCES += memfile_lease_mgr_unittest.cc
+libdhcpsrv_unittests_SOURCES += dhcp_parsers_unittest.cc
if HAVE_MYSQL
libdhcpsrv_unittests_SOURCES += mysql_lease_mgr_unittest.cc
endif
diff --git a/src/lib/dhcpsrv/tests/alloc_engine_unittest.cc b/src/lib/dhcpsrv/tests/alloc_engine_unittest.cc
index ddc0f62..05f3741 100644
--- a/src/lib/dhcpsrv/tests/alloc_engine_unittest.cc
+++ b/src/lib/dhcpsrv/tests/alloc_engine_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -149,18 +149,26 @@ public:
///
/// @param lease lease to be checked
void checkLease4(const Lease4Ptr& lease) {
- // that is belongs to the right subnet
+ // Check that is belongs to the right subnet
EXPECT_EQ(lease->subnet_id_, subnet_->getID());
EXPECT_TRUE(subnet_->inRange(lease->addr_));
EXPECT_TRUE(subnet_->inPool(lease->addr_));
- // that it have proper parameters
+ // Check that it has proper parameters
EXPECT_EQ(subnet_->getValid(), lease->valid_lft_);
EXPECT_EQ(subnet_->getT1(), lease->t1_);
EXPECT_EQ(subnet_->getT2(), lease->t2_);
EXPECT_TRUE(false == lease->fqdn_fwd_);
EXPECT_TRUE(false == lease->fqdn_rev_);
- EXPECT_TRUE(*lease->client_id_ == *clientid_);
+ if (lease->client_id_ && !clientid_) {
+ ADD_FAILURE() << "Lease4 has a client-id, while it should have none.";
+ } else
+ if (!lease->client_id_ && clientid_) {
+ ADD_FAILURE() << "Lease4 has no client-id, but it was expected to have one.";
+ } else
+ if (lease->client_id_ && clientid_) {
+ EXPECT_TRUE(*lease->client_id_ == *clientid_);
+ }
EXPECT_TRUE(lease->hwaddr_ == hwaddr_->hwaddr_);
// @todo: check cltt
}
@@ -169,11 +177,11 @@ public:
factory_.destroy();
}
- ClientIdPtr clientid_; ///< client-identifier (value used in tests)
- HWAddrPtr hwaddr_; ///< hardware address (value used in tests)
- Subnet4Ptr subnet_; ///< subnet4 (used in tests)
- Pool4Ptr pool_; ///< pool belonging to subnet_
- LeaseMgrFactory factory_; ///< pointer to LeaseMgr factory
+ ClientIdPtr clientid_; ///< Client-identifier (value used in tests)
+ HWAddrPtr hwaddr_; ///< Hardware address (value used in tests)
+ Subnet4Ptr subnet_; ///< Subnet4 (used in tests)
+ Pool4Ptr pool_; ///< Pool belonging to subnet_
+ LeaseMgrFactory factory_; ///< Pointer to LeaseMgr factory
};
// This test checks if the Allocation Engine can be instantiated and that it
@@ -197,10 +205,10 @@ TEST_F(AllocEngine6Test, simpleAlloc6) {
Lease6Ptr lease = engine->allocateAddress6(subnet_, duid_, iaid_, IOAddress("::"),
false);
- // check that we got a lease
+ // Check that we got a lease
ASSERT_TRUE(lease);
- // do all checks on the lease
+ // Do all checks on the lease
checkLease6(lease);
// Check that the lease is indeed in LeaseMgr
@@ -220,10 +228,10 @@ TEST_F(AllocEngine6Test, fakeAlloc6) {
Lease6Ptr lease = engine->allocateAddress6(subnet_, duid_, iaid_, IOAddress("::"),
true);
- // check that we got a lease
+ // Check that we got a lease
ASSERT_TRUE(lease);
- // do all checks on the lease
+ // Do all checks on the lease
checkLease6(lease);
// Check that the lease is NOT in LeaseMgr
@@ -242,13 +250,13 @@ TEST_F(AllocEngine6Test, allocWithValidHint6) {
IOAddress("2001:db8:1::15"),
false);
- // check that we got a lease
+ // Check that we got a lease
ASSERT_TRUE(lease);
- // we should get what we asked for
+ // We should get what we asked for
EXPECT_EQ(lease->addr_.toText(), "2001:db8:1::15");
- // do all checks on the lease
+ // Do all checks on the lease
checkLease6(lease);
// Check that the lease is indeed in LeaseMgr
@@ -266,29 +274,29 @@ TEST_F(AllocEngine6Test, allocWithUsedHint6) {
ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
ASSERT_TRUE(engine);
- // let's create a lease and put it in the LeaseMgr
+ // Let's create a lease and put it in the LeaseMgr
DuidPtr duid2 = boost::shared_ptr<DUID>(new DUID(vector<uint8_t>(8, 0xff)));
time_t now = time(NULL);
Lease6Ptr used(new Lease6(Lease6::LEASE_IA_NA, IOAddress("2001:db8:1::1f"),
duid2, 1, 2, 3, 4, now, subnet_->getID()));
ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used));
- // another client comes in and request an address that is in pool, but
+ // Another client comes in and request an address that is in pool, but
// unfortunately it is used already. The same address must not be allocated
// twice.
Lease6Ptr lease = engine->allocateAddress6(subnet_, duid_, iaid_,
IOAddress("2001:db8:1::1f"),
false);
- // check that we got a lease
+ // Check that we got a lease
ASSERT_TRUE(lease);
- // allocated address must be different
+ // Allocated address must be different
EXPECT_TRUE(used->addr_.toText() != lease->addr_.toText());
- // we should NOT get what we asked for, because it is used already
+ // We should NOT get what we asked for, because it is used already
EXPECT_TRUE(lease->addr_.toText() != "2001:db8:1::1f");
- // do all checks on the lease
+ // Do all checks on the lease
checkLease6(lease);
// Check that the lease is indeed in LeaseMgr
@@ -312,13 +320,13 @@ TEST_F(AllocEngine6Test, allocBogusHint6) {
Lease6Ptr lease = engine->allocateAddress6(subnet_, duid_, iaid_,
IOAddress("3000::abc"),
false);
- // check that we got a lease
+ // Check that we got a lease
ASSERT_TRUE(lease);
- // we should NOT get what we asked for, because it is used already
+ // We should NOT get what we asked for, because it is used already
EXPECT_TRUE(lease->addr_.toText() != "3000::abc");
- // do all checks on the lease
+ // Do all checks on the lease
checkLease6(lease);
// Check that the lease is indeed in LeaseMgr
@@ -329,6 +337,24 @@ TEST_F(AllocEngine6Test, allocBogusHint6) {
detailCompareLease(lease, from_mgr);
}
+// This test checks that NULL values are handled properly
+TEST_F(AllocEngine6Test, allocateAddress6Nulls) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+ ASSERT_TRUE(engine);
+
+ // Allocations without subnet are not allowed
+ Lease6Ptr lease = engine->allocateAddress6(Subnet6Ptr(), duid_, iaid_,
+ IOAddress("::"), false);
+ ASSERT_FALSE(lease);
+
+ // Allocations without DUID are not allowed either
+ lease = engine->allocateAddress6(subnet_, DuidPtr(), iaid_,
+ IOAddress("::"), false);
+ ASSERT_FALSE(lease);
+}
+
+
// This test verifies that the allocator picks addresses that belong to the
// pool
TEST_F(AllocEngine6Test, IterativeAllocator) {
@@ -346,7 +372,7 @@ TEST_F(AllocEngine6Test, IterativeAllocator) {
// in all pools in specified subnet. It also must not pick the same address twice
// unless it runs out of pool space and must start over.
TEST_F(AllocEngine6Test, IterativeAllocator_manyPools6) {
- NakedAllocEngine::IterativeAllocator* alloc = new NakedAllocEngine::IterativeAllocator();
+ NakedAllocEngine::IterativeAllocator alloc;
// let's start from 2, as there is 2001:db8:1::10 - 2001:db8:1::20 pool already.
for (int i = 2; i < 10; ++i) {
@@ -357,18 +383,17 @@ TEST_F(AllocEngine6Test, IterativeAllocator_manyPools6) {
Pool6Ptr pool(new Pool6(Pool6::TYPE_IA, IOAddress(min.str()),
IOAddress(max.str())));
- // cout << "Adding pool: " << min.str() << "-" << max.str() << endl;
subnet_->addPool(pool);
}
- int total = 17 + 8*9; // first pool (::10 - ::20) has 17 addresses in it,
- // there are 8 extra pools with 9 addresses in each.
+ int total = 17 + 8 * 9; // First pool (::10 - ::20) has 17 addresses in it,
+ // there are 8 extra pools with 9 addresses in each.
// Let's keep picked addresses here and check their uniqueness.
std::set<IOAddress> generated_addrs;
int cnt = 0;
while (++cnt) {
- IOAddress candidate = alloc->pickAddress(subnet_, duid_, IOAddress("::"));
+ IOAddress candidate = alloc.pickAddress(subnet_, duid_, IOAddress("::"));
EXPECT_TRUE(subnet_->inPool(candidate));
// One way to easily verify that the iterative allocator really works is
@@ -377,13 +402,13 @@ TEST_F(AllocEngine6Test, IterativeAllocator_manyPools6) {
// cout << candidate.toText() << endl;
if (generated_addrs.find(candidate) == generated_addrs.end()) {
- // we haven't had this
+ // We haven't had this.
generated_addrs.insert(candidate);
} else {
- // we have seen this address before. That should mean that we
+ // We have seen this address before. That should mean that we
// iterated over all addresses.
if (generated_addrs.size() == total) {
- // we have exactly the number of address in all pools
+ // We have exactly the number of address in all pools.
break;
}
ADD_FAILURE() << "Too many or not enough unique addresses generated.";
@@ -395,8 +420,6 @@ TEST_F(AllocEngine6Test, IterativeAllocator_manyPools6) {
break;
}
}
-
- delete alloc;
}
// This test checks if really small pools are working
@@ -423,7 +446,7 @@ TEST_F(AllocEngine6Test, smallPool6) {
EXPECT_EQ("2001:db8:1::ad", lease->addr_.toText());
- // do all checks on the lease
+ // Do all checks on the lease
checkLease6(lease);
// Check that the lease is indeed in LeaseMgr
@@ -702,6 +725,42 @@ TEST_F(AllocEngine4Test, allocBogusHint4) {
}
+// This test checks that NULL values are handled properly
+TEST_F(AllocEngine4Test, allocateAddress4Nulls) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+ ASSERT_TRUE(engine);
+
+ // Allocations without subnet are not allowed
+ Lease4Ptr lease = engine->allocateAddress4(SubnetPtr(), clientid_, hwaddr_,
+ IOAddress("0.0.0.0"), false);
+ EXPECT_FALSE(lease);
+
+ // Allocations without HW address are not allowed
+ lease = engine->allocateAddress4(subnet_, clientid_, HWAddrPtr(),
+ IOAddress("0.0.0.0"), false);
+ EXPECT_FALSE(lease);
+
+ // Allocations without client-id are allowed
+ clientid_ = ClientIdPtr();
+ lease = engine->allocateAddress4(subnet_, ClientIdPtr(), hwaddr_,
+ IOAddress("0.0.0.0"), false);
+ // Check that we got a lease
+ ASSERT_TRUE(lease);
+
+ // Do all checks on the lease
+ checkLease4(lease);
+
+ // Check that the lease is indeed in LeaseMgr
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+ ASSERT_TRUE(from_mgr);
+
+ // Now check that the lease in LeaseMgr has the same parameters
+ detailCompareLease(lease, from_mgr);
+}
+
+
+
// This test verifies that the allocator picks addresses that belong to the
// pool
TEST_F(AllocEngine4Test, IterativeAllocator) {
@@ -720,7 +779,7 @@ TEST_F(AllocEngine4Test, IterativeAllocator) {
// in all pools in specified subnet. It also must not pick the same address twice
// unless it runs out of pool space and must start over.
TEST_F(AllocEngine4Test, IterativeAllocator_manyPools4) {
- NakedAllocEngine::IterativeAllocator* alloc = new NakedAllocEngine::IterativeAllocator();
+ NakedAllocEngine::IterativeAllocator alloc;
// Let's start from 2, as there is 2001:db8:1::10 - 2001:db8:1::20 pool already.
for (int i = 2; i < 10; ++i) {
@@ -742,7 +801,7 @@ TEST_F(AllocEngine4Test, IterativeAllocator_manyPools4) {
std::set<IOAddress> generated_addrs;
int cnt = 0;
while (++cnt) {
- IOAddress candidate = alloc->pickAddress(subnet_, clientid_, IOAddress("0.0.0.0"));
+ IOAddress candidate = alloc.pickAddress(subnet_, clientid_, IOAddress("0.0.0.0"));
EXPECT_TRUE(subnet_->inPool(candidate));
// One way to easily verify that the iterative allocator really works is
@@ -754,10 +813,10 @@ TEST_F(AllocEngine4Test, IterativeAllocator_manyPools4) {
// We haven't had this
generated_addrs.insert(candidate);
} else {
- // we have seen this address before. That should mean that we
+ // We have seen this address before. That should mean that we
// iterated over all addresses.
if (generated_addrs.size() == total) {
- // we have exactly the number of address in all pools
+ // We have exactly the number of address in all pools
break;
}
ADD_FAILURE() << "Too many or not enough unique addresses generated.";
@@ -769,8 +828,6 @@ TEST_F(AllocEngine4Test, IterativeAllocator_manyPools4) {
break;
}
}
-
- delete alloc;
}
@@ -902,7 +959,7 @@ TEST_F(AllocEngine4Test, requestReuseExpiredLease4) {
time_t now = time(NULL) - 500; // Allocated 500 seconds ago
Lease4Ptr lease(new Lease4(addr, clientid2, sizeof(clientid2), hwaddr2, sizeof(hwaddr2),
495, 100, 200, now, subnet_->getID()));
- // lease was assigned 500 seconds ago, but its valid lifetime is 495, so it
+ // Lease was assigned 500 seconds ago, but its valid lifetime is 495, so it
// is expired already
ASSERT_TRUE(lease->expired());
ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
@@ -963,4 +1020,4 @@ TEST_F(AllocEngine4Test, renewLease4) {
detailCompareLease(lease, from_mgr);
}
-}; // end of anonymous namespace
+}; // End of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc b/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc
index 9b3d61b..77c3e36 100644
--- a/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc
+++ b/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc
@@ -15,7 +15,9 @@
#include <config.h>
#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/dhcp_parsers.h>
#include <exceptions/exceptions.h>
+#include <dhcp/dhcp6.h>
#include <gtest/gtest.h>
@@ -36,12 +38,143 @@ using boost::scoped_ptr;
namespace {
+// This test verifies that BooleanStorage functions properly.
+TEST(ValueStorageTest, BooleanTesting) {
+ BooleanStorage testStore;
+
+ // Verify that we can add and retrieve parameters.
+ testStore.setParam("firstBool", false);
+ testStore.setParam("secondBool", true);
+
+ EXPECT_FALSE(testStore.getParam("firstBool"));
+ EXPECT_TRUE(testStore.getParam("secondBool"));
+
+ // Verify that we can update parameters.
+ testStore.setParam("firstBool", true);
+ testStore.setParam("secondBool", false);
+
+ EXPECT_TRUE(testStore.getParam("firstBool"));
+ EXPECT_FALSE(testStore.getParam("secondBool"));
+
+ // Verify that we can delete a parameter and it will no longer be found.
+ testStore.delParam("firstBool");
+ EXPECT_THROW(testStore.getParam("firstBool"), isc::dhcp::DhcpConfigError);
+
+ // Verify that the delete was safe and the store still operates.
+ EXPECT_FALSE(testStore.getParam("secondBool"));
+
+ // Verify that looking for a parameter that never existed throws.
+ ASSERT_THROW(testStore.getParam("bogusBool"), isc::dhcp::DhcpConfigError);
+
+ // Verify that attempting to delete a parameter that never existed does not throw.
+ EXPECT_NO_THROW(testStore.delParam("bogusBool"));
+
+ // Verify that we can empty the list.
+ testStore.clear();
+ EXPECT_THROW(testStore.getParam("secondBool"), isc::dhcp::DhcpConfigError);
+
+}
+
+// This test verifies that Uint32Storage functions properly.
+TEST(ValueStorageTest, Uint32Testing) {
+ Uint32Storage testStore;
+
+ uint32_t intOne = 77;
+ uint32_t intTwo = 33;
+
+ // Verify that we can add and retrieve parameters.
+ testStore.setParam("firstInt", intOne);
+ testStore.setParam("secondInt", intTwo);
+
+ EXPECT_EQ(testStore.getParam("firstInt"), intOne);
+ EXPECT_EQ(testStore.getParam("secondInt"), intTwo);
+
+ // Verify that we can update parameters.
+ testStore.setParam("firstInt", --intOne);
+ testStore.setParam("secondInt", ++intTwo);
+
+ EXPECT_EQ(testStore.getParam("firstInt"), intOne);
+ EXPECT_EQ(testStore.getParam("secondInt"), intTwo);
+
+ // Verify that we can delete a parameter and it will no longer be found.
+ testStore.delParam("firstInt");
+ EXPECT_THROW(testStore.getParam("firstInt"), isc::dhcp::DhcpConfigError);
+
+ // Verify that the delete was safe and the store still operates.
+ EXPECT_EQ(testStore.getParam("secondInt"), intTwo);
+
+ // Verify that looking for a parameter that never existed throws.
+ ASSERT_THROW(testStore.getParam("bogusInt"), isc::dhcp::DhcpConfigError);
+
+ // Verify that attempting to delete a parameter that never existed does not throw.
+ EXPECT_NO_THROW(testStore.delParam("bogusInt"));
+
+ // Verify that we can empty the list.
+ testStore.clear();
+ EXPECT_THROW(testStore.getParam("secondInt"), isc::dhcp::DhcpConfigError);
+}
+
+// This test verifies that StringStorage functions properly.
+TEST(ValueStorageTest, StringTesting) {
+ StringStorage testStore;
+
+ std::string stringOne = "seventy-seven";
+ std::string stringTwo = "thirty-three";
+
+ // Verify that we can add and retrieve parameters.
+ testStore.setParam("firstString", stringOne);
+ testStore.setParam("secondString", stringTwo);
+
+ EXPECT_EQ(testStore.getParam("firstString"), stringOne);
+ EXPECT_EQ(testStore.getParam("secondString"), stringTwo);
+
+ // Verify that we can update parameters.
+ stringOne.append("-boo");
+ stringTwo.append("-boo");
+
+ testStore.setParam("firstString", stringOne);
+ testStore.setParam("secondString", stringTwo);
+
+ EXPECT_EQ(testStore.getParam("firstString"), stringOne);
+ EXPECT_EQ(testStore.getParam("secondString"), stringTwo);
+
+ // Verify that we can delete a parameter and it will no longer be found.
+ testStore.delParam("firstString");
+ EXPECT_THROW(testStore.getParam("firstString"), isc::dhcp::DhcpConfigError);
+
+ // Verify that the delete was safe and the store still operates.
+ EXPECT_EQ(testStore.getParam("secondString"), stringTwo);
+
+ // Verify that looking for a parameter that never existed throws.
+ ASSERT_THROW(testStore.getParam("bogusString"), isc::dhcp::DhcpConfigError);
+
+ // Verify that attempting to delete a parameter that never existed does not throw.
+ EXPECT_NO_THROW(testStore.delParam("bogusString"));
+
+ // Verify that we can empty the list.
+ testStore.clear();
+ EXPECT_THROW(testStore.getParam("secondString"), isc::dhcp::DhcpConfigError);
+}
+
+
+
class CfgMgrTest : public ::testing::Test {
public:
CfgMgrTest() {
// make sure we start with a clean configuration
CfgMgr::instance().deleteSubnets4();
CfgMgr::instance().deleteSubnets6();
+ CfgMgr::instance().deleteOptionDefs();
+ }
+
+ /// @brief generates interface-id option based on provided text
+ ///
+ /// @param text content of the option to be created
+ ///
+ /// @return pointer to the option object created
+ OptionPtr generateInterfaceId(const string& text) {
+ OptionBuffer buffer(text.begin(), text.end());
+ return OptionPtr(new Option(Option::V6, D6O_INTERFACE_ID, buffer));
}
~CfgMgrTest() {
@@ -285,6 +418,95 @@ TEST_F(CfgMgrTest, subnet6) {
EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("4000::123")));
}
+// This test verifies if the configuration manager is able to hold, select
+// and return valid subnets, based on interface names.
+TEST_F(CfgMgrTest, subnet6Interface) {
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+
+ Subnet6Ptr subnet1(new Subnet6(IOAddress("2000::"), 48, 1, 2, 3, 4));
+ Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"), 48, 1, 2, 3, 4));
+ Subnet6Ptr subnet3(new Subnet6(IOAddress("4000::"), 48, 1, 2, 3, 4));
+ subnet1->setIface("foo");
+ subnet2->setIface("bar");
+ subnet3->setIface("foobar");
+
+ // There shouldn't be any subnet configured at this stage
+ EXPECT_FALSE(cfg_mgr.getSubnet6("foo"));
+
+ cfg_mgr.addSubnet6(subnet1);
+
+ // Now we have only one subnet, any request will be served from it
+ EXPECT_EQ(subnet1, cfg_mgr.getSubnet6("foo"));
+
+ // Check that the interface name is checked even when there is
+ // only one subnet defined.
+ EXPECT_FALSE(cfg_mgr.getSubnet6("bar"));
+
+ // If we have only a single subnet and the request came from a local
+ // address, let's use that subnet
+ EXPECT_EQ(subnet1, cfg_mgr.getSubnet6(IOAddress("fe80::dead:beef")));
+
+ cfg_mgr.addSubnet6(subnet2);
+ cfg_mgr.addSubnet6(subnet3);
+
+ EXPECT_EQ(subnet3, cfg_mgr.getSubnet6("foobar"));
+ EXPECT_EQ(subnet2, cfg_mgr.getSubnet6("bar"));
+ EXPECT_FALSE(cfg_mgr.getSubnet6("xyzzy")); // no such interface
+
+ // Check that deletion of the subnets works.
+ cfg_mgr.deleteSubnets6();
+ EXPECT_FALSE(cfg_mgr.getSubnet6("foo"));
+ EXPECT_FALSE(cfg_mgr.getSubnet6("bar"));
+ EXPECT_FALSE(cfg_mgr.getSubnet6("foobar"));
+}
+
+// This test verifies if the configuration manager is able to hold, select
+// and return valid leases, based on interface-id option values
+TEST_F(CfgMgrTest, subnet6InterfaceId) {
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+
+ Subnet6Ptr subnet1(new Subnet6(IOAddress("2000::"), 48, 1, 2, 3, 4));
+ Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"), 48, 1, 2, 3, 4));
+ Subnet6Ptr subnet3(new Subnet6(IOAddress("4000::"), 48, 1, 2, 3, 4));
+
+ // interface-id options used in subnets 1,2, and 3
+ OptionPtr ifaceid1 = generateInterfaceId("relay1.eth0");
+ OptionPtr ifaceid2 = generateInterfaceId("VL32");
+ // That's a strange interface-id, but this is a real life example
+ OptionPtr ifaceid3 = generateInterfaceId("ISAM144|299|ipv6|nt:vp:1:110");
+
+ // bogus interface-id
+ OptionPtr ifaceid_bogus = generateInterfaceId("non-existent");
+
+ subnet1->setInterfaceId(ifaceid1);
+ subnet2->setInterfaceId(ifaceid2);
+ subnet3->setInterfaceId(ifaceid3);
+
+ // There shouldn't be any subnet configured at this stage
+ EXPECT_FALSE(cfg_mgr.getSubnet6(ifaceid1));
+
+ cfg_mgr.addSubnet6(subnet1);
+
+ // If we have only a single subnet and the request came from a local
+ // address, let's use that subnet
+ EXPECT_EQ(subnet1, cfg_mgr.getSubnet6(ifaceid1));
+ EXPECT_FALSE(cfg_mgr.getSubnet6(ifaceid2));
+
+ cfg_mgr.addSubnet6(subnet2);
+ cfg_mgr.addSubnet6(subnet3);
+
+ EXPECT_EQ(subnet3, cfg_mgr.getSubnet6(ifaceid3));
+ EXPECT_EQ(subnet2, cfg_mgr.getSubnet6(ifaceid2));
+ EXPECT_FALSE(cfg_mgr.getSubnet6(ifaceid_bogus));
+
+ // Check that deletion of the subnets works.
+ cfg_mgr.deleteSubnets6();
+ EXPECT_FALSE(cfg_mgr.getSubnet6(ifaceid1));
+ EXPECT_FALSE(cfg_mgr.getSubnet6(ifaceid2));
+ EXPECT_FALSE(cfg_mgr.getSubnet6(ifaceid3));
+}
+
+
// This test verifies that new DHCPv4 option spaces can be added to
// the configuration manager and that duplicated option space is
// rejected.
diff --git a/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc b/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc
new file mode 100644
index 0000000..687ef92
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc
@@ -0,0 +1,519 @@
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <config/ccsession.h>
+#include <dhcp/option.h>
+#include <dhcp/option_custom.h>
+#include <dhcp/option_int.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/subnet.h>
+#include <dhcpsrv/dhcp_parsers.h>
+#include <exceptions/exceptions.h>
+
+#include <gtest/gtest.h>
+#include <boost/foreach.hpp>
+
+#include <map>
+#include <string>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::data;
+using namespace isc::config;
+
+namespace {
+
+/// @brief DHCP Parser test fixture class
+class DhcpParserTest : public ::testing::Test {
+public:
+ /// @brief Constructor
+ ///
+ DhcpParserTest() {
+ }
+};
+
+
+/// @brief Check BooleanParser basic functionality.
+///
+/// Verifies that the parser:
+/// 1. Does not allow empty for storage.
+/// 2. Rejects a non-boolean element.
+/// 3. Builds with a valid true value.
+/// 4. Bbuils with a valid false value.
+/// 5. Updates storage upon commit.
+TEST_F(DhcpParserTest, booleanParserTest) {
+
+ const std::string name = "boolParm";
+
+ // Verify that parser does not allow empty for storage.
+ BooleanStoragePtr bs;
+ EXPECT_THROW(BooleanParser(name, bs), isc::dhcp::DhcpConfigError);
+
+ // Construct parser for testing.
+ BooleanStoragePtr storage(new BooleanStorage());
+ BooleanParser parser(name, storage);
+
+ // Verify that parser with rejects a non-boolean element.
+ ElementPtr wrong_element = Element::create("I am a string");
+ EXPECT_THROW(parser.build(wrong_element), isc::BadValue);
+
+ // Verify that parser will build with a valid true value.
+ bool test_value = true;
+ ElementPtr element = Element::create(test_value);
+ ASSERT_NO_THROW(parser.build(element));
+
+ // Verify that commit updates storage.
+ bool actual_value = !test_value;
+ parser.commit();
+ EXPECT_NO_THROW((actual_value = storage->getParam(name)));
+ EXPECT_EQ(test_value, actual_value);
+
+ // Verify that parser will build with a valid false value.
+ test_value = false;
+ element->setValue(test_value);
+ EXPECT_NO_THROW(parser.build(element));
+
+ // Verify that commit updates storage.
+ actual_value = ~test_value;
+ parser.commit();
+ EXPECT_NO_THROW((actual_value = storage->getParam(name)));
+ EXPECT_EQ(test_value, actual_value);
+}
+
+/// @brief Check StringParser basic functionality
+///
+/// Verifies that the parser:
+/// 1. Does not allow empty for storage.
+/// 2. Builds with a nont string value.
+/// 3. Builds with a string value.
+/// 4. Updates storage upon commit.
+TEST_F(DhcpParserTest, stringParserTest) {
+
+ const std::string name = "strParm";
+
+ // Verify that parser does not allow empty for storage.
+ StringStoragePtr bs;
+ EXPECT_THROW(StringParser(name, bs), isc::dhcp::DhcpConfigError);
+
+ // Construct parser for testing.
+ StringStoragePtr storage(new StringStorage());
+ StringParser parser(name, storage);
+
+ // Verify that parser with accepts a non-string element.
+ ElementPtr element = Element::create(9999);
+ EXPECT_NO_THROW(parser.build(element));
+
+ // Verify that commit updates storage.
+ parser.commit();
+ std::string actual_value;
+ EXPECT_NO_THROW((actual_value = storage->getParam(name)));
+ EXPECT_EQ("9999", actual_value);
+
+ // Verify that parser will build with a string value.
+ const std::string test_value = "test value";
+ element = Element::create(test_value);
+ ASSERT_NO_THROW(parser.build(element));
+
+ // Verify that commit updates storage.
+ parser.commit();
+ EXPECT_NO_THROW((actual_value = storage->getParam(name)));
+ EXPECT_EQ(test_value, actual_value);
+}
+
+/// @brief Check Uint32Parser basic functionality
+///
+/// Verifies that the parser:
+/// 1. Does not allow empty for storage.
+/// 2. Rejects a non-integer element.
+/// 3. Rejects a negative value.
+/// 4. Rejects too large a value.
+/// 5. Builds with value of zero.
+/// 6. Builds with a value greater than zero.
+/// 7. Updates storage upon commit.
+TEST_F(DhcpParserTest, uint32ParserTest) {
+
+ const std::string name = "intParm";
+
+ // Verify that parser does not allow empty for storage.
+ Uint32StoragePtr bs;
+ EXPECT_THROW(Uint32Parser(name, bs), isc::dhcp::DhcpConfigError);
+
+ // Construct parser for testing.
+ Uint32StoragePtr storage(new Uint32Storage());
+ Uint32Parser parser(name, storage);
+
+ // Verify that parser with rejects a non-interger element.
+ ElementPtr wrong_element = Element::create("I am a string");
+ EXPECT_THROW(parser.build(wrong_element), isc::BadValue);
+
+ // Verify that parser with rejects a negative value.
+ ElementPtr int_element = Element::create(-1);
+ EXPECT_THROW(parser.build(int_element), isc::BadValue);
+
+ // Verify that parser with rejects too large a value provided we are on
+ // 64-bit platform.
+ if (sizeof(long) > sizeof(uint32_t)) {
+ long max = (long)(std::numeric_limits<uint32_t>::max()) + 1;
+ int_element->setValue(max);
+ EXPECT_THROW(parser.build(int_element), isc::BadValue);
+ }
+
+ // Verify that parser will build with value of zero.
+ int test_value = 0;
+ int_element->setValue((long)test_value);
+ ASSERT_NO_THROW(parser.build(int_element));
+
+ // Verify that commit updates storage.
+ parser.commit();
+ uint32_t actual_value = 0;
+ EXPECT_NO_THROW((actual_value = storage->getParam(name)));
+ EXPECT_EQ(test_value, actual_value);
+
+ // Verify that parser will build with a valid positive value.
+ test_value = 77;
+ int_element->setValue((long)test_value);
+ ASSERT_NO_THROW(parser.build(int_element));
+
+ // Verify that commit updates storage.
+ parser.commit();
+ EXPECT_NO_THROW((actual_value = storage->getParam(name)));
+ EXPECT_EQ(test_value, actual_value);
+}
+
+/// @brief Check InterfaceListParser basic functionality
+///
+/// Verifies that the parser:
+/// 1. Does not allow empty for storage.
+/// 2. Does not allow name other than "interface"
+///
+/// InterfaceListParser doesn't do very much, this test will need to
+/// expand once it does.
+TEST_F(DhcpParserTest, interfaceListParserTest) {
+
+ const std::string name = "interface";
+
+ // Verify that parser constructor fails if parameter name isn't "interface"
+ EXPECT_THROW(InterfaceListConfigParser("bogus_name"), isc::BadValue);
+
+ InterfaceListConfigParser parser(name);
+ ElementPtr list_element = Element::createList();
+ list_element->add(Element::create("eth0"));
+ list_element->add(Element::create("eth1"));
+}
+
+/// @brief Test Implementation of abstract OptionDataParser class. Allows
+/// testing basic option parsing.
+class UtestOptionDataParser : public OptionDataParser {
+public:
+
+ UtestOptionDataParser(const std::string&,
+ OptionStoragePtr options, ParserContextPtr global_context)
+ :OptionDataParser("", options, global_context) {
+ }
+
+ static OptionDataParser* factory(const std::string& param_name,
+ OptionStoragePtr options, ParserContextPtr global_context) {
+ return (new UtestOptionDataParser(param_name, options, global_context));
+ }
+
+protected:
+ // Dummy out last two params since test derivation doesn't use them.
+ virtual OptionDefinitionPtr findServerSpaceOptionDefinition (
+ std::string&, uint32_t) {
+ OptionDefinitionPtr def;
+ // always return empty
+ return (def);
+ }
+};
+
+/// @brief Test Fixture class which provides basic structure for testing
+/// configuration parsing. This is essentially the same structure provided
+/// by dhcp servers.
+class ParseConfigTest : public ::testing::Test {
+public:
+ /// @brief Constructor
+ ParseConfigTest() {
+ reset_context();
+ }
+
+ ~ParseConfigTest() {
+ reset_context();
+ }
+
+ /// @brief Parses a configuration.
+ ///
+ /// Parse the given configuration, populating the context storage with
+ /// the parsed elements.
+ ///
+ /// @param config_set is the set of elements to parse.
+ /// @return returns an ConstElementPtr containing the numeric result
+ /// code and outcome comment.
+ isc::data::ConstElementPtr parseElementSet(isc::data::ConstElementPtr
+ config_set) {
+ // Answer will hold the result.
+ ConstElementPtr answer;
+ if (!config_set) {
+ answer = isc::config::createAnswer(1,
+ string("Can't parse NULL config"));
+ return (answer);
+ }
+
+ // option parsing must be done last, so save it if we hit if first
+ ParserPtr option_parser;
+
+ ConfigPair config_pair;
+ try {
+ // Iteraate over the config elements.
+ const std::map<std::string, ConstElementPtr>& values_map =
+ config_set->mapValue();
+ BOOST_FOREACH(config_pair, values_map) {
+ // Create the parser based on element name.
+ ParserPtr parser(createConfigParser(config_pair.first));
+ // Options must be parsed last
+ if (config_pair.first == "option-data") {
+ option_parser = parser;
+ } else {
+ // Anything else we can call build straight away.
+ parser->build(config_pair.second);
+ parser->commit();
+ }
+ }
+
+ // The option values parser is the next one to be run.
+ std::map<std::string, ConstElementPtr>::const_iterator
+ option_config = values_map.find("option-data");
+ if (option_config != values_map.end()) {
+ option_parser->build(option_config->second);
+ option_parser->commit();
+ }
+
+ // Everything was fine. Configuration is successful.
+ answer = isc::config::createAnswer(0, "Configuration committed.");
+ } catch (const isc::Exception& ex) {
+ answer = isc::config::createAnswer(1,
+ string("Configuration parsing failed: ") + ex.what());
+
+ } catch (...) {
+ answer = isc::config::createAnswer(1,
+ string("Configuration parsing failed"));
+ }
+
+ return (answer);
+ }
+
+ /// @brief Create an element parser based on the element name.
+ ///
+ /// Note that currently it only supports option-defs and option-data,
+ ///
+ /// @param config_id is the name of the configuration element.
+ /// @return returns a raw pointer to DhcpConfigParser. Note caller is
+ /// responsible for deleting it once no longer needed.
+ /// @throw throws NotImplemented if element name isn't supported.
+ DhcpConfigParser* createConfigParser(const std::string& config_id) {
+ DhcpConfigParser* parser = NULL;
+ if (config_id.compare("option-data") == 0) {
+ parser = new OptionDataListParser(config_id,
+ parser_context_->options_,
+ parser_context_,
+ UtestOptionDataParser::factory);
+ } else if (config_id.compare("option-def") == 0) {
+ parser = new OptionDefListParser(config_id,
+ parser_context_->option_defs_);
+ } else {
+ isc_throw(NotImplemented,
+ "Parser error: configuration parameter not supported: "
+ << config_id);
+ }
+
+ return (parser);
+ }
+
+ /// @brief Convenicee method for parsing a configuration
+ ///
+ /// Given a configuration string, convert it into Elements
+ /// and parse them.
+ /// @param config is the configuration string to parse
+ ///
+ /// @return retuns 0 if the configuration parsed successfully,
+ /// non-zero otherwise failure.
+ int parseConfiguration (std::string &config) {
+ int rcode_ = 1;
+ // Turn config into elements.
+ // Test json just to make sure its valid.
+ ElementPtr json = Element::fromJSON(config);
+ EXPECT_TRUE(json);
+ if (json) {
+ ConstElementPtr status = parseElementSet(json);
+ ConstElementPtr comment_ = parseAnswer(rcode_, status);
+ }
+
+ return (rcode_);
+ }
+
+ /// @brief Find an option definition for a given space and code within
+ /// the parser context.
+ /// @param space is the space name of the desired option.
+ /// @param code is the numeric "type" of the desired option.
+ /// @return returns an OptionDefinitionPtr which points to the found
+ /// definition or is empty.
+ /// ASSERT_ tests don't work inside functions that return values
+ OptionDefinitionPtr getOptionDef(std::string space, uint32_t code)
+ {
+ OptionDefinitionPtr def;
+ OptionDefContainerPtr defs =
+ parser_context_->option_defs_->getItems(space);
+ // Should always be able to get definitions list even if it is empty.
+ EXPECT_TRUE(defs);
+ if (defs) {
+ // Attempt to find desired definiton.
+ const OptionDefContainerTypeIndex& idx = defs->get<1>();
+ const OptionDefContainerTypeRange& range = idx.equal_range(code);
+ int cnt = std::distance(range.first, range.second);
+ EXPECT_EQ(1, cnt);
+ if (cnt == 1) {
+ def = *(idx.begin());
+ }
+ }
+ return (def);
+ }
+
+ /// @brief Find an option for a given space and code within the parser
+ /// context.
+ /// @param space is the space name of the desired option.
+ /// @param code is the numeric "type" of the desired option.
+ /// @return returns an OptionPtr which points to the found
+ /// option or is empty.
+ /// ASSERT_ tests don't work inside functions that return values
+ OptionPtr getOptionPtr(std::string space, uint32_t code)
+ {
+ OptionPtr option_ptr;
+ Subnet::OptionContainerPtr options =
+ parser_context_->options_->getItems(space);
+ // Should always be able to get options list even if it is empty.
+ EXPECT_TRUE(options);
+ if (options) {
+ // Attempt to find desired option.
+ const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
+ const Subnet::OptionContainerTypeRange& range =
+ idx.equal_range(code);
+ int cnt = std::distance(range.first, range.second);
+ EXPECT_EQ(1, cnt);
+ if (cnt == 1) {
+ Subnet::OptionDescriptor desc = *(idx.begin());
+ option_ptr = desc.option;
+ EXPECT_TRUE(option_ptr);
+ }
+ }
+
+ return (option_ptr);
+ }
+
+ /// @brief Wipes the contents of the context to allowing another parsing
+ /// during a given test if needed.
+ void reset_context(){
+ // Note set context universe to V6 as it has to be something.
+ CfgMgr::instance().deleteSubnets4();
+ CfgMgr::instance().deleteSubnets6();
+ CfgMgr::instance().deleteOptionDefs();
+ parser_context_.reset(new ParserContext(Option::V6));
+ }
+
+ /// @brief Parser context - provides storage for options and definitions
+ ParserContextPtr parser_context_;
+};
+
+/// @brief Check Basic parsing of option definitions.
+///
+/// Note that this tests basic operation of the OptionDefinitionListParser and
+/// OptionDefinitionParser. It uses a simple configuration consisting of one
+/// one definition and verifies that it is parsed and committed to storage
+/// correctly.
+TEST_F(ParseConfigTest, basicOptionDefTest) {
+
+ // Configuration string.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"ipv4-address\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\","
+ " \"encapsulate\": \"\""
+ " } ]"
+ "}";
+
+ // Verify that the configuration string parses.
+ int rcode = parseConfiguration(config);
+ ASSERT_TRUE(rcode == 0);
+
+ // Verify that the option definition can be retrieved.
+ OptionDefinitionPtr def = getOptionDef("isc", 100);
+ ASSERT_TRUE(def);
+
+ // Verify that the option definition is correct.
+ EXPECT_EQ("foo", def->getName());
+ EXPECT_EQ(100, def->getCode());
+ EXPECT_FALSE(def->getArrayType());
+ EXPECT_EQ(OPT_IPV4_ADDRESS_TYPE, def->getType());
+ EXPECT_TRUE(def->getEncapsulatedSpace().empty());
+}
+
+/// @brief Check Basic parsing of options.
+///
+/// Note that this tests basic operation of the OptionDataListParser and
+/// OptionDataParser. It uses a simple configuration consisting of one
+/// one definition and matching option data. It verifies that the option
+/// is parsed and committed to storage correctly.
+TEST_F(ParseConfigTest, basicOptionDataTest) {
+
+ // Configuration string.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"ipv4-address\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\","
+ " \"encapsulate\": \"\""
+ " } ], "
+ " \"option-data\": [ {"
+ " \"name\": \"foo\","
+ " \"space\": \"isc\","
+ " \"code\": 100,"
+ " \"data\": \"192.168.2.1\","
+ " \"csv-format\": True"
+ " } ]"
+ "}";
+
+ // Verify that the configuration string parses.
+ int rcode = parseConfiguration(config);
+ ASSERT_TRUE(rcode == 0);
+
+ // Verify that the option can be retrieved.
+ OptionPtr opt_ptr = getOptionPtr("isc", 100);
+ ASSERT_TRUE(opt_ptr);
+
+ // Verify that the option definition is correct.
+ std::string val = "type=100, len=4, data fields:\n "
+ " #0 192.168.2.1 ( ipv4-address ) \n";
+
+ EXPECT_EQ(val, opt_ptr->toText());
+}
+
+}; // Anonymous namespace
+
diff --git a/src/lib/dhcpsrv/tests/lease_mgr_unittest.cc b/src/lib/dhcpsrv/tests/lease_mgr_unittest.cc
index 6103a96..124301a 100644
--- a/src/lib/dhcpsrv/tests/lease_mgr_unittest.cc
+++ b/src/lib/dhcpsrv/tests/lease_mgr_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -312,8 +312,8 @@ TEST(Lease4, OperatorEquals) {
// Check when the leases are equal.
Lease4 lease1(ADDRESS, HWADDR, sizeof(HWADDR),
- CLIENTID, sizeof(CLIENTID), VALID_LIFETIME, current_time, 0, 0,
- SUBNET_ID);
+ CLIENTID, sizeof(CLIENTID), VALID_LIFETIME, current_time, 0,
+ 0, SUBNET_ID);
Lease4 lease2(ADDRESS, HWADDR, sizeof(HWADDR),
CLIENTID, sizeof(CLIENTID), VALID_LIFETIME, current_time, 0, 0,
SUBNET_ID);
@@ -440,8 +440,8 @@ TEST(Lease6, Lease6Constructor) {
// Other values
uint8_t llt[] = {0, 1, 2, 3, 4, 5, 6, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf};
DuidPtr duid(new DUID(llt, sizeof(llt)));
- uint32_t iaid = 7; // just a number
- SubnetID subnet_id = 8; // just another number
+ uint32_t iaid = 7; // Just a number
+ SubnetID subnet_id = 8; // Just another number
for (int i = 0; i < sizeof(ADDRESS) / sizeof(ADDRESS[0]); ++i) {
IOAddress addr(ADDRESS[i]);
@@ -462,9 +462,10 @@ TEST(Lease6, Lease6Constructor) {
// Lease6 must be instantiated with a DUID, not with NULL pointer
IOAddress addr(ADDRESS[0]);
- EXPECT_THROW(new Lease6(Lease6::LEASE_IA_NA, addr,
- DuidPtr(), iaid, 100, 200, 50, 80,
- subnet_id), InvalidOperation);
+ Lease6Ptr lease2;
+ EXPECT_THROW(lease2.reset(new Lease6(Lease6::LEASE_IA_NA, addr,
+ DuidPtr(), iaid, 100, 200, 50, 80,
+ subnet_id)), InvalidOperation);
}
/// @brief Lease6 Equality Test
@@ -611,21 +612,21 @@ TEST(Lease6, Lease6Expired) {
const IOAddress addr("2001:db8:1::456");
const uint8_t duid_array[] = {0, 1, 2, 3, 4, 5, 6, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf};
const DuidPtr duid(new DUID(duid_array, sizeof(duid_array)));
- const uint32_t iaid = 7; // just a number
- const SubnetID subnet_id = 8; // just another number
+ const uint32_t iaid = 7; // Just a number
+ const SubnetID subnet_id = 8; // Just another number
Lease6 lease(Lease6::LEASE_IA_NA, addr, duid, iaid, 100, 200, 50, 80,
subnet_id);
- // case 1: a second before expiration
+ // Case 1: a second before expiration
lease.cltt_ = time(NULL) - 100;
lease.valid_lft_ = 101;
EXPECT_FALSE(lease.expired());
- // case 2: the lease will expire after this second is concluded
+ // Case 2: the lease will expire after this second is concluded
lease.cltt_ = time(NULL) - 101;
EXPECT_FALSE(lease.expired());
- // case 3: the lease is expired
+ // Case 3: the lease is expired
lease.cltt_ = time(NULL) - 102;
EXPECT_TRUE(lease.expired());
}
diff --git a/src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc b/src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc
index ddb2645..5b2fa9e 100644
--- a/src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc
+++ b/src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc
@@ -18,6 +18,7 @@
#include <dhcpsrv/lease_mgr_factory.h>
#include <dhcpsrv/mysql_lease_mgr.h>
#include <dhcpsrv/tests/test_utils.h>
+#include <exceptions/exceptions.h>
#include <gtest/gtest.h>
@@ -118,21 +119,16 @@ validConnectionString() {
// There is no error checking in this code: if something fails, one of the
// tests will (should) fall over.
void destroySchema() {
- // Initialise
- MYSQL handle;
- (void) mysql_init(&handle);
+ MySqlHolder mysql;
// Open database
- (void) mysql_real_connect(&handle, "localhost", "keatest",
+ (void) mysql_real_connect(mysql, "localhost", "keatest",
"keatest", "keatest", 0, NULL, 0);
// Get rid of everything in it.
for (int i = 0; destroy_statement[i] != NULL; ++i) {
- (void) mysql_query(&handle, destroy_statement[i]);
+ (void) mysql_query(mysql, destroy_statement[i]);
}
-
- // ... and close
- (void) mysql_close(&handle);
}
// @brief Create the Schema
@@ -142,21 +138,16 @@ void destroySchema() {
// There is no error checking in this code: if it fails, one of the tests
// will fall over.
void createSchema() {
- // Initialise
- MYSQL handle;
- (void) mysql_init(&handle);
+ MySqlHolder mysql;
// Open database
- (void) mysql_real_connect(&handle, "localhost", "keatest",
+ (void) mysql_real_connect(mysql, "localhost", "keatest",
"keatest", "keatest", 0, NULL, 0);
// Execute creation statements.
for (int i = 0; create_statement[i] != NULL; ++i) {
- (void) mysql_query(&handle, create_statement[i]);
+ (void) mysql_query(mysql, create_statement[i]);
}
-
- // ... and close
- (void) mysql_close(&handle);
}
/// @brief Test fixture class for testing MySQL Lease Manager
@@ -317,8 +308,7 @@ public:
} else if (address == straddress4_[7]) {
lease->hwaddr_ = vector<uint8_t>(); // Empty
- lease->client_id_ = ClientIdPtr(
- new ClientId(vector<uint8_t>())); // Empty
+ lease->client_id_ = ClientIdPtr(); // Empty
lease->valid_lft_ = 7975;
lease->cltt_ = 213876;
lease->subnet_id_ = 19;
@@ -712,6 +702,83 @@ TEST_F(MySqlLeaseMgrTest, basicLease4) {
detailCompareLease(leases[2], l_returned);
}
+/// @brief Basic Lease4 Checks
+///
+/// Checks that the addLease, getLease4(by address), getLease4(hwaddr,subnet_id),
+/// updateLease4() and deleteLease (IPv4 address) can handle NULL client-id.
+/// (client-id is optional and may not be present)
+TEST_F(MySqlLeaseMgrTest, lease4NullClientId) {
+ // Get the leases to be used for the test.
+ vector<Lease4Ptr> leases = createLeases4();
+
+ // Let's clear client-id pointers
+ leases[1]->client_id_ = ClientIdPtr();
+ leases[2]->client_id_ = ClientIdPtr();
+ leases[3]->client_id_ = ClientIdPtr();
+
+ // Start the tests. Add three leases to the database, read them back and
+ // check they are what we think they are.
+ EXPECT_TRUE(lmptr_->addLease(leases[1]));
+ EXPECT_TRUE(lmptr_->addLease(leases[2]));
+ EXPECT_TRUE(lmptr_->addLease(leases[3]));
+ lmptr_->commit();
+
+ // Reopen the database to ensure that they actually got stored.
+ reopen();
+
+ Lease4Ptr l_returned = lmptr_->getLease4(ioaddress4_[1]);
+ ASSERT_TRUE(l_returned);
+ detailCompareLease(leases[1], l_returned);
+
+ l_returned = lmptr_->getLease4(ioaddress4_[2]);
+ ASSERT_TRUE(l_returned);
+ detailCompareLease(leases[2], l_returned);
+
+ l_returned = lmptr_->getLease4(ioaddress4_[3]);
+ ASSERT_TRUE(l_returned);
+ detailCompareLease(leases[3], l_returned);
+
+ // Check that we can't add a second lease with the same address
+ EXPECT_FALSE(lmptr_->addLease(leases[1]));
+
+ // Check that we can get the lease by HWAddr
+ HWAddr tmp(leases[2]->hwaddr_, HTYPE_ETHER);
+ Lease4Collection returned = lmptr_->getLease4(tmp);
+ ASSERT_EQ(1, returned.size());
+ detailCompareLease(leases[2], *returned.begin());
+
+ l_returned = lmptr_->getLease4(tmp, leases[2]->subnet_id_);
+ ASSERT_TRUE(l_returned);
+ detailCompareLease(leases[2], l_returned);
+
+
+ // Check that we can update the lease
+ // Modify some fields in lease 1 (not the address) and update it.
+ ++leases[1]->subnet_id_;
+ leases[1]->valid_lft_ *= 2;
+ lmptr_->updateLease4(leases[1]);
+
+ // ... and check that the lease is indeed updated
+ l_returned = lmptr_->getLease4(ioaddress4_[1]);
+ ASSERT_TRUE(l_returned);
+ detailCompareLease(leases[1], l_returned);
+
+
+
+ // Delete a lease, check that it's gone, and that we can't delete it
+ // a second time.
+ EXPECT_TRUE(lmptr_->deleteLease(ioaddress4_[1]));
+ l_returned = lmptr_->getLease4(ioaddress4_[1]);
+ EXPECT_FALSE(l_returned);
+ EXPECT_FALSE(lmptr_->deleteLease(ioaddress4_[1]));
+
+ // Check that the second address is still there.
+ l_returned = lmptr_->getLease4(ioaddress4_[2]);
+ ASSERT_TRUE(l_returned);
+ detailCompareLease(leases[2], l_returned);
+
+}
+
/// @brief Basic Lease6 Checks
///
/// Checks that the addLease, getLease6 (by address) and deleteLease (with an
@@ -791,14 +858,14 @@ TEST_F(MySqlLeaseMgrTest, getLease4Hwaddr) {
// Repeat test with just one expected match
// @todo: Simply use HWAddr directly once 2589 is implemented
returned = lmptr_->getLease4(HWAddr(leases[2]->hwaddr_, HTYPE_ETHER));
- EXPECT_EQ(1, returned.size());
+ ASSERT_EQ(1, returned.size());
detailCompareLease(leases[2], *returned.begin());
// Check that an empty vector is valid
EXPECT_TRUE(leases[7]->hwaddr_.empty());
// @todo: Simply use HWAddr directly once 2589 is implemented
returned = lmptr_->getLease4(HWAddr(leases[7]->hwaddr_, HTYPE_ETHER));
- EXPECT_EQ(1, returned.size());
+ ASSERT_EQ(1, returned.size());
detailCompareLease(leases[7], *returned.begin());
// Try to get something with invalid hardware address
@@ -817,28 +884,23 @@ TEST_F(MySqlLeaseMgrTest, getLease4HwaddrSize) {
vector<Lease4Ptr> leases = createLeases4();
// Now add leases with increasing hardware address size.
- for (uint8_t i = 0; i <= Lease4::HWADDR_MAX; ++i) {
+ for (uint8_t i = 0; i <= HWAddr::MAX_HWADDR_LEN; ++i) {
leases[1]->hwaddr_.resize(i, i);
EXPECT_TRUE(lmptr_->addLease(leases[1]));
// @todo: Simply use HWAddr directly once 2589 is implemented
- Lease4Collection returned = lmptr_->getLease4(HWAddr(leases[1]->hwaddr_, HTYPE_ETHER));
+ Lease4Collection returned =
+ lmptr_->getLease4(HWAddr(leases[1]->hwaddr_, HTYPE_ETHER));
+
ASSERT_EQ(1, returned.size());
detailCompareLease(leases[1], *returned.begin());
(void) lmptr_->deleteLease(leases[1]->addr_);
}
- // Expect some problem when accessing a lease that had too long a hardware
- // address. (The 42 is a random value put in each byte of the address.)
- // In fact the address is stored in a truncated form, so we won't find it
- // when we look.
- // @todo Check if there is some way of detecting that data added
- // to the database is truncated. There does not appear to
- // be any indication in the C API.
- leases[1]->hwaddr_.resize(Lease4::HWADDR_MAX + 100, 42);
- EXPECT_TRUE(lmptr_->addLease(leases[1]));
- // @todo: Simply use HWAddr directly once 2589 is implemented
- Lease4Collection returned = lmptr_->getLease4(HWAddr(leases[1]->hwaddr_, HTYPE_ETHER));
- EXPECT_EQ(0, returned.size());
+ // Database should not let us add one that is too big
+ // (The 42 is a random value put in each byte of the address.)
+ // @todo: 2589 will make this test impossible
+ leases[1]->hwaddr_.resize(HWAddr::MAX_HWADDR_LEN + 100, 42);
+ EXPECT_THROW(lmptr_->addLease(leases[1]), isc::dhcp::DbOperationError);
}
/// @brief Check GetLease4 methods - access by Hardware Address & Subnet ID
@@ -855,8 +917,9 @@ TEST_F(MySqlLeaseMgrTest, getLease4HwaddrSubnetId) {
// Get the leases matching the hardware address of lease 1 and
// subnet ID of lease 1. Result should be a single lease - lease 1.
// @todo: Simply use HWAddr directly once 2589 is implemented
- Lease4Ptr returned = lmptr_->getLease4(HWAddr(leases[1]->hwaddr_, HTYPE_ETHER),
- leases[1]->subnet_id_);
+ Lease4Ptr returned = lmptr_->getLease4(HWAddr(leases[1]->hwaddr_,
+ HTYPE_ETHER), leases[1]->subnet_id_);
+
ASSERT_TRUE(returned);
detailCompareLease(leases[1], returned);
@@ -891,8 +954,9 @@ TEST_F(MySqlLeaseMgrTest, getLease4HwaddrSubnetId) {
leases[1]->addr_ = leases[2]->addr_;
EXPECT_TRUE(lmptr_->addLease(leases[1]));
// @todo: Simply use HWAddr directly once 2589 is implemented
- EXPECT_THROW(returned = lmptr_->getLease4(HWAddr(leases[1]->hwaddr_, HTYPE_ETHER),
- leases[1]->subnet_id_),
+ EXPECT_THROW(returned = lmptr_->getLease4(HWAddr(leases[1]->hwaddr_,
+ HTYPE_ETHER),
+ leases[1]->subnet_id_),
isc::dhcp::MultipleRecords);
// Delete all leases in the database
@@ -913,28 +977,22 @@ TEST_F(MySqlLeaseMgrTest, getLease4HwaddrSubnetIdSize) {
// Now add leases with increasing hardware address size and check
// that they can be retrieved.
- for (uint8_t i = 0; i <= Lease4::HWADDR_MAX; ++i) {
+ for (uint8_t i = 0; i <= HWAddr::MAX_HWADDR_LEN; ++i) {
leases[1]->hwaddr_.resize(i, i);
EXPECT_TRUE(lmptr_->addLease(leases[1]));
// @todo: Simply use HWAddr directly once 2589 is implemented
- Lease4Ptr returned = lmptr_->getLease4(HWAddr(leases[1]->hwaddr_, HTYPE_ETHER),
+ Lease4Ptr returned = lmptr_->getLease4(HWAddr(leases[1]->hwaddr_,
+ HTYPE_ETHER),
leases[1]->subnet_id_);
ASSERT_TRUE(returned);
detailCompareLease(leases[1], returned);
(void) lmptr_->deleteLease(leases[1]->addr_);
}
- // Expect some error when getting a lease with too long a hardware
- // address. Set the contents of each byte to 42, a random value.
- // @todo Check if there is some way of detecting that data added
- // to the database is truncated. There does not appear to
- // be any indication in the C API.
- leases[1]->hwaddr_.resize(Lease4::HWADDR_MAX + 100, 42);
- EXPECT_TRUE(lmptr_->addLease(leases[1]));
- // @todo: Simply use HWAddr directly once 2589 is implemented
- Lease4Ptr returned = lmptr_->getLease4(HWAddr(leases[1]->hwaddr_, HTYPE_ETHER),
- leases[1]->subnet_id_);
- EXPECT_FALSE(returned);
+ // Database should not let us add one that is too big
+ // (The 42 is a random value put in each byte of the address.)
+ leases[1]->hwaddr_.resize(HWAddr::MAX_HWADDR_LEN + 100, 42);
+ EXPECT_THROW(lmptr_->addLease(leases[1]), isc::dhcp::DbOperationError);
}
/// @brief Check GetLease4 methods - access by Client ID
@@ -968,13 +1026,14 @@ TEST_F(MySqlLeaseMgrTest, getLease4ClientId) {
// Repeat test with just one expected match
returned = lmptr_->getLease4(*leases[3]->client_id_);
- EXPECT_EQ(1, returned.size());
+ ASSERT_EQ(1, returned.size());
detailCompareLease(leases[3], *returned.begin());
- // Check that an empty vector is valid
- EXPECT_TRUE(leases[7]->client_id_->getClientId().empty());
- returned = lmptr_->getLease4(leases[7]->hwaddr_);
- EXPECT_EQ(1, returned.size());
+ // Check that client-id is NULL
+ EXPECT_FALSE(leases[7]->client_id_);
+ HWAddr tmp(leases[7]->hwaddr_, HTYPE_ETHER);
+ returned = lmptr_->getLease4(tmp);
+ ASSERT_EQ(1, returned.size());
detailCompareLease(leases[7], *returned.begin());
// Try to get something with invalid client ID
@@ -998,7 +1057,11 @@ TEST_F(MySqlLeaseMgrTest, getLease4ClientIdSize) {
// ClientId::MAX_CLIENT_ID_LEN is used in an EXPECT_EQ.
int client_id_max = ClientId::MAX_CLIENT_ID_LEN;
EXPECT_EQ(128, client_id_max);
- for (uint8_t i = 0; i <= client_id_max; i += 16) {
+
+ int client_id_min = ClientId::MIN_CLIENT_ID_LEN;
+ EXPECT_EQ(2, client_id_min); // See RFC2132, section 9.14
+
+ for (uint8_t i = client_id_min; i <= client_id_max; i += 16) {
vector<uint8_t> clientid_vec(i, i);
leases[1]->client_id_.reset(new ClientId(clientid_vec));
EXPECT_TRUE(lmptr_->addLease(leases[1]));
@@ -1107,13 +1170,17 @@ TEST_F(MySqlLeaseMgrTest, getLease6DuidIaidSize) {
// For speed, go from 0 to 128 is steps of 16.
int duid_max = DUID::MAX_DUID_LEN;
EXPECT_EQ(128, duid_max);
- for (uint8_t i = 0; i <= duid_max; i += 16) {
+
+ int duid_min = DUID::MIN_DUID_LEN;
+ EXPECT_EQ(1, duid_min);
+
+ for (uint8_t i = duid_min; i <= duid_max; i += 16) {
vector<uint8_t> duid_vec(i, i);
leases[1]->duid_.reset(new DUID(duid_vec));
EXPECT_TRUE(lmptr_->addLease(leases[1]));
Lease6Collection returned = lmptr_->getLease6(*leases[1]->duid_,
leases[1]->iaid_);
- EXPECT_EQ(1, returned.size());
+ ASSERT_EQ(1, returned.size());
detailCompareLease(leases[1], *returned.begin());
(void) lmptr_->deleteLease(leases[1]->addr_);
}
@@ -1172,7 +1239,11 @@ TEST_F(MySqlLeaseMgrTest, getLease6DuidIaidSubnetIdSize) {
// For speed, go from 0 to 128 is steps of 16.
int duid_max = DUID::MAX_DUID_LEN;
EXPECT_EQ(128, duid_max);
- for (uint8_t i = 0; i <= duid_max; i += 16) {
+
+ int duid_min = DUID::MIN_DUID_LEN;
+ EXPECT_EQ(1, duid_min);
+
+ for (uint8_t i = duid_min; i <= duid_max; i += 16) {
vector<uint8_t> duid_vec(i, i);
leases[1]->duid_.reset(new DUID(duid_vec));
EXPECT_TRUE(lmptr_->addLease(leases[1]));
diff --git a/src/lib/dhcpsrv/tests/run_unittests.cc b/src/lib/dhcpsrv/tests/run_unittests.cc
index d333a6f..dcc0204 100644
--- a/src/lib/dhcpsrv/tests/run_unittests.cc
+++ b/src/lib/dhcpsrv/tests/run_unittests.cc
@@ -12,6 +12,7 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
+#include <config.h>
#include <log/logger_support.h>
#include <gtest/gtest.h>
diff --git a/src/lib/dhcpsrv/tests/schema_copy.h b/src/lib/dhcpsrv/tests/schema_copy.h
index 48a11ca..9ebd057 100644
--- a/src/lib/dhcpsrv/tests/schema_copy.h
+++ b/src/lib/dhcpsrv/tests/schema_copy.h
@@ -51,6 +51,10 @@ const char* create_statement[] = {
"subnet_id INT UNSIGNED"
") ENGINE = INNODB",
+ "CREATE INDEX lease4_by_hwaddr_subnet_id ON lease4 (hwaddr, subnet_id)",
+
+ "CREATE INDEX lease4_by_client_id_subnet_id ON lease4 (client_id, subnet_id)",
+
"CREATE TABLE lease6 ("
"address VARCHAR(39) PRIMARY KEY NOT NULL,"
"duid VARBINARY(128),"
@@ -63,6 +67,8 @@ const char* create_statement[] = {
"prefix_len TINYINT UNSIGNED"
") ENGINE = INNODB",
+ "CREATE INDEX lease6_by_iaid_subnet_id_duid ON lease6 (iaid, subnet_id, duid)",
+
"CREATE TABLE lease6_types ("
"lease_type TINYINT PRIMARY KEY NOT NULL,"
"name VARCHAR(5)"
diff --git a/src/lib/dhcpsrv/tests/subnet_unittest.cc b/src/lib/dhcpsrv/tests/subnet_unittest.cc
index 1f0ef8b..fbcebcb 100644
--- a/src/lib/dhcpsrv/tests/subnet_unittest.cc
+++ b/src/lib/dhcpsrv/tests/subnet_unittest.cc
@@ -16,6 +16,7 @@
#include <asiolink/io_address.h>
#include <dhcp/option.h>
+#include <dhcp/dhcp6.h>
#include <dhcpsrv/subnet.h>
#include <exceptions/exceptions.h>
@@ -516,4 +517,19 @@ TEST(Subnet6Test, iface) {
EXPECT_EQ("en1", subnet.getIface());
}
+// This trivial test checks if the interface-id option can be set and
+// later retrieved for a subnet6 object.
+TEST(Subnet6Test, interfaceId) {
+ // Create as subnet to add options to it.
+ Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4));
+
+ EXPECT_FALSE(subnet->getInterfaceId());
+
+ OptionPtr option(new Option(Option::V6, D6O_INTERFACE_ID, OptionBuffer(10, 0xFF)));
+ subnet->setInterfaceId(option);
+
+ EXPECT_EQ(option, subnet->getInterfaceId());
+
+}
+
};
diff --git a/src/lib/dhcpsrv/tests/test_utils.cc b/src/lib/dhcpsrv/tests/test_utils.cc
index 3c69dbe..ea62225 100644
--- a/src/lib/dhcpsrv/tests/test_utils.cc
+++ b/src/lib/dhcpsrv/tests/test_utils.cc
@@ -27,7 +27,23 @@ detailCompareLease(const Lease4Ptr& first, const Lease4Ptr& second) {
// thrown for IPv6 addresses.
EXPECT_EQ(first->addr_.toText(), second->addr_.toText());
EXPECT_TRUE(first->hwaddr_ == second->hwaddr_);
- EXPECT_TRUE(*first->client_id_ == *second->client_id_);
+ if (first->client_id_ && second->client_id_) {
+ EXPECT_TRUE(*first->client_id_ == *second->client_id_);
+ } else {
+ if (first->client_id_ && !second->client_id_) {
+
+ ADD_FAILURE() << "Client-id present in first lease ("
+ << first->client_id_->getClientId().size()
+ << " bytes), but missing in second.";
+ }
+ if (!first->client_id_ && second->client_id_) {
+ ADD_FAILURE() << "Client-id missing in first lease, but present in second ("
+ << second->client_id_->getClientId().size()
+ << " bytes).";
+ }
+ // else here would mean that both leases do not have client_id_
+ // which makes them equal in that regard. It is ok.
+ }
EXPECT_EQ(first->valid_lft_, second->valid_lft_);
EXPECT_EQ(first->cltt_, second->cltt_);
EXPECT_EQ(first->subnet_id_, second->subnet_id_);
diff --git a/src/lib/dns/gen-rdatacode.py.in b/src/lib/dns/gen-rdatacode.py.in
index d0a2576..a49f72f 100755
--- a/src/lib/dns/gen-rdatacode.py.in
+++ b/src/lib/dns/gen-rdatacode.py.in
@@ -1,6 +1,6 @@
#!@PYTHON@
-# Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+# Copyright (C) 2010-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
@@ -32,17 +32,25 @@ import sys
#
# Example:
# new_rdata_factory_users = [('a', 'in'), ('a', 'ch'), ('soa', 'generic')]
-new_rdata_factory_users = [('a', 'in'), ('aaaa', 'in'),
+new_rdata_factory_users = [('a', 'in'),
+ ('aaaa', 'in'),
+ ('afsdb', 'generic'),
('cname', 'generic'),
+ ('dhcid', 'in'),
('dlv', 'generic'),
('dname', 'generic'),
+ ('dnskey', 'generic'),
('ds', 'generic'),
('hinfo', 'generic'),
('naptr', 'generic'),
('mx', 'generic'),
('ns', 'generic'),
('nsec', 'generic'),
+ ('nsec3', 'generic'),
+ ('nsec3param', 'generic'),
+ ('opt', 'generic'),
('ptr', 'generic'),
+ ('rrsig', 'generic'),
('soa', 'generic'),
('spf', 'generic'),
('srv', 'in'),
diff --git a/src/lib/dns/master_loader.cc b/src/lib/dns/master_loader.cc
index 1c822ea..5c96563 100644
--- a/src/lib/dns/master_loader.cc
+++ b/src/lib/dns/master_loader.cc
@@ -400,7 +400,7 @@ private:
const Name zone_origin_;
Name active_origin_; // The origin used during parsing
// (modifiable by $ORIGIN)
- shared_ptr<Name> last_name_; // Last seen name (for INITAL_WS handling)
+ shared_ptr<Name> last_name_; // Last seen name (for INITIAL_WS handling)
const RRClass zone_class_;
MasterLoaderCallbacks callbacks_;
const AddRRCallback add_callback_;
diff --git a/src/lib/dns/nsec3hash.cc b/src/lib/dns/nsec3hash.cc
index 8fe3cf1..0e03798 100644
--- a/src/lib/dns/nsec3hash.cc
+++ b/src/lib/dns/nsec3hash.cc
@@ -45,7 +45,7 @@ namespace {
/// calculation specified in RFC5155.
///
/// Currently the only pre-defined algorithm in the RFC is SHA1. So we don't
-/// over-generalize it at the moment, and rather hardocde it and assume that
+/// over-generalize it at the moment, and rather hardcode it and assume that
/// specific algorithm.
///
/// The implementation details are only open within this file, but to avoid
diff --git a/src/lib/dns/python/pydnspp_common.h b/src/lib/dns/python/pydnspp_common.h
index 4095f54..8e498b3 100644
--- a/src/lib/dns/python/pydnspp_common.h
+++ b/src/lib/dns/python/pydnspp_common.h
@@ -50,7 +50,7 @@ int addClassVariable(PyTypeObject& c, const char* name, PyObject* obj);
/// \brief Initialize a wrapped class type, and add to module
///
-/// The type object is initalized, and its refcount is increased after
+/// The type object is initialized, and its refcount is increased after
/// successful addition to the module.
///
/// \param type The type object to initialize
diff --git a/src/lib/dns/python/rrset_python.cc b/src/lib/dns/python/rrset_python.cc
index 2992522..dc2af22 100644
--- a/src/lib/dns/python/rrset_python.cc
+++ b/src/lib/dns/python/rrset_python.cc
@@ -293,8 +293,16 @@ RRset_addRdata(PyObject* self, PyObject* args) {
PyErr_Clear();
PyErr_SetString(PyExc_TypeError,
"Rdata type to add must match type of RRset");
- return (NULL);
+ } catch (const exception& ex) {
+ const string ex_what =
+ "Unexpected failure adding rrset Rdata: " +
+ string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError,
+ "Unexpected failure adding rrset Rdata");
}
+ return (NULL);
}
PyObject*
diff --git a/src/lib/dns/python/tests/Makefile.am b/src/lib/dns/python/tests/Makefile.am
index de6b010..4574303 100644
--- a/src/lib/dns/python/tests/Makefile.am
+++ b/src/lib/dns/python/tests/Makefile.am
@@ -28,7 +28,7 @@ EXTRA_DIST += testutil.py
# required by loadable python modules.
LIBRARY_PATH_PLACEHOLDER =
if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/util/threads/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$$$(ENV_LIBRARY_PATH)
endif
# test using command-line arguments, so use check-local target instead of TESTS
diff --git a/src/lib/dns/python/tests/nsec3hash_python_test.py b/src/lib/dns/python/tests/nsec3hash_python_test.py
index 320529a..f3d1cfb 100644
--- a/src/lib/dns/python/tests/nsec3hash_python_test.py
+++ b/src/lib/dns/python/tests/nsec3hash_python_test.py
@@ -41,7 +41,7 @@ class NSEC3HashTest(unittest.TestCase):
RRClass.IN,
"1 0 12 aabbccdd"), 1)
- # Invaid type of RDATA
+ # Invalid type of RDATA
self.assertRaises(TypeError, NSEC3Hash, Rdata(RRType.A, RRClass.IN,
"192.0.2.1"))
diff --git a/src/lib/dns/python/tests/rrset_python_test.py b/src/lib/dns/python/tests/rrset_python_test.py
index 010b60c..0ffcdbe 100644
--- a/src/lib/dns/python/tests/rrset_python_test.py
+++ b/src/lib/dns/python/tests/rrset_python_test.py
@@ -78,7 +78,12 @@ class TestModuleSpec(unittest.TestCase):
def test_add_rdata(self):
# no iterator to read out yet (TODO: add addition test once implemented)
- self.assertRaises(TypeError, self.rrset_a.add_rdata,
+ # This should result in TypeError, but FreeBSD 9.1 cannot correctly
+ # catch the expected internal C++ exception, resulting in SystemError.
+ # In general it's not a good practice to weaken the test condition for
+ # a limited set of buggy environment, but this seems to be the only
+ # case it could fail this way, so we'd live with it. See #2887.
+ self.assertRaises((TypeError, SystemError), self.rrset_a.add_rdata,
Rdata(RRType("NS"), RRClass("IN"), "test.name."))
def test_to_text(self):
diff --git a/src/lib/dns/rdata/generic/afsdb_18.cc b/src/lib/dns/rdata/generic/afsdb_18.cc
index ec76ee0..cf6a35c 100644
--- a/src/lib/dns/rdata/generic/afsdb_18.cc
+++ b/src/lib/dns/rdata/generic/afsdb_18.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -25,9 +25,12 @@
#include <boost/lexical_cast.hpp>
+#include <dns/rdata/generic/detail/lexer_util.h>
+
using namespace std;
+using boost::lexical_cast;
using namespace isc::util;
-using namespace isc::util::str;
+using isc::dns::rdata::generic::detail::createNameFromLexer;
// BEGIN_ISC_NAMESPACE
// BEGIN_RDATA_NAMESPACE
@@ -52,24 +55,58 @@ using namespace isc::util::str;
AFSDB::AFSDB(const std::string& afsdb_str) :
subtype_(0), server_(Name::ROOT_NAME())
{
- istringstream iss(afsdb_str);
-
try {
- const uint32_t subtype = tokenToNum<int32_t, 16>(getToken(iss));
- const Name servername(getToken(iss));
+ std::istringstream ss(afsdb_str);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
- if (!iss.eof()) {
- isc_throw(InvalidRdataText, "Unexpected input for AFSDB"
- "RDATA: " << afsdb_str);
+ createFromLexer(lexer, NULL);
+
+ if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText, "extra input text for AFSDB: "
+ << afsdb_str);
}
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText, "Failed to construct AFSDB from '" <<
+ afsdb_str << "': " << ex.what());
+ }
+}
- subtype_ = subtype;
- server_ = servername;
+/// \brief Constructor with a context of MasterLexer.
+///
+/// The \c lexer should point to the beginning of valid textual representation
+/// of an AFSDB RDATA. The SERVER field can be non-absolute if \c origin
+/// is non-NULL, in which case \c origin is used to make it absolute.
+/// It must not be represented as a quoted string.
+///
+/// The SUBTYPE field must be a valid decimal representation of an
+/// unsigned 16-bit integer.
+///
+/// \throw MasterLexer::LexerError General parsing error such as missing field.
+/// \throw Other Exceptions from the Name and RRTTL constructors if
+/// construction of textual fields as these objects fail.
+///
+/// \param lexer A \c MasterLexer object parsing a master file for the
+/// RDATA to be created
+/// \param origin If non NULL, specifies the origin of SERVER when it
+/// is non-absolute.
+AFSDB::AFSDB(MasterLexer& lexer, const Name* origin,
+ MasterLoader::Options, MasterLoaderCallbacks&) :
+ subtype_(0), server_(".")
+{
+ createFromLexer(lexer, origin);
+}
- } catch (const StringTokenError& ste) {
- isc_throw(InvalidRdataText, "Invalid AFSDB text: " <<
- ste.what() << ": " << afsdb_str);
+void
+AFSDB::createFromLexer(MasterLexer& lexer, const Name* origin)
+{
+ const uint32_t num = lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ if (num > 65535) {
+ isc_throw(InvalidRdataText, "Invalid AFSDB subtype: " << num);
}
+ subtype_ = static_cast<uint16_t>(num);
+
+ server_ = createNameFromLexer(lexer, origin);
}
/// \brief Constructor from wire-format data.
@@ -111,7 +148,7 @@ AFSDB::operator=(const AFSDB& source) {
/// \return A \c string object that represents the \c AFSDB object.
string
AFSDB::toText() const {
- return (boost::lexical_cast<string>(subtype_) + " " + server_.toText());
+ return (lexical_cast<string>(subtype_) + " " + server_.toText());
}
/// \brief Render the \c AFSDB in the wire format without name compression.
diff --git a/src/lib/dns/rdata/generic/afsdb_18.h b/src/lib/dns/rdata/generic/afsdb_18.h
index 4a46775..359a6e4 100644
--- a/src/lib/dns/rdata/generic/afsdb_18.h
+++ b/src/lib/dns/rdata/generic/afsdb_18.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -61,6 +61,8 @@ public:
uint16_t getSubtype() const;
private:
+ void createFromLexer(MasterLexer& lexer, const Name* origin);
+
uint16_t subtype_;
Name server_;
};
diff --git a/src/lib/dns/rdata/generic/detail/nsec3param_common.cc b/src/lib/dns/rdata/generic/detail/nsec3param_common.cc
index 6eea2c7..caa92ce 100644
--- a/src/lib/dns/rdata/generic/detail/nsec3param_common.cc
+++ b/src/lib/dns/rdata/generic/detail/nsec3param_common.cc
@@ -39,41 +39,33 @@ namespace detail {
namespace nsec3 {
ParseNSEC3ParamResult
-parseNSEC3ParamText(const char* const rrtype_name,
- const string& rdata_str, istringstream& iss,
- vector<uint8_t>& salt)
+parseNSEC3ParamFromLexer(const char* const rrtype_name,
+ MasterLexer& lexer, vector<uint8_t>& salt)
{
- unsigned int hashalg, flags, iterations;
- string iterations_str, salthex;
-
- iss >> hashalg >> flags >> iterations_str >> salthex;
- if (iss.bad() || iss.fail()) {
- isc_throw(InvalidRdataText, "Invalid " << rrtype_name <<
- " text: " << rdata_str);
- }
+ const uint32_t hashalg =
+ lexer.getNextToken(MasterToken::NUMBER).getNumber();
if (hashalg > 0xff) {
isc_throw(InvalidRdataText, rrtype_name <<
" hash algorithm out of range: " << hashalg);
}
+
+ const uint32_t flags =
+ lexer.getNextToken(MasterToken::NUMBER).getNumber();
if (flags > 0xff) {
isc_throw(InvalidRdataText, rrtype_name << " flags out of range: " <<
flags);
}
- // Convert iteration. To reject an invalid case where there's no space
- // between iteration and salt, we extract this field as string and convert
- // to integer.
- try {
- iterations = boost::lexical_cast<unsigned int>(iterations_str);
- } catch (const boost::bad_lexical_cast&) {
- isc_throw(InvalidRdataText, "Bad " << rrtype_name <<
- " iteration: " << iterations_str);
- }
+
+ const uint32_t iterations =
+ lexer.getNextToken(MasterToken::NUMBER).getNumber();
if (iterations > 0xffff) {
isc_throw(InvalidRdataText, rrtype_name <<
- " iterations out of range: " <<
- iterations);
+ " iterations out of range: " << iterations);
}
+ const string salthex =
+ lexer.getNextToken(MasterToken::STRING).getString();
+
// Salt is up to 255 bytes, and space is not allowed in the HEX encoding,
// so the encoded string cannot be longer than the double of max length
// of the actual salt.
diff --git a/src/lib/dns/rdata/generic/detail/nsec3param_common.h b/src/lib/dns/rdata/generic/detail/nsec3param_common.h
index 1891fae..a8b848d 100644
--- a/src/lib/dns/rdata/generic/detail/nsec3param_common.h
+++ b/src/lib/dns/rdata/generic/detail/nsec3param_common.h
@@ -15,12 +15,11 @@
#ifndef NSEC3PARAM_COMMON_H
#define NSEC3PARAM_COMMON_H 1
+#include <dns/master_lexer.h>
+
#include <util/buffer.h>
#include <stdint.h>
-
-#include <sstream>
-#include <string>
#include <vector>
namespace isc {
@@ -59,7 +58,7 @@ struct ParseNSEC3ParamResult {
/// \brief Convert textual representation of NSEC3 parameters.
///
-/// This function takes an input string stream that consists of a complete
+/// This function takes an input MasterLexer that points at a complete
/// textual representation of an NSEC3 or NSEC3PARAM RDATA and parses it
/// extracting the hash algorithm, flags, iterations, and salt fields.
///
@@ -67,28 +66,26 @@ struct ParseNSEC3ParamResult {
/// The salt will be stored in the given vector. The vector is expected
/// to be empty, but if not, the existing content will be overridden.
///
-/// On successful return the given input stream will reach the end of the
+/// On successful return the given MasterLexer will reach the end of the
/// salt field.
///
/// \exception isc::BadValue The salt is not a valid hex string.
-/// \exception InvalidRdataText The given string is otherwise invalid for
+/// \exception InvalidRdataText The given RDATA is otherwise invalid for
/// NSEC3 or NSEC3PARAM fields.
+/// \exception MasterLexer::LexerError There was a syntax error reading
+/// a field from the MasterLexer.
///
/// \param rrtype_name Either "NSEC3" or "NSEC3PARAM"; used as part of
/// exception messages.
-/// \param rdata_str A complete textual string of the RDATA; used as part of
-/// exception messages.
-/// \param iss Input stream that consists of a complete textual string of
-/// the RDATA.
+/// \param lexer The MasterLexer to read NSEC3 parameter fields from.
/// \param salt A placeholder for the salt field value of the RDATA.
/// Expected to be empty, but it's not checked (and will be overridden).
///
/// \return The hash algorithm, flags, iterations in the form of
/// ParseNSEC3ParamResult.
-ParseNSEC3ParamResult parseNSEC3ParamText(const char* const rrtype_name,
- const std::string& rdata_str,
- std::istringstream& iss,
- std::vector<uint8_t>& salt);
+ParseNSEC3ParamResult parseNSEC3ParamFromLexer(const char* const rrtype_name,
+ isc::dns::MasterLexer& lexer,
+ std::vector<uint8_t>& salt);
/// \brief Extract NSEC3 parameters from wire-format data.
///
diff --git a/src/lib/dns/rdata/generic/detail/nsec_bitmap.cc b/src/lib/dns/rdata/generic/detail/nsec_bitmap.cc
index 319056e..5deaa69 100644
--- a/src/lib/dns/rdata/generic/detail/nsec_bitmap.cc
+++ b/src/lib/dns/rdata/generic/detail/nsec_bitmap.cc
@@ -78,68 +78,28 @@ checkRRTypeBitmaps(const char* const rrtype_name,
}
void
-buildBitmapsFromText(const char* const rrtype_name,
- istringstream& iss, vector<uint8_t>& typebits)
-{
- uint8_t bitmap[8 * 1024]; // 64k bits
- memset(bitmap, 0, sizeof(bitmap));
-
- do {
- string type;
- iss >> type;
- if (iss.bad() || iss.fail()) {
- isc_throw(InvalidRdataText, "Unexpected input for "
- << rrtype_name << " bitmap");
- }
- try {
- const int code = RRType(type).getCode();
- bitmap[code / 8] |= (0x80 >> (code % 8));
- } catch (const InvalidRRType&) {
- isc_throw(InvalidRdataText, "Invalid RRtype in "
- << rrtype_name << " bitmap: " << type);
- }
- } while (!iss.eof());
-
- for (int window = 0; window < 256; ++window) {
- int octet;
- for (octet = 31; octet >= 0; octet--) {
- if (bitmap[window * 32 + octet] != 0) {
- break;
- }
- }
- if (octet < 0) {
- continue;
- }
- typebits.push_back(window);
- typebits.push_back(octet + 1);
- for (int i = 0; i <= octet; ++i) {
- typebits.push_back(bitmap[window * 32 + i]);
- }
- }
-}
-
-// Note: this function shares common code with buildBitmapsFromText()
-// above, but it is expected that buildBitmapsFromText() will be deleted
-// entirely once the transition to MasterLexer is done for all dependent
-// RR types. So a common method is not made from the two.
-void
buildBitmapsFromLexer(const char* const rrtype_name,
- MasterLexer& lexer, vector<uint8_t>& typebits)
+ MasterLexer& lexer, vector<uint8_t>& typebits,
+ bool allow_empty)
{
uint8_t bitmap[8 * 1024]; // 64k bits
memset(bitmap, 0, sizeof(bitmap));
bool have_rrtypes = false;
+ std::string type_str;
while (true) {
- const MasterToken& token = lexer.getNextToken();
- if (token.getType() != MasterToken::STRING) {
+ const MasterToken& token =
+ lexer.getNextToken(MasterToken::STRING, true);
+ if ((token.getType() == MasterToken::END_OF_FILE) ||
+ (token.getType() == MasterToken::END_OF_LINE)) {
break;
}
+ // token is now assured to be of type STRING.
+
have_rrtypes = true;
- std::string type_str;
+ token.getString(type_str);
try {
- type_str = token.getString();
const int code = RRType(type_str).getCode();
bitmap[code / 8] |= (0x80 >> (code % 8));
} catch (const InvalidRRType&) {
@@ -151,8 +111,12 @@ buildBitmapsFromLexer(const char* const rrtype_name,
lexer.ungetToken();
if (!have_rrtypes) {
+ if (allow_empty) {
+ return;
+ }
isc_throw(InvalidRdataText,
- rrtype_name << " record does not end with RR type mnemonic");
+ rrtype_name <<
+ " record does not end with RR type mnemonic");
}
for (int window = 0; window < 256; ++window) {
diff --git a/src/lib/dns/rdata/generic/detail/nsec_bitmap.h b/src/lib/dns/rdata/generic/detail/nsec_bitmap.h
index ea5cad0..5525384 100644
--- a/src/lib/dns/rdata/generic/detail/nsec_bitmap.h
+++ b/src/lib/dns/rdata/generic/detail/nsec_bitmap.h
@@ -54,33 +54,11 @@ namespace nsec {
void checkRRTypeBitmaps(const char* const rrtype_name,
const std::vector<uint8_t>& typebits);
-/// \brief Convert textual sequence of RR types into type bitmaps.
-///
-/// This function extracts a sequence of strings, converts each sequence
-/// into an RR type, and builds NSEC/NSEC3 type bitmaps with the corresponding
-/// bits for the extracted types being on. The resulting bitmaps (which are
-/// in the wire format according to RFC4034 and RFC5155) are stored in the
-/// given vector. This function expects the given string stream ends with
-/// the sequence.
-///
-/// \exception InvalidRdataText The given input stream does not meet the
-/// assumption (e.g. including invalid form of RR type, not ending with
-/// an RR type string).
-///
-/// \param rrtype_name Either "NSEC" or "NSEC3"; used as part of exception
-/// messages.
-/// \param iss Input stream that consists of a complete sequence of textual
-/// RR types for which the corresponding bits are set.
-/// \param typebits A placeholder for the resulting bitmaps. Expected to be
-/// empty, but it's not checked.
-void buildBitmapsFromText(const char* const rrtype_name,
- std::istringstream& iss,
- std::vector<uint8_t>& typebits);
-
/// \brief Convert textual sequence of RR types read from a lexer into
/// type bitmaps.
///
-/// See the other variant above for description.
+/// See the other variant above for description. If \c allow_empty is
+/// true and there are no mnemonics, \c typebits is left untouched.
///
/// \exception InvalidRdataText Data read from the given lexer does not
/// meet the assumption (e.g. including invalid form of RR type, not
@@ -93,9 +71,13 @@ void buildBitmapsFromText(const char* const rrtype_name,
/// bits are set.
/// \param typebits A placeholder for the resulting bitmaps. Expected to be
/// empty, but it's not checked.
+/// \param allow_empty If true, the function simply returns if no RR
+/// type mnemonics are found. Otherwise, it throws an exception if no RR
+/// type mnemonics are found.
void buildBitmapsFromLexer(const char* const rrtype_name,
isc::dns::MasterLexer& lexer,
- std::vector<uint8_t>& typebits);
+ std::vector<uint8_t>& typebits,
+ bool allow_empty = false);
/// \brief Convert type bitmaps to textual sequence of RR types.
///
diff --git a/src/lib/dns/rdata/generic/dnskey_48.cc b/src/lib/dns/rdata/generic/dnskey_48.cc
index 054ac18..2e9a9f3 100644
--- a/src/lib/dns/rdata/generic/dnskey_48.cc
+++ b/src/lib/dns/rdata/generic/dnskey_48.cc
@@ -27,6 +27,8 @@
#include <dns/rdata.h>
#include <dns/rdataclass.h>
+#include <memory>
+
#include <stdio.h>
#include <time.h>
@@ -51,51 +53,157 @@ struct DNSKEYImpl {
const vector<uint8_t> keydata_;
};
+/// \brief Constructor from string.
+///
+/// The given string must represent a valid DNSKEY RDATA. There can be
+/// extra space characters at the beginning or end of the text (which
+/// are simply ignored), but other extra text, including a new line,
+/// will make the construction fail with an exception.
+///
+/// The Protocol and Algorithm fields must be within their valid
+/// ranges. The Public Key field must be present and must contain a
+/// Base64 encoding of the public key. Whitespace is allowed within the
+/// Base64 text.
+///
+/// It is okay for the key data to be missing. Note: BIND 9 also accepts
+/// DNSKEY missing key data. While the RFC is silent in this case, and it
+/// may be debatable what an implementation should do, but since this field
+/// is algorithm dependent and this implementations doesn't reject unknown
+/// algorithms, it's lenient here.
+///
+/// \throw InvalidRdataText if any fields are out of their valid range,
+/// or are incorrect.
+///
+/// \param dnskey_str A string containing the RDATA to be created
DNSKEY::DNSKEY(const std::string& dnskey_str) :
impl_(NULL)
{
- istringstream iss(dnskey_str);
- unsigned int flags, protocol, algorithm;
- stringbuf keydatabuf;
+ // We use auto_ptr here because if there is an exception in this
+ // constructor, the destructor is not called and there could be a
+ // leak of the DNSKEYImpl that constructFromLexer() returns.
+ std::auto_ptr<DNSKEYImpl> impl_ptr(NULL);
+
+ try {
+ std::istringstream ss(dnskey_str);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ impl_ptr.reset(constructFromLexer(lexer));
+
+ if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText,
+ "Extra input text for DNSKEY: " << dnskey_str);
+ }
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText,
+ "Failed to construct DNSKEY from '" << dnskey_str << "': "
+ << ex.what());
+ }
- iss >> flags >> protocol >> algorithm >> &keydatabuf;
- if (iss.bad() || iss.fail()) {
- isc_throw(InvalidRdataText, "Invalid DNSKEY text");
+ impl_ = impl_ptr.release();
+}
+
+/// \brief Constructor from InputBuffer.
+///
+/// The passed buffer must contain a valid DNSKEY RDATA.
+///
+/// The Protocol and Algorithm fields are not checked for unknown
+/// values. It is okay for the key data to be missing (see the description
+/// of the constructor from string).
+DNSKEY::DNSKEY(InputBuffer& buffer, size_t rdata_len) :
+ impl_(NULL)
+{
+ if (rdata_len < 4) {
+ isc_throw(InvalidRdataLength, "DNSKEY too short: " << rdata_len);
}
+
+ const uint16_t flags = buffer.readUint16();
+ const uint16_t protocol = buffer.readUint8();
+ const uint16_t algorithm = buffer.readUint8();
+
+ rdata_len -= 4;
+
+ vector<uint8_t> keydata;
+ // If key data is missing, it's OK. See the API documentation of the
+ // constructor.
+ if (rdata_len > 0) {
+ keydata.resize(rdata_len);
+ buffer.readData(&keydata[0], rdata_len);
+ }
+
+ impl_ = new DNSKEYImpl(flags, protocol, algorithm, keydata);
+}
+
+/// \brief Constructor with a context of MasterLexer.
+///
+/// The \c lexer should point to the beginning of valid textual
+/// representation of an DNSKEY RDATA.
+///
+/// See \c DNSKEY::DNSKEY(const std::string&) for description of the
+/// expected RDATA fields.
+///
+/// \throw MasterLexer::LexerError General parsing error such as
+/// missing field.
+/// \throw InvalidRdataText if any fields are out of their valid range,
+/// or are incorrect.
+///
+/// \param lexer A \c MasterLexer object parsing a master file for the
+/// RDATA to be created
+DNSKEY::DNSKEY(MasterLexer& lexer, const Name*,
+ MasterLoader::Options, MasterLoaderCallbacks&) :
+ impl_(NULL)
+{
+ impl_ = constructFromLexer(lexer);
+}
+
+DNSKEYImpl*
+DNSKEY::constructFromLexer(MasterLexer& lexer) {
+ const uint32_t flags = lexer.getNextToken(MasterToken::NUMBER).getNumber();
if (flags > 0xffff) {
- isc_throw(InvalidRdataText, "DNSKEY flags out of range");
+ isc_throw(InvalidRdataText,
+ "DNSKEY flags out of range: " << flags);
}
+
+ const uint32_t protocol =
+ lexer.getNextToken(MasterToken::NUMBER).getNumber();
if (protocol > 0xff) {
- isc_throw(InvalidRdataText, "DNSKEY protocol out of range");
+ isc_throw(InvalidRdataText,
+ "DNSKEY protocol out of range: " << protocol);
}
+
+ const uint32_t algorithm =
+ lexer.getNextToken(MasterToken::NUMBER).getNumber();
if (algorithm > 0xff) {
- isc_throw(InvalidRdataText, "DNSKEY algorithm out of range");
+ isc_throw(InvalidRdataText,
+ "DNSKEY algorithm out of range: " << algorithm);
}
- vector<uint8_t> keydata;
- decodeBase64(keydatabuf.str(), keydata);
-
- if (algorithm == 1 && keydata.size() < 3) {
- isc_throw(InvalidRdataLength, "DNSKEY keydata too short");
- }
+ std::string keydata_str;
+ std::string keydata_substr;
+ while (true) {
+ const MasterToken& token =
+ lexer.getNextToken(MasterToken::STRING, true);
+ if ((token.getType() == MasterToken::END_OF_FILE) ||
+ (token.getType() == MasterToken::END_OF_LINE)) {
+ break;
+ }
- impl_ = new DNSKEYImpl(flags, protocol, algorithm, keydata);
-}
+ // token is now assured to be of type STRING.
-DNSKEY::DNSKEY(InputBuffer& buffer, size_t rdata_len) {
- if (rdata_len < 4) {
- isc_throw(InvalidRdataLength, "DNSKEY too short: " << rdata_len);
+ token.getString(keydata_substr);
+ keydata_str.append(keydata_substr);
}
- uint16_t flags = buffer.readUint16();
- uint16_t protocol = buffer.readUint8();
- uint16_t algorithm = buffer.readUint8();
+ lexer.ungetToken();
- rdata_len -= 4;
- vector<uint8_t> keydata(rdata_len);
- buffer.readData(&keydata[0], rdata_len);
+ vector<uint8_t> keydata;
+ // If key data is missing, it's OK. See the API documentation of the
+ // constructor.
+ if (keydata_str.size() > 0) {
+ decodeBase64(keydata_str, keydata);
+ }
- impl_ = new DNSKEYImpl(flags, protocol, algorithm, keydata);
+ return (new DNSKEYImpl(flags, protocol, algorithm, keydata));
}
DNSKEY::DNSKEY(const DNSKEY& source) :
@@ -157,11 +265,11 @@ DNSKEY::compare(const Rdata& other) const {
return (impl_->algorithm_ < other_dnskey.impl_->algorithm_ ? -1 : 1);
}
- size_t this_len = impl_->keydata_.size();
- size_t other_len = other_dnskey.impl_->keydata_.size();
- size_t cmplen = min(this_len, other_len);
- int cmp = memcmp(&impl_->keydata_[0], &other_dnskey.impl_->keydata_[0],
- cmplen);
+ const size_t this_len = impl_->keydata_.size();
+ const size_t other_len = other_dnskey.impl_->keydata_.size();
+ const size_t cmplen = min(this_len, other_len);
+ const int cmp = memcmp(&impl_->keydata_[0],
+ &other_dnskey.impl_->keydata_[0], cmplen);
if (cmp != 0) {
return (cmp);
} else {
@@ -172,15 +280,24 @@ DNSKEY::compare(const Rdata& other) const {
uint16_t
DNSKEY::getTag() const {
if (impl_->algorithm_ == 1) {
- int len = impl_->keydata_.size();
+ // See RFC 4034 appendix B.1 for why the key data must contain
+ // at least 4 bytes with RSA/MD5: 3 trailing bytes to extract
+ // the tag from, and 1 byte of exponent length subfield before
+ // modulus.
+ const int len = impl_->keydata_.size();
+ if (len < 4) {
+ isc_throw(isc::OutOfRange,
+ "DNSKEY keydata too short for tag extraction");
+ }
+
return ((impl_->keydata_[len - 3] << 8) + impl_->keydata_[len - 2]);
}
uint32_t ac = impl_->flags_;
ac += (impl_->protocol_ << 8);
ac += impl_->algorithm_;
-
- size_t size = impl_->keydata_.size();
+
+ const size_t size = impl_->keydata_.size();
for (size_t i = 0; i < size; i ++) {
ac += (i & 1) ? impl_->keydata_[i] : (impl_->keydata_[i] << 8);
}
diff --git a/src/lib/dns/rdata/generic/dnskey_48.h b/src/lib/dns/rdata/generic/dnskey_48.h
index 14fad9f..3ef18e6 100644
--- a/src/lib/dns/rdata/generic/dnskey_48.h
+++ b/src/lib/dns/rdata/generic/dnskey_48.h
@@ -20,6 +20,7 @@
#include <dns/rrtype.h>
#include <dns/rrttl.h>
#include <dns/rdata.h>
+#include <dns/master_lexer.h>
// BEGIN_HEADER_GUARD
@@ -42,11 +43,19 @@ public:
///
/// Specialized methods
///
+
+ /// \brief Returns the key tag
+ ///
+ /// \throw isc::OutOfRange if the key data for RSA/MD5 is too short
+ /// to support tag extraction.
uint16_t getTag() const;
+
uint16_t getFlags() const;
uint8_t getAlgorithm() const;
private:
+ DNSKEYImpl* constructFromLexer(isc::dns::MasterLexer& lexer);
+
DNSKEYImpl* impl_;
};
diff --git a/src/lib/dns/rdata/generic/mx_15.cc b/src/lib/dns/rdata/generic/mx_15.cc
index 12ada97..ae9978e 100644
--- a/src/lib/dns/rdata/generic/mx_15.cc
+++ b/src/lib/dns/rdata/generic/mx_15.cc
@@ -68,15 +68,7 @@ MX::MX(const std::string& mx_str) :
MasterLexer lexer;
lexer.pushSource(ss);
- const uint32_t num =
- lexer.getNextToken(MasterToken::NUMBER).getNumber();
- if (num > 65535) {
- isc_throw(InvalidRdataText, "Invalid MX preference in: "
- << mx_str);
- }
- preference_ = static_cast<uint16_t>(num);
-
- mxname_ = createNameFromLexer(lexer, NULL);
+ constructFromLexer(lexer, NULL);
if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
isc_throw(InvalidRdataText, "extra input text for MX: "
@@ -108,8 +100,13 @@ MX::MX(const std::string& mx_str) :
/// is non-absolute.
MX::MX(MasterLexer& lexer, const Name* origin,
MasterLoader::Options, MasterLoaderCallbacks&) :
- preference_(0), mxname_(".")
+ preference_(0), mxname_(Name::ROOT_NAME())
{
+ constructFromLexer(lexer, origin);
+}
+
+void
+MX::constructFromLexer(MasterLexer& lexer, const Name* origin) {
const uint32_t num = lexer.getNextToken(MasterToken::NUMBER).getNumber();
if (num > 65535) {
isc_throw(InvalidRdataText, "Invalid MX preference: " << num);
diff --git a/src/lib/dns/rdata/generic/mx_15.h b/src/lib/dns/rdata/generic/mx_15.h
index 1381f18..84e7112 100644
--- a/src/lib/dns/rdata/generic/mx_15.h
+++ b/src/lib/dns/rdata/generic/mx_15.h
@@ -42,6 +42,9 @@ public:
uint16_t getMXPref() const;
private:
+ void constructFromLexer(isc::dns::MasterLexer& lexer,
+ const isc::dns::Name* origin);
+
/// Note: this is a prototype version; we may reconsider
/// this representation later.
uint16_t preference_;
diff --git a/src/lib/dns/rdata/generic/nsec3_50.cc b/src/lib/dns/rdata/generic/nsec3_50.cc
index fb92246..fd8f78d 100644
--- a/src/lib/dns/rdata/generic/nsec3_50.cc
+++ b/src/lib/dns/rdata/generic/nsec3_50.cc
@@ -35,6 +35,8 @@
#include <dns/rdata/generic/detail/nsec_bitmap.h>
#include <dns/rdata/generic/detail/nsec3param_common.h>
+#include <memory>
+
#include <stdio.h>
#include <time.h>
@@ -64,24 +66,86 @@ struct NSEC3Impl {
const vector<uint8_t> typebits_;
};
+/// \brief Constructor from string.
+///
+/// The given string must represent a valid NSEC3 RDATA. There
+/// can be extra space characters at the beginning or end of the
+/// text (which are simply ignored), but other extra text, including
+/// a new line, will make the construction fail with an exception.
+///
+/// The Hash Algorithm, Flags and Iterations fields must be within their
+/// valid ranges. The Salt field may contain "-" to indicate that the
+/// salt is of length 0. The Salt field must not contain any whitespace.
+/// The type mnemonics must be valid, and separated by whitespace. If
+/// any invalid mnemonics are found, InvalidRdataText exception is
+/// thrown.
+///
+/// \throw InvalidRdataText if any fields are out of their valid range,
+/// or are incorrect.
+///
+/// \param nsec3_str A string containing the RDATA to be created
NSEC3::NSEC3(const std::string& nsec3_str) :
impl_(NULL)
{
- istringstream iss(nsec3_str);
+ // We use auto_ptr here because if there is an exception in this
+ // constructor, the destructor is not called and there could be a
+ // leak of the NSEC3Impl that constructFromLexer() returns.
+ std::auto_ptr<NSEC3Impl> impl_ptr(NULL);
+
+ try {
+ std::istringstream ss(nsec3_str);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ impl_ptr.reset(constructFromLexer(lexer));
+
+ if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText,
+ "Extra input text for NSEC3: " << nsec3_str);
+ }
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText,
+ "Failed to construct NSEC3 from '" << nsec3_str << "': "
+ << ex.what());
+ }
+
+ impl_ = impl_ptr.release();
+}
+
+/// \brief Constructor with a context of MasterLexer.
+///
+/// The \c lexer should point to the beginning of valid textual
+/// representation of an NSEC3 RDATA.
+///
+/// See \c NSEC3::NSEC3(const std::string&) for description of the
+/// expected RDATA fields.
+///
+/// \throw MasterLexer::LexerError General parsing error such as
+/// missing field.
+/// \throw InvalidRdataText if any fields are out of their valid range,
+/// or are incorrect.
+///
+/// \param lexer A \c MasterLexer object parsing a master file for the
+/// RDATA to be created
+NSEC3::NSEC3(MasterLexer& lexer, const Name*, MasterLoader::Options,
+ MasterLoaderCallbacks&) :
+ impl_(NULL)
+{
+ impl_ = constructFromLexer(lexer);
+}
+
+NSEC3Impl*
+NSEC3::constructFromLexer(MasterLexer& lexer) {
vector<uint8_t> salt;
const ParseNSEC3ParamResult params =
- parseNSEC3ParamText("NSEC3", nsec3_str, iss, salt);
+ parseNSEC3ParamFromLexer("NSEC3", lexer, salt);
- // Extract Next hash. It must be an unpadded base32hex string.
- string nexthash;
- iss >> nexthash;
- if (iss.bad() || iss.fail()) {
- isc_throw(InvalidRdataText, "Invalid NSEC3 text: " << nsec3_str);
- }
- assert(!nexthash.empty());
+ const string& nexthash =
+ lexer.getNextToken(MasterToken::STRING).getString();
if (*nexthash.rbegin() == '=') {
- isc_throw(InvalidRdataText, "NSEC3 hash has padding: " << nsec3_str);
+ isc_throw(InvalidRdataText, "NSEC3 hash has padding: " << nexthash);
}
+
vector<uint8_t> next;
decodeBase32Hex(nexthash, next);
if (next.size() > 255) {
@@ -89,22 +153,16 @@ NSEC3::NSEC3(const std::string& nsec3_str) :
<< next.size() << " bytes");
}
- // For NSEC3 empty bitmap is possible and allowed.
- if (iss.eof()) {
- impl_ = new NSEC3Impl(params.algorithm, params.flags,
- params.iterations, salt, next,
- vector<uint8_t>());
- return;
- }
-
vector<uint8_t> typebits;
- buildBitmapsFromText("NSEC3", iss, typebits);
-
- impl_ = new NSEC3Impl(params.algorithm, params.flags, params.iterations,
- salt, next, typebits);
+ // For NSEC3 empty bitmap is possible and allowed.
+ buildBitmapsFromLexer("NSEC3", lexer, typebits, true);
+ return (new NSEC3Impl(params.algorithm, params.flags, params.iterations,
+ salt, next, typebits));
}
-NSEC3::NSEC3(InputBuffer& buffer, size_t rdata_len) {
+NSEC3::NSEC3(InputBuffer& buffer, size_t rdata_len) :
+ impl_(NULL)
+{
vector<uint8_t> salt;
const ParseNSEC3ParamResult params =
parseNSEC3ParamWire("NSEC3", buffer, rdata_len, salt);
diff --git a/src/lib/dns/rdata/generic/nsec3_50.h b/src/lib/dns/rdata/generic/nsec3_50.h
index c766ade..6a1dcb5 100644
--- a/src/lib/dns/rdata/generic/nsec3_50.h
+++ b/src/lib/dns/rdata/generic/nsec3_50.h
@@ -21,6 +21,7 @@
#include <dns/rrtype.h>
#include <dns/rrttl.h>
#include <dns/rdata.h>
+#include <dns/master_lexer.h>
// BEGIN_HEADER_GUARD
@@ -47,6 +48,8 @@ public:
const std::vector<uint8_t>& getNext() const;
private:
+ NSEC3Impl* constructFromLexer(isc::dns::MasterLexer& lexer);
+
NSEC3Impl* impl_;
};
diff --git a/src/lib/dns/rdata/generic/nsec3param_51.cc b/src/lib/dns/rdata/generic/nsec3param_51.cc
index 5686353..494746d 100644
--- a/src/lib/dns/rdata/generic/nsec3param_51.cc
+++ b/src/lib/dns/rdata/generic/nsec3param_51.cc
@@ -22,6 +22,7 @@
#include <boost/lexical_cast.hpp>
+#include <memory>
#include <string>
#include <sstream>
#include <vector>
@@ -46,24 +47,84 @@ struct NSEC3PARAMImpl {
const vector<uint8_t> salt_;
};
+/// \brief Constructor from string.
+///
+/// The given string must represent a valid NSEC3PARAM RDATA. There
+/// can be extra space characters at the beginning or end of the
+/// text (which are simply ignored), but other extra text, including
+/// a new line, will make the construction fail with an exception.
+///
+/// The Hash Algorithm, Flags and Iterations fields must be within their
+/// valid ranges. The Salt field may contain "-" to indicate that the
+/// salt is of length 0. The Salt field must not contain any whitespace.
+///
+/// \throw InvalidRdataText if any fields are out of their valid range,
+/// or are incorrect.
+///
+/// \param nsec3param_str A string containing the RDATA to be created
NSEC3PARAM::NSEC3PARAM(const std::string& nsec3param_str) :
impl_(NULL)
{
- istringstream iss(nsec3param_str);
+ // We use auto_ptr here because if there is an exception in this
+ // constructor, the destructor is not called and there could be a
+ // leak of the NSEC3PARAMImpl that constructFromLexer() returns.
+ std::auto_ptr<NSEC3PARAMImpl> impl_ptr(NULL);
+
+ try {
+ std::istringstream ss(nsec3param_str);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ impl_ptr.reset(constructFromLexer(lexer));
+
+ if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText,
+ "Extra input text for NSEC3PARAM: " << nsec3param_str);
+ }
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText,
+ "Failed to construct NSEC3PARAM from '" << nsec3param_str
+ << "': " << ex.what());
+ }
+
+ impl_ = impl_ptr.release();
+}
+
+/// \brief Constructor with a context of MasterLexer.
+///
+/// The \c lexer should point to the beginning of valid textual
+/// representation of an NSEC3PARAM RDATA.
+///
+/// See \c NSEC3PARAM::NSEC3PARAM(const std::string&) for description of
+/// the expected RDATA fields.
+///
+/// \throw MasterLexer::LexerError General parsing error such as
+/// missing field.
+/// \throw InvalidRdataText if any fields are out of their valid range,
+/// or are incorrect.
+///
+/// \param lexer A \c MasterLexer object parsing a master file for the
+/// RDATA to be created
+NSEC3PARAM::NSEC3PARAM(MasterLexer& lexer, const Name*, MasterLoader::Options,
+ MasterLoaderCallbacks&) :
+ impl_(NULL)
+{
+ impl_ = constructFromLexer(lexer);
+}
+
+NSEC3PARAMImpl*
+NSEC3PARAM::constructFromLexer(MasterLexer& lexer) {
vector<uint8_t> salt;
const ParseNSEC3ParamResult params =
- parseNSEC3ParamText("NSEC3PARAM", nsec3param_str, iss, salt);
-
- if (!iss.eof()) {
- isc_throw(InvalidRdataText, "Invalid NSEC3PARAM (redundant text): "
- << nsec3param_str);
- }
+ parseNSEC3ParamFromLexer("NSEC3PARAM", lexer, salt);
- impl_ = new NSEC3PARAMImpl(params.algorithm, params.flags,
- params.iterations, salt);
+ return (new NSEC3PARAMImpl(params.algorithm, params.flags,
+ params.iterations, salt));
}
-NSEC3PARAM::NSEC3PARAM(InputBuffer& buffer, size_t rdata_len) {
+NSEC3PARAM::NSEC3PARAM(InputBuffer& buffer, size_t rdata_len) :
+ impl_(NULL)
+{
vector<uint8_t> salt;
const ParseNSEC3ParamResult params =
parseNSEC3ParamWire("NSEC3PARAM", buffer, rdata_len, salt);
diff --git a/src/lib/dns/rdata/generic/nsec3param_51.h b/src/lib/dns/rdata/generic/nsec3param_51.h
index 130c759..bd2ce75 100644
--- a/src/lib/dns/rdata/generic/nsec3param_51.h
+++ b/src/lib/dns/rdata/generic/nsec3param_51.h
@@ -21,6 +21,7 @@
#include <dns/rrtype.h>
#include <dns/rrttl.h>
#include <dns/rdata.h>
+#include <dns/master_lexer.h>
// BEGIN_HEADER_GUARD
@@ -47,7 +48,10 @@ public:
uint8_t getFlags() const;
uint16_t getIterations() const;
const std::vector<uint8_t>& getSalt() const;
+
private:
+ NSEC3PARAMImpl* constructFromLexer(isc::dns::MasterLexer& lexer);
+
NSEC3PARAMImpl* impl_;
};
diff --git a/src/lib/dns/rdata/generic/nsec_47.cc b/src/lib/dns/rdata/generic/nsec_47.cc
index c474b02..ffa2c97 100644
--- a/src/lib/dns/rdata/generic/nsec_47.cc
+++ b/src/lib/dns/rdata/generic/nsec_47.cc
@@ -136,15 +136,17 @@ NSEC::NSEC(InputBuffer& buffer, size_t rdata_len) {
///
/// \param lexer A \c MasterLexer object parsing a master file for the
/// RDATA to be created
+/// \param origin The origin to use with a relative Next Domain Name
+/// field
NSEC::NSEC(MasterLexer& lexer, const Name* origin, MasterLoader::Options,
MasterLoaderCallbacks&)
{
- const Name origin_name(createNameFromLexer(lexer, origin));
+ const Name next_name(createNameFromLexer(lexer, origin));
vector<uint8_t> typebits;
buildBitmapsFromLexer("NSEC", lexer, typebits);
- impl_ = new NSECImpl(origin_name, typebits);
+ impl_ = new NSECImpl(next_name, typebits);
}
NSEC::NSEC(const NSEC& source) :
diff --git a/src/lib/dns/rdata/generic/opt_41.cc b/src/lib/dns/rdata/generic/opt_41.cc
index d64effb..136bdf9 100644
--- a/src/lib/dns/rdata/generic/opt_41.cc
+++ b/src/lib/dns/rdata/generic/opt_41.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2010-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
@@ -27,10 +27,26 @@ using namespace isc::util;
// BEGIN_ISC_NAMESPACE
// BEGIN_RDATA_NAMESPACE
+/// \brief Constructor from string.
+///
+/// This constructor cannot be used, and always throws an exception.
+///
+/// \throw InvalidRdataText OPT RR cannot be constructed from text.
OPT::OPT(const std::string&) {
isc_throw(InvalidRdataText, "OPT RR cannot be constructed from text");
}
+/// \brief Constructor with a context of MasterLexer.
+///
+/// This constructor cannot be used, and always throws an exception.
+///
+/// \throw InvalidRdataText OPT RR cannot be constructed from text.
+OPT::OPT(MasterLexer&, const Name*,
+ MasterLoader::Options, MasterLoaderCallbacks&)
+{
+ isc_throw(InvalidRdataText, "OPT RR cannot be constructed from text");
+}
+
OPT::OPT(InputBuffer& buffer, size_t rdata_len) {
// setPosition() will throw against a short buffer anyway, but it's safer
// to check it explicitly here.
@@ -48,6 +64,7 @@ OPT::OPT(const OPT&) : Rdata() {
std::string
OPT::toText() const {
+ // OPT records do not have a text format.
return ("");
}
diff --git a/src/lib/dns/rdata/generic/rrsig_46.cc b/src/lib/dns/rdata/generic/rrsig_46.cc
index e0137b9..8e826d7 100644
--- a/src/lib/dns/rdata/generic/rrsig_46.cc
+++ b/src/lib/dns/rdata/generic/rrsig_46.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2010-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
@@ -26,9 +26,9 @@
#include <dns/messagerenderer.h>
#include <dns/name.h>
#include <dns/rrtype.h>
-#include <dns/rrttl.h>
#include <dns/rdata.h>
#include <dns/rdataclass.h>
+#include <dns/rdata/generic/detail/lexer_util.h>
#include <stdio.h>
#include <time.h>
@@ -36,6 +36,7 @@
using namespace std;
using namespace isc::util;
using namespace isc::util::encode;
+using isc::dns::rdata::generic::detail::createNameFromLexer;
// BEGIN_ISC_NAMESPACE
// BEGIN_RDATA_NAMESPACE
@@ -52,8 +53,8 @@ const size_t RRSIG_MINIMUM_LEN = 2 * sizeof(uint8_t) + 2 * sizeof(uint16_t) +
struct RRSIGImpl {
// straightforward representation of RRSIG RDATA fields
RRSIGImpl(const RRType& covered, uint8_t algorithm, uint8_t labels,
- uint32_t originalttl, uint32_t timeexpire, uint32_t timeinception,
- uint16_t tag, const Name& signer,
+ uint32_t originalttl, uint32_t timeexpire,
+ uint32_t timeinception, uint16_t tag, const Name& signer,
const vector<uint8_t>& signature) :
covered_(covered), algorithm_(algorithm), labels_(labels),
originalttl_(originalttl), timeexpire_(timeexpire),
@@ -72,38 +73,125 @@ struct RRSIGImpl {
const vector<uint8_t> signature_;
};
-RRSIG::RRSIG(const std::string& rrsig_str) :
- impl_(NULL)
-{
- istringstream iss(rrsig_str);
- string covered_txt, signer_txt, expire_txt, inception_txt;
- unsigned int algorithm, labels;
- uint32_t originalttl;
- uint16_t tag;
- stringbuf signaturebuf;
-
- iss >> covered_txt >> algorithm >> labels >> originalttl
- >> expire_txt >> inception_txt >> tag >> signer_txt
- >> &signaturebuf;
- if (iss.bad() || iss.fail()) {
- isc_throw(InvalidRdataText, "Invalid RRSIG text");
- }
+// helper function for string and lexer constructors
+RRSIGImpl*
+RRSIG::constructFromLexer(MasterLexer& lexer, const Name* origin) {
+ const RRType covered(lexer.getNextToken(MasterToken::STRING).getString());
+ const uint32_t algorithm =
+ lexer.getNextToken(MasterToken::NUMBER).getNumber();
if (algorithm > 0xff) {
isc_throw(InvalidRdataText, "RRSIG algorithm out of range");
}
+ const uint32_t labels =
+ lexer.getNextToken(MasterToken::NUMBER).getNumber();
if (labels > 0xff) {
isc_throw(InvalidRdataText, "RRSIG labels out of range");
}
-
- const uint32_t timeexpire = timeFromText32(expire_txt);
- const uint32_t timeinception = timeFromText32(inception_txt);
+ const uint32_t originalttl =
+ lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ const uint32_t timeexpire =
+ timeFromText32(lexer.getNextToken(MasterToken::STRING).getString());
+ const uint32_t timeinception =
+ timeFromText32(lexer.getNextToken(MasterToken::STRING).getString());
+ const uint32_t tag =
+ lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ if (tag > 0xffff) {
+ isc_throw(InvalidRdataText, "RRSIG key tag out of range");
+ }
+ const Name& signer = createNameFromLexer(lexer, origin);
+
+ string signature_txt;
+ string signature_part;
+ // Whitespace is allowed within base64 text, so read to the end of input.
+ while (true) {
+ const MasterToken& token =
+ lexer.getNextToken(MasterToken::STRING, true);
+ if ((token.getType() == MasterToken::END_OF_FILE) ||
+ (token.getType() == MasterToken::END_OF_LINE)) {
+ break;
+ }
+ token.getString(signature_part);
+ signature_txt.append(signature_part);
+ }
+ lexer.ungetToken();
vector<uint8_t> signature;
- decodeBase64(signaturebuf.str(), signature);
+ // missing signature is okay
+ if (signature_txt.size() > 0) {
+ decodeBase64(signature_txt, signature);
+ }
- impl_ = new RRSIGImpl(RRType(covered_txt), algorithm, labels,
- originalttl, timeexpire, timeinception, tag,
- Name(signer_txt), signature);
+ return (new RRSIGImpl(covered, algorithm, labels,
+ originalttl, timeexpire, timeinception,
+ static_cast<uint16_t>(tag), signer, signature));
+}
+
+/// \brief Constructor from string.
+///
+/// The given string must represent a valid RRSIG RDATA. There can be extra
+/// space characters at the beginning or end of the text (which are simply
+/// ignored), but other extra text, including a new line, will make the
+/// construction fail with an exception.
+///
+/// The Signer's Name must be absolute since there's no parameter that
+/// specifies the origin name; if this is not absolute, \c MissingNameOrigin
+/// exception will be thrown. This must not be represented as a quoted
+/// string.
+///
+/// See the construction that takes \c MasterLexer for other fields.
+///
+/// \throw Others Exception from the Name constructor.
+/// \throw InvalidRdataText Other general syntax errors.
+RRSIG::RRSIG(const std::string& rrsig_str) :
+ impl_(NULL)
+{
+ // We use auto_ptr here because if there is an exception in this
+ // constructor, the destructor is not called and there could be a
+ // leak of the RRSIGImpl that constructFromLexer() returns.
+ std::auto_ptr<RRSIGImpl> impl_ptr(NULL);
+
+ try {
+ std::istringstream iss(rrsig_str);
+ MasterLexer lexer;
+ lexer.pushSource(iss);
+
+ impl_ptr.reset(constructFromLexer(lexer, NULL));
+
+ if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText, "extra input text for RRSIG: "
+ << rrsig_str);
+ }
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText, "Failed to construct RRSIG from '" <<
+ rrsig_str << "': " << ex.what());
+ }
+
+ impl_ = impl_ptr.release();
+}
+
+/// \brief Constructor with a context of MasterLexer.
+///
+/// The \c lexer should point to the beginning of valid textual representation
+/// of an RRSIG RDATA. The Signer's Name fields can be non absolute if \c
+/// origin is non NULL, in which case \c origin is used to make it absolute.
+/// This must not be represented as a quoted string.
+///
+/// The Original TTL field is a valid decimal representation of an unsigned
+/// 32-bit integer. Note that alternate textual representations of \c RRTTL,
+/// such as "1H" for 3600 seconds, are not allowed here.
+///
+/// \throw MasterLexer::LexerError General parsing error such as missing field.
+/// \throw Other Exceptions from the Name constructor if
+/// construction of textual fields as these objects fail.
+///
+/// \param lexer A \c MasterLexer object parsing a master file for the
+/// RDATA to be created
+/// \param origin If non NULL, specifies the origin of Signer's Name when
+/// it is non absolute.
+RRSIG::RRSIG(MasterLexer& lexer, const Name* origin,
+ MasterLoader::Options, MasterLoaderCallbacks&) :
+ impl_(constructFromLexer(lexer, origin))
+{
}
RRSIG::RRSIG(InputBuffer& buffer, size_t rdata_len) {
diff --git a/src/lib/dns/rdata/generic/rrsig_46.h b/src/lib/dns/rdata/generic/rrsig_46.h
index b32c17f..de72f64 100644
--- a/src/lib/dns/rdata/generic/rrsig_46.h
+++ b/src/lib/dns/rdata/generic/rrsig_46.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2010-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
@@ -18,7 +18,6 @@
#include <dns/name.h>
#include <dns/rrtype.h>
-#include <dns/rrttl.h>
#include <dns/rdata.h>
// BEGIN_HEADER_GUARD
@@ -32,6 +31,12 @@
struct RRSIGImpl;
+/// \brief \c rdata::RRSIG class represents the RRSIG RDATA as defined %in
+/// RFC4034.
+///
+/// This class implements the basic interfaces inherited from the abstract
+/// \c rdata::Rdata class, and provides trivial accessors specific to the
+/// RRSIG RDATA.
class RRSIG : public Rdata {
public:
// BEGIN_COMMON_MEMBERS
@@ -42,6 +47,9 @@ public:
// specialized methods
const RRType& typeCovered() const;
private:
+ // helper function for string and lexer constructors
+ RRSIGImpl* constructFromLexer(MasterLexer& lexer, const Name* origin);
+
RRSIGImpl* impl_;
};
diff --git a/src/lib/dns/rdata/in_1/dhcid_49.cc b/src/lib/dns/rdata/in_1/dhcid_49.cc
index 7745161..184866c 100644
--- a/src/lib/dns/rdata/in_1/dhcid_49.cc
+++ b/src/lib/dns/rdata/in_1/dhcid_49.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -15,8 +15,6 @@
#include <stdint.h>
#include <string.h>
-#include <string>
-
#include <exceptions/exceptions.h>
#include <util/buffer.h>
@@ -28,53 +26,77 @@
using namespace std;
using namespace isc::util;
+using namespace isc::util::encode;
// BEGIN_ISC_NAMESPACE
// BEGIN_RDATA_NAMESPACE
+void
+DHCID::constructFromLexer(MasterLexer& lexer) {
+ string digest_txt = lexer.getNextToken(MasterToken::STRING).getString();
+
+ // Whitespace is allowed within base64 text, so read to the end of input.
+ string digest_part;
+ while (true) {
+ const MasterToken& token =
+ lexer.getNextToken(MasterToken::STRING, true);
+ if ((token.getType() == MasterToken::END_OF_FILE) ||
+ (token.getType() == MasterToken::END_OF_LINE)) {
+ break;
+ }
+ token.getString(digest_part);
+ digest_txt.append(digest_part);
+ }
+ lexer.ungetToken();
+
+ decodeBase64(digest_txt, digest_);
+}
+
/// \brief Constructor from string.
///
/// \param dhcid_str A base-64 representation of the DHCID binary data.
-/// The data is considered to be opaque, but a sanity check is performed.
-///
-/// <b>Exceptions</b>
///
-/// \c dhcid_str must be a valid BASE-64 string, otherwise an exception
-/// of class \c isc::BadValue will be thrown;
-/// the binary data should consist of at leat of 3 octets as per RFC4701:
-/// < 2 octets > Identifier type code
-/// < 1 octet > Digest type code
-/// < n octets > Digest (length depends on digest type)
-/// If the data is less than 3 octets (i.e. it cannot contain id type code and
-/// digest type code), an exception of class \c InvalidRdataLength is thrown.
+/// \throw InvalidRdataText if the string could not be parsed correctly.
DHCID::DHCID(const std::string& dhcid_str) {
- istringstream iss(dhcid_str);
- stringbuf digestbuf;
-
- iss >> &digestbuf;
- isc::util::encode::decodeBase64(digestbuf.str(), digest_);
-
- // RFC4701 states DNS software should consider the RDATA section to
- // be opaque, but there must be at least three bytes in the data:
- // < 2 octets > Identifier type code
- // < 1 octet > Digest type code
- if (digest_.size() < 3) {
- isc_throw(InvalidRdataLength, "DHCID length " << digest_.size() <<
- " too short, need at least 3 bytes");
+ try {
+ std::istringstream iss(dhcid_str);
+ MasterLexer lexer;
+ lexer.pushSource(iss);
+
+ constructFromLexer(lexer);
+
+ if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText, "extra input text for DHCID: "
+ << dhcid_str);
+ }
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText, "Failed to construct DHCID from '" <<
+ dhcid_str << "': " << ex.what());
}
}
+/// \brief Constructor with a context of MasterLexer.
+///
+/// The \c lexer should point to the beginning of valid textual representation
+/// of a DHCID RDATA.
+///
+/// \throw BadValue if the text is not valid base-64.
+/// \throw MasterLexer::LexerError General parsing error such as missing field.
+///
+/// \param lexer A \c MasterLexer object parsing a master file for the
+/// RDATA to be created
+DHCID::DHCID(MasterLexer& lexer, const Name*,
+ MasterLoader::Options, MasterLoaderCallbacks&) {
+ constructFromLexer(lexer);
+}
+
/// \brief Constructor from wire-format data.
///
/// \param buffer A buffer storing the wire format data.
/// \param rdata_len The length of the RDATA in bytes
-///
-/// <b>Exceptions</b>
-/// \c InvalidRdataLength is thrown if \c rdata_len is than minimum of 3 octets
DHCID::DHCID(InputBuffer& buffer, size_t rdata_len) {
- if (rdata_len < 3) {
- isc_throw(InvalidRdataLength, "DHCID length " << rdata_len <<
- " too short, need at least 3 bytes");
+ if (rdata_len == 0) {
+ isc_throw(InvalidRdataLength, "Missing DHCID rdata");
}
digest_.resize(rdata_len);
@@ -112,7 +134,7 @@ DHCID::toWire(AbstractMessageRenderer& renderer) const {
/// \return A string representation of \c DHCID.
string
DHCID::toText() const {
- return (isc::util::encode::encodeBase64(digest_));
+ return (encodeBase64(digest_));
}
/// \brief Compare two instances of \c DHCID RDATA.
diff --git a/src/lib/dns/rdata/in_1/dhcid_49.h b/src/lib/dns/rdata/in_1/dhcid_49.h
index 90f5fab..81041b0 100644
--- a/src/lib/dns/rdata/in_1/dhcid_49.h
+++ b/src/lib/dns/rdata/in_1/dhcid_49.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -43,6 +43,9 @@ public:
const std::vector<uint8_t>& getDigest() const;
private:
+ // helper for string and lexer constructors
+ void constructFromLexer(MasterLexer& lexer);
+
/// \brief Private data representation
///
/// Opaque data at least 3 octets long as per RFC4701.
diff --git a/src/lib/dns/tests/labelsequence_unittest.cc b/src/lib/dns/tests/labelsequence_unittest.cc
index 62cbcec..a3ac767 100644
--- a/src/lib/dns/tests/labelsequence_unittest.cc
+++ b/src/lib/dns/tests/labelsequence_unittest.cc
@@ -949,7 +949,7 @@ public:
foo_example("foo.example."),
org("org")
{
- // explicitely set to non-zero data, to make sure
+ // explicitly set to non-zero data, to make sure
// we don't try to use data we don't set
memset(buf, 0xff, LabelSequence::MAX_SERIALIZED_LENGTH);
}
diff --git a/src/lib/dns/tests/master_loader_unittest.cc b/src/lib/dns/tests/master_loader_unittest.cc
index 653da05..ce9b8f7 100644
--- a/src/lib/dns/tests/master_loader_unittest.cc
+++ b/src/lib/dns/tests/master_loader_unittest.cc
@@ -167,8 +167,8 @@ TEST_F(MasterLoaderTest, basicLoad) {
// Hardcode expected values taken from the test data file, assuming it
// won't change too often.
- EXPECT_EQ(549, loader_->getSize());
- EXPECT_EQ(549, loader_->getPosition());
+ EXPECT_EQ(550, loader_->getSize());
+ EXPECT_EQ(550, loader_->getPosition());
checkBasicRRs();
}
@@ -227,20 +227,20 @@ TEST_F(MasterLoaderTest, includeAndIncremental) {
EXPECT_EQ(zone_data.size(), loader_->getSize());
EXPECT_EQ(first_rr.size(), loader_->getPosition());
- // Read next 4. It includes $INCLUDE processing. Magic number of 549
- // is the size of the test zone file (see above); 506 is the position in
+ // Read next 4. It includes $INCLUDE processing. Magic number of 550
+ // is the size of the test zone file (see above); 507 is the position in
// the file at the end of 4th RR (due to extra comments it's smaller than
// the file size).
loader_->loadIncremental(4);
- EXPECT_EQ(zone_data.size() + 549, loader_->getSize());
- EXPECT_EQ(first_rr.size() + include_str.size() + 506,
+ EXPECT_EQ(zone_data.size() + 550, loader_->getSize());
+ EXPECT_EQ(first_rr.size() + include_str.size() + 507,
loader_->getPosition());
// Read the last one. At this point getSize and getPosition return
// the same value, indicating progress of 100%.
loader_->loadIncremental(1);
- EXPECT_EQ(zone_data.size() + 549, loader_->getSize());
- EXPECT_EQ(zone_data.size() + 549, loader_->getPosition());
+ EXPECT_EQ(zone_data.size() + 550, loader_->getSize());
+ EXPECT_EQ(zone_data.size() + 550, loader_->getPosition());
// we were not interested in checking RRs in this test. clear them to
// not confuse TearDown().
diff --git a/src/lib/dns/tests/masterload_unittest.cc b/src/lib/dns/tests/masterload_unittest.cc
index 7f1961c..dfd901a 100644
--- a/src/lib/dns/tests/masterload_unittest.cc
+++ b/src/lib/dns/tests/masterload_unittest.cc
@@ -167,7 +167,11 @@ TEST_F(MasterLoadTest, loadRRsigs) {
EXPECT_EQ(2, results.size());
}
-TEST_F(MasterLoadTest, loadRRWithComment) {
+// This test was disabled by #2387, because the test data has trailing
+// comments and it (eventually) uses the string RDATA constructor which
+// doesn't support them. This test should be fixed and re-enabled by
+// #2381, or deleted.
+TEST_F(MasterLoadTest, DISABLED_loadRRWithComment) {
// Comment at the end of line should be ignored and the RR should be
// accepted.
rr_stream << "example.com. 3600 IN DNSKEY 256 3 7 "
@@ -180,7 +184,11 @@ TEST_F(MasterLoadTest, loadRRWithComment) {
dnskey_rdata)));
}
-TEST_F(MasterLoadTest, loadRRWithCommentNoSpace) {
+// This test was disabled by #2387, because the test data has trailing
+// comments and it (eventually) uses the string RDATA constructor which
+// doesn't support them. This test should be fixed and re-enabled by
+// #2381, or deleted.
+TEST_F(MasterLoadTest, DISABLED_loadRRWithCommentNoSpace) {
// Similar to the previous one, but there's no space before comments.
// It should still work.
rr_stream << "example.com. 3600 IN DNSKEY 256 3 7 "
@@ -193,7 +201,11 @@ TEST_F(MasterLoadTest, loadRRWithCommentNoSpace) {
dnskey_rdata)));
}
-TEST_F(MasterLoadTest, loadRRWithCommentEmptyComment) {
+// This test was disabled by #2387, because the test data has trailing
+// comments and it (eventually) uses the string RDATA constructor which
+// doesn't support them. This test should be fixed and re-enabled by
+// #2381, or deleted.
+TEST_F(MasterLoadTest, DISABLED_loadRRWithCommentEmptyComment) {
// Similar to the previous one, but there's no data after the ;
// It should still work.
rr_stream << "example.com. 3600 IN DNSKEY 256 3 7 "
@@ -206,7 +218,11 @@ TEST_F(MasterLoadTest, loadRRWithCommentEmptyComment) {
dnskey_rdata)));
}
-TEST_F(MasterLoadTest, loadRRWithCommentEmptyCommentNoSpace) {
+// This test was disabled by #2387, because the test data has trailing
+// comments and it (eventually) uses the string RDATA constructor which
+// doesn't support them. This test should be fixed and re-enabled by
+// #2381, or deleted.
+TEST_F(MasterLoadTest, DISABLED_loadRRWithCommentEmptyCommentNoSpace) {
// Similar to the previous one, but there's no space before or after ;
// It should still work.
rr_stream << "example.com. 3600 IN DNSKEY 256 3 7 "
diff --git a/src/lib/dns/tests/message_unittest.cc b/src/lib/dns/tests/message_unittest.cc
index 835aa48..8aaebaa 100644
--- a/src/lib/dns/tests/message_unittest.cc
+++ b/src/lib/dns/tests/message_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2010-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
@@ -98,7 +98,7 @@ protected:
rrset_rrsig = RRsetPtr(new RRset(test_name, RRClass::IN(),
RRType::RRSIG(), RRTTL(3600)));
rrset_rrsig->addRdata(generic::RRSIG("AAAA 5 3 7200 20100322084538 "
- "20100220084538 1 example.com "
+ "20100220084538 1 example.com. "
"FAKEFAKEFAKEFAKE"));
rrset_aaaa->addRRsig(rrset_rrsig);
}
diff --git a/src/lib/dns/tests/rdata_afsdb_unittest.cc b/src/lib/dns/tests/rdata_afsdb_unittest.cc
index 9bb64b7..9a628cd 100644
--- a/src/lib/dns/tests/rdata_afsdb_unittest.cc
+++ b/src/lib/dns/tests/rdata_afsdb_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -34,7 +34,7 @@ using namespace isc::dns::rdata;
const char* const afsdb_text = "1 afsdb.example.com.";
const char* const afsdb_text2 = "0 root.example.com.";
const char* const too_long_label("012345678901234567890123456789"
- "0123456789012345678901234567890123");
+ "0123456789012345678901234567890123.");
namespace {
class Rdata_AFSDB_Test : public RdataTest {
@@ -68,9 +68,17 @@ TEST_F(Rdata_AFSDB_Test, badText) {
// number of fields (must be 2) is incorrect
EXPECT_THROW(const generic::AFSDB rdata_afsdb("10 afsdb. example.com."),
InvalidRdataText);
+ // No origin and relative
+ EXPECT_THROW(const generic::AFSDB rdata_afsdb("1 afsdb.example.com"),
+ MissingNameOrigin);
// bad name
EXPECT_THROW(const generic::AFSDB rdata_afsdb("1 afsdb.example.com." +
- string(too_long_label)), TooLongLabel);
+ string(too_long_label)), TooLongLabel);
+}
+
+TEST_F(Rdata_AFSDB_Test, copy) {
+ const generic::AFSDB rdata_afsdb2(rdata_afsdb);
+ EXPECT_EQ(0, rdata_afsdb.compare(rdata_afsdb2));
}
TEST_F(Rdata_AFSDB_Test, assignment) {
@@ -119,9 +127,24 @@ TEST_F(Rdata_AFSDB_Test, createFromLexer) {
*test::createRdataUsingLexer(RRType::AFSDB(), RRClass::IN(),
afsdb_text)));
+ // test::createRdataUsingLexer() constructs relative to
+ // "example.org." origin.
+ generic::AFSDB tmp = generic::AFSDB("1 afsdb2.example.org.");
+ EXPECT_EQ(0, tmp.compare(
+ *test::createRdataUsingLexer(RRType::AFSDB(), RRClass::IN(),
+ "1 afsdb2")));
+
// Exceptions cause NULL to be returned.
EXPECT_FALSE(test::createRdataUsingLexer(RRType::AFSDB(), RRClass::IN(),
"1root.example.com."));
+
+ // 65536 is larger than maximum possible subtype
+ EXPECT_FALSE(test::createRdataUsingLexer(RRType::AFSDB(), RRClass::IN(),
+ "65536 afsdb.example.com."));
+
+ // Extra text at end of line
+ EXPECT_FALSE(test::createRdataUsingLexer(RRType::AFSDB(), RRClass::IN(),
+ "1 afsdb.example.com. extra."));
}
TEST_F(Rdata_AFSDB_Test, toWireBuffer) {
@@ -197,9 +220,9 @@ TEST_F(Rdata_AFSDB_Test, compare) {
EXPECT_EQ(0, rdata_afsdb.compare(generic::AFSDB("1 "
"AFSDB.example.com.")));
- const generic::AFSDB small1("10 afsdb.example.com");
- const generic::AFSDB large1("65535 afsdb.example.com");
- const generic::AFSDB large2("256 afsdb.example.com");
+ const generic::AFSDB small1("10 afsdb.example.com.");
+ const generic::AFSDB large1("65535 afsdb.example.com.");
+ const generic::AFSDB large2("256 afsdb.example.com.");
// confirm these are compared as unsigned values
EXPECT_GT(0, rdata_afsdb.compare(large1));
@@ -210,7 +233,7 @@ TEST_F(Rdata_AFSDB_Test, compare) {
EXPECT_LT(0, large2.compare(small1));
// another AFSDB whose server name is larger than that of rdata_afsdb.
- const generic::AFSDB large3("256 zzzzz.example.com");
+ const generic::AFSDB large3("256 zzzzz.example.com.");
EXPECT_GT(0, large2.compare(large3));
EXPECT_LT(0, large3.compare(large2));
diff --git a/src/lib/dns/tests/rdata_dhcid_unittest.cc b/src/lib/dns/tests/rdata_dhcid_unittest.cc
index 8d56c0e..77baccd 100644
--- a/src/lib/dns/tests/rdata_dhcid_unittest.cc
+++ b/src/lib/dns/tests/rdata_dhcid_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -12,6 +12,8 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
+#include <exceptions/exceptions.h>
+
#include <util/buffer.h>
#include <dns/rdataclass.h>
#include <util/encode/base64.h>
@@ -23,6 +25,7 @@
using isc::UnitTestUtil;
using namespace std;
+using namespace isc;
using namespace isc::dns;
using namespace isc::util;
using namespace isc::util::encode;
@@ -30,25 +33,69 @@ using namespace isc::dns::rdata;
namespace {
-const string string_dhcid(
- "0LIg0LvQtdGB0YMg0YDQvtC00LjQu9Cw0YHRjCDRkdC70L7Rh9C60LA=");
-
-const in::DHCID rdata_dhcid(string_dhcid);
-
class Rdata_DHCID_Test : public RdataTest {
+protected:
+ Rdata_DHCID_Test() :
+ dhcid_txt("0LIg0LvQtdGB0YMg0YDQvtC00LjQu9Cw0YHRjCDRkdC70L7Rh9C60LA="),
+ rdata_dhcid(dhcid_txt)
+ {}
+
+ void checkFromText_None(const string& rdata_str) {
+ checkFromText<in::DHCID, isc::Exception, isc::Exception>(
+ rdata_str, rdata_dhcid, false, false);
+ }
+
+ void checkFromText_BadValue(const string& rdata_str) {
+ checkFromText<in::DHCID, BadValue, BadValue>(
+ rdata_str, rdata_dhcid, true, true);
+ }
+
+ void checkFromText_LexerError(const string& rdata_str) {
+ checkFromText
+ <in::DHCID, InvalidRdataText, MasterLexer::LexerError>(
+ rdata_str, rdata_dhcid, true, true);
+ }
+
+ void checkFromText_BadString(const string& rdata_str) {
+ checkFromText
+ <in::DHCID, InvalidRdataText, isc::Exception>(
+ rdata_str, rdata_dhcid, true, false);
+ }
+
+ const string dhcid_txt;
+ const in::DHCID rdata_dhcid;
};
-TEST_F(Rdata_DHCID_Test, createFromString) {
- const in::DHCID rdata_dhcid2(string_dhcid);
- EXPECT_EQ(0, rdata_dhcid2.compare(rdata_dhcid));
-}
+TEST_F(Rdata_DHCID_Test, fromText) {
+ EXPECT_EQ(dhcid_txt, rdata_dhcid.toText());
+
+ // Space in digest data is OK
+ checkFromText_None(
+ "0LIg0LvQtdGB0YMg 0YDQvtC00LjQu9Cw 0YHRjCDRkdC70L7R h9C60LA=");
+
+ // Multi-line digest data is OK, if enclosed in parentheses
+ checkFromText_None(
+ "( 0LIg0LvQtdGB0YMg0YDQvtC00LjQu9Cw\n0YHRjCDRkdC70L7R h9C60LA= )");
-TEST_F(Rdata_DHCID_Test, badBase64) {
- EXPECT_THROW(const in::DHCID rdata_dhcid_bad("00"), isc::BadValue);
+ // Trailing garbage. This should cause only the string constructor
+ // to fail, but the lexer constructor must be able to continue
+ // parsing from it.
+ checkFromText_BadString(
+ "0LIg0LvQtdGB0YMg0YDQvtC00LjQu9Cw0YHRjCDRkdC70L7Rh9C60LA="
+ " ; comment\n"
+ "AAIBY2/AuCccgoJbsaxcQc9TUapptP69lOjxfNuVAA2kjEA=");
}
-TEST_F(Rdata_DHCID_Test, badLength) {
- EXPECT_THROW(const in::DHCID rdata_dhcid_bad("MDA="), InvalidRdataLength);
+TEST_F(Rdata_DHCID_Test, badText) {
+ // missing digest data
+ checkFromText_LexerError("");
+
+ // invalid base64
+ checkFromText_BadValue("EEeeeeeeEEEeeeeeeGaaahAAAAAAAAHHHHHHHHHHH!=");
+
+ // unterminated multi-line base64
+ checkFromText_LexerError(
+ "( 0LIg0LvQtdGB0YMg0YDQvtC00LjQu9Cw\n0YHRjCDRkdC70L7R h9C60LA=");
}
TEST_F(Rdata_DHCID_Test, copy) {
@@ -60,17 +107,17 @@ TEST_F(Rdata_DHCID_Test, createFromWire) {
EXPECT_EQ(0, rdata_dhcid.compare(
*rdataFactoryFromFile(RRType("DHCID"), RRClass("IN"),
"rdata_dhcid_fromWire")));
+
+ InputBuffer buffer(NULL, 0);
+ EXPECT_THROW(in::DHCID(buffer, 0), InvalidRdataLength);
+
// TBD: more tests
}
TEST_F(Rdata_DHCID_Test, createFromLexer) {
EXPECT_EQ(0, rdata_dhcid.compare(
*test::createRdataUsingLexer(RRType::DHCID(), RRClass::IN(),
- string_dhcid)));
-
- // Exceptions cause NULL to be returned.
- EXPECT_FALSE(test::createRdataUsingLexer(RRType::DHCID(), RRClass::IN(),
- "00"));
+ dhcid_txt)));
}
TEST_F(Rdata_DHCID_Test, toWireRenderer) {
@@ -92,13 +139,13 @@ TEST_F(Rdata_DHCID_Test, toWireBuffer) {
}
TEST_F(Rdata_DHCID_Test, toText) {
- EXPECT_EQ(string_dhcid, rdata_dhcid.toText());
+ EXPECT_EQ(dhcid_txt, rdata_dhcid.toText());
}
TEST_F(Rdata_DHCID_Test, getDHCIDDigest) {
- const string string_dhcid1(encodeBase64(rdata_dhcid.getDigest()));
+ const string dhcid_txt1(encodeBase64(rdata_dhcid.getDigest()));
- EXPECT_EQ(string_dhcid, string_dhcid1);
+ EXPECT_EQ(dhcid_txt, dhcid_txt1);
}
TEST_F(Rdata_DHCID_Test, compare) {
@@ -117,6 +164,6 @@ TEST_F(Rdata_DHCID_Test, compare) {
EXPECT_GT(rdata_dhcid3.compare(rdata_dhcid2), 0);
// comparison attempt between incompatible RR types should be rejected
- EXPECT_THROW(rdata_dhcid.compare(*rdata_nomatch), bad_cast);
+ EXPECT_THROW(rdata_dhcid.compare(*rdata_nomatch), bad_cast);
}
}
diff --git a/src/lib/dns/tests/rdata_dnskey_unittest.cc b/src/lib/dns/tests/rdata_dnskey_unittest.cc
index 58d29bf..872dc2a 100644
--- a/src/lib/dns/tests/rdata_dnskey_unittest.cc
+++ b/src/lib/dns/tests/rdata_dnskey_unittest.cc
@@ -37,98 +37,168 @@ using namespace isc::dns::rdata;
namespace {
class Rdata_DNSKEY_Test : public RdataTest {
- // there's nothing to specialize
+protected:
+ Rdata_DNSKEY_Test() :
+ dnskey_txt("257 3 5 BEAAAAOhHQDBrhQbtphgq2wQUpEQ5t4DtUHxoMV"
+ "Fu2hWLDMvoOMRXjGrhhCeFvAZih7yJHf8ZGfW6hd38hXG/x"
+ "ylYCO6Krpbdojwx8YMXLA5/kA+u50WIL8ZR1R6KTbsYVMf/"
+ "Qx5RiNbPClw+vT+U8eXEJmO20jIS1ULgqy347cBB1zMnnz/"
+ "4LJpA0da9CbKj3A254T515sNIMcwsB8/2+2E63/zZrQzBkj"
+ "0BrN/9Bexjpiks3jRhZatEsXn3dTy47R09Uix5WcJt+xzqZ"
+ "7+ysyLKOOedS39Z7SDmsn2eA0FKtQpwA6LXeG2w+jxmw3oA"
+ "8lVUgEf/rzeC/bByBNsO70aEFTd"),
+ dnskey_txt2("257 3 5 YmluZDEwLmlzYy5vcmc="),
+ rdata_dnskey(dnskey_txt),
+ rdata_dnskey2(dnskey_txt2)
+ {}
+
+ void checkFromText_None(const string& rdata_str) {
+ checkFromText<generic::DNSKEY, isc::Exception, isc::Exception>(
+ rdata_str, rdata_dnskey2, false, false);
+ }
+
+ void checkFromText_InvalidText(const string& rdata_str) {
+ checkFromText<generic::DNSKEY, InvalidRdataText, InvalidRdataText>(
+ rdata_str, rdata_dnskey2, true, true);
+ }
+
+ void checkFromText_InvalidLength(const string& rdata_str) {
+ checkFromText<generic::DNSKEY, InvalidRdataLength, InvalidRdataLength>(
+ rdata_str, rdata_dnskey2, true, true);
+ }
+
+ void checkFromText_BadValue(const string& rdata_str) {
+ checkFromText<generic::DNSKEY, BadValue, BadValue>(
+ rdata_str, rdata_dnskey2, true, true);
+ }
+
+ void checkFromText_LexerError(const string& rdata_str) {
+ checkFromText
+ <generic::DNSKEY, InvalidRdataText, MasterLexer::LexerError>(
+ rdata_str, rdata_dnskey2, true, true);
+ }
+
+ void checkFromText_BadString(const string& rdata_str) {
+ checkFromText
+ <generic::DNSKEY, InvalidRdataText, isc::Exception>(
+ rdata_str, rdata_dnskey2, true, false);
+ }
+
+ const string dnskey_txt;
+ const string dnskey_txt2;
+ const generic::DNSKEY rdata_dnskey;
+ const generic::DNSKEY rdata_dnskey2;
};
-string dnskey_txt("257 3 5 BEAAAAOhHQDBrhQbtphgq2wQUpEQ5t4DtUHxoMV"
- "Fu2hWLDMvoOMRXjGrhhCeFvAZih7yJHf8ZGfW6hd38hXG/x"
- "ylYCO6Krpbdojwx8YMXLA5/kA+u50WIL8ZR1R6KTbsYVMf/"
- "Qx5RiNbPClw+vT+U8eXEJmO20jIS1ULgqy347cBB1zMnnz/"
- "4LJpA0da9CbKj3A254T515sNIMcwsB8/2+2E63/zZrQzBkj"
- "0BrN/9Bexjpiks3jRhZatEsXn3dTy47R09Uix5WcJt+xzqZ"
- "7+ysyLKOOedS39Z7SDmsn2eA0FKtQpwA6LXeG2w+jxmw3oA"
- "8lVUgEf/rzeC/bByBNsO70aEFTd");
-
TEST_F(Rdata_DNSKEY_Test, fromText) {
- generic::DNSKEY rdata_dnskey(dnskey_txt);
EXPECT_EQ(dnskey_txt, rdata_dnskey.toText());
-}
-TEST_F(Rdata_DNSKEY_Test, assign) {
- generic::DNSKEY rdata_dnskey(dnskey_txt);
- generic::DNSKEY rdata_dnskey2 = rdata_dnskey;
- EXPECT_EQ(0, rdata_dnskey.compare(rdata_dnskey2));
-}
+ // Space in key data is OK
+ checkFromText_None("257 3 5 YmluZDEw LmlzYy5vcmc=");
+
+ // Delimited number in key data is OK
+ checkFromText_None("257 3 5 YmluZDEwLmlzYy 5 vcmc=");
+
+ // Missing keydata is OK
+ EXPECT_NO_THROW(const generic::DNSKEY rdata_dnskey3("257 3 5"));
+
+ // Key data too short for RSA/MD5 algorithm is OK when
+ // constructing. But getTag() on this object would throw (see
+ // .getTag tests).
+ EXPECT_NO_THROW(const generic::DNSKEY rdata_dnskey4("1 1 1 YQ=="));
+
+ // Flags field out of range
+ checkFromText_InvalidText("65536 3 5 YmluZDEwLmlzYy5vcmc=");
+
+ // Protocol field out of range
+ checkFromText_InvalidText("257 256 5 YmluZDEwLmlzYy5vcmc=");
+
+ // Algorithm field out of range
+ checkFromText_InvalidText("257 3 256 YmluZDEwLmlzYy5vcmc=");
+
+ // Missing algorithm field
+ checkFromText_LexerError("257 3 YmluZDEwLmlzYy5vcmc=");
-TEST_F(Rdata_DNSKEY_Test, badText) {
- EXPECT_THROW(generic::DNSKEY("257 3 5"),
- InvalidRdataText);
- EXPECT_THROW(generic::DNSKEY("99999 3 5 BAAAAAAAAAAAD"),
- InvalidRdataText);
- EXPECT_THROW(generic::DNSKEY("257 300 5 BAAAAAAAAAAAD"),
- InvalidRdataText);
- EXPECT_THROW(generic::DNSKEY("257 3 500 BAAAAAAAAAAAD"),
- InvalidRdataText);
- EXPECT_THROW(generic::DNSKEY("257 3 5 BAAAAAAAAAAAD"), BadValue);
+ // Invalid key data field (not Base64)
+ checkFromText_BadValue("257 3 5 BAAAAAAAAAAAD");
+
+ // String instead of number
+ checkFromText_LexerError("foo 3 5 YmluZDEwLmlzYy5vcmc=");
+ checkFromText_LexerError("257 foo 5 YmluZDEwLmlzYy5vcmc=");
+ checkFromText_LexerError("257 3 foo YmluZDEwLmlzYy5vcmc=");
+
+ // Trailing garbage. This should cause only the string constructor
+ // to fail, but the lexer constructor must be able to continue
+ // parsing from it.
+ checkFromText_BadString("257 3 5 YmluZDEwLmlzYy5vcmc= ; comment\n"
+ "257 3 4 YmluZDEwLmlzYy5vcmc=");
+
+ // Unmatched parenthesis should cause a lexer error
+ checkFromText_LexerError("257 3 5 )YmluZDEwLmlzYy5vcmc=");
}
-TEST_F(Rdata_DNSKEY_Test, DISABLED_badText) {
- // Should this be allowed? Probably not. But the test currently fails.
- EXPECT_THROW(generic::DNSKEY("257 3 5BEAAEFTd"),
- InvalidRdataText);
- // How about this? It's even more confusing for the parser because
- // it could be ambiguous '51 EAAA' vs '5 1EAA..'
- EXPECT_THROW(generic::DNSKEY("257 3 51EAAEFTd"),
- InvalidRdataText);
+TEST_F(Rdata_DNSKEY_Test, assign) {
+ generic::DNSKEY rdata_dnskey2("257 3 5 YQ==");
+ rdata_dnskey2 = rdata_dnskey;
+ EXPECT_EQ(0, rdata_dnskey.compare(rdata_dnskey2));
}
TEST_F(Rdata_DNSKEY_Test, createFromLexer) {
- generic::DNSKEY rdata_dnskey(dnskey_txt);
EXPECT_EQ(0, rdata_dnskey.compare(
*test::createRdataUsingLexer(RRType::DNSKEY(), RRClass::IN(),
dnskey_txt)));
-
- // Exceptions cause NULL to be returned.
- EXPECT_FALSE(test::createRdataUsingLexer(RRType::DNSKEY(), RRClass::IN(),
- "257 3 5"));
}
TEST_F(Rdata_DNSKEY_Test, toWireRenderer) {
renderer.skip(2);
- generic::DNSKEY rdata_dnskey(dnskey_txt);
rdata_dnskey.toWire(renderer);
vector<unsigned char> data;
- UnitTestUtil::readWireData("rdata_dnskey_fromWire", data);
+ UnitTestUtil::readWireData("rdata_dnskey_fromWire.wire", data);
EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
static_cast<const uint8_t *>(renderer.getData()) + 2,
renderer.getLength() - 2, &data[2], data.size() - 2);
}
TEST_F(Rdata_DNSKEY_Test, toWireBuffer) {
- generic::DNSKEY rdata_dnskey(dnskey_txt);
rdata_dnskey.toWire(obuffer);
+
+ vector<unsigned char> data;
+ UnitTestUtil::readWireData("rdata_dnskey_fromWire.wire", data);
+ EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+ obuffer.getData(), obuffer.getLength(),
+ &data[2], data.size() - 2);
}
TEST_F(Rdata_DNSKEY_Test, createFromWire) {
- generic::DNSKEY rdata_dnskey(dnskey_txt);
EXPECT_EQ(0, rdata_dnskey.compare(
*rdataFactoryFromFile(RRType("DNSKEY"), RRClass("IN"),
- "rdata_dnskey_fromWire")));
+ "rdata_dnskey_fromWire.wire")));
+
+ // Missing keydata is OK
+ const generic::DNSKEY rdata_dnskey_missing_keydata("257 3 5");
+ EXPECT_EQ(0, rdata_dnskey_missing_keydata.compare(
+ *rdataFactoryFromFile(RRType("DNSKEY"), RRClass("IN"),
+ "rdata_dnskey_empty_keydata_fromWire.wire")));
}
TEST_F(Rdata_DNSKEY_Test, getTag) {
- generic::DNSKEY rdata_dnskey(dnskey_txt);
EXPECT_EQ(12892, rdata_dnskey.getTag());
+
+ // Short keydata with algorithm RSA/MD5 must throw.
+ const generic::DNSKEY rdata_dnskey_short_keydata1("1 1 1 YQ==");
+ EXPECT_THROW(rdata_dnskey_short_keydata1.getTag(), isc::OutOfRange);
+
+ // Short keydata with algorithm not RSA/MD5 must not throw.
+ const generic::DNSKEY rdata_dnskey_short_keydata2("257 3 5 YQ==");
+ EXPECT_NO_THROW(rdata_dnskey_short_keydata2.getTag());
}
TEST_F(Rdata_DNSKEY_Test, getAlgorithm) {
- generic::DNSKEY rdata_dnskey(dnskey_txt);
EXPECT_EQ(5, rdata_dnskey.getAlgorithm());
}
TEST_F(Rdata_DNSKEY_Test, getFlags) {
- generic::DNSKEY rdata_dnskey(dnskey_txt);
EXPECT_EQ(257, rdata_dnskey.getFlags());
}
diff --git a/src/lib/dns/tests/rdata_ds_like_unittest.cc b/src/lib/dns/tests/rdata_ds_like_unittest.cc
index c797199..ae6a360 100644
--- a/src/lib/dns/tests/rdata_ds_like_unittest.cc
+++ b/src/lib/dns/tests/rdata_ds_like_unittest.cc
@@ -47,15 +47,15 @@ template<> RRTYPE<generic::DLV>::RRTYPE() : RRType(RRType::DLV()) {}
template <class DS_LIKE>
class Rdata_DS_LIKE_Test : public RdataTest {
protected:
- static DS_LIKE const rdata_ds_like;
+ Rdata_DS_LIKE_Test() :
+ ds_like_txt("12892 5 2 F1E184C0E1D615D20EB3C223ACED3B03C773DD952D"
+ "5F0EB5C777586DE18DA6B5"),
+ rdata_ds_like(ds_like_txt)
+ {}
+ const string ds_like_txt;
+ const DS_LIKE rdata_ds_like;
};
-string ds_like_txt("12892 5 2 F1E184C0E1D615D20EB3C223ACED3B03C773DD952D"
- "5F0EB5C777586DE18DA6B5");
-
-template <class DS_LIKE>
-DS_LIKE const Rdata_DS_LIKE_Test<DS_LIKE>::rdata_ds_like(ds_like_txt);
-
// The list of types we want to test.
typedef testing::Types<generic::DS, generic::DLV> Implementations;
@@ -70,7 +70,7 @@ TYPED_TEST(Rdata_DS_LIKE_Test, createFromText) {
}
TYPED_TEST(Rdata_DS_LIKE_Test, toText_DS_LIKE) {
- EXPECT_EQ(ds_like_txt, this->rdata_ds_like.toText());
+ EXPECT_EQ(this->ds_like_txt, this->rdata_ds_like.toText());
}
TYPED_TEST(Rdata_DS_LIKE_Test, badText_DS_LIKE) {
@@ -96,7 +96,7 @@ TYPED_TEST(Rdata_DS_LIKE_Test, createFromWire_DS_LIKE) {
TYPED_TEST(Rdata_DS_LIKE_Test, createFromLexer_DS_LIKE) {
EXPECT_EQ(0, this->rdata_ds_like.compare(
*test::createRdataUsingLexer(RRTYPE<TypeParam>(), RRClass::IN(),
- ds_like_txt)));
+ this->ds_like_txt)));
// Whitespace is okay
EXPECT_EQ(0, this->rdata_ds_like.compare(
@@ -121,13 +121,13 @@ TYPED_TEST(Rdata_DS_LIKE_Test, createFromLexer_DS_LIKE) {
}
TYPED_TEST(Rdata_DS_LIKE_Test, assignment_DS_LIKE) {
- TypeParam copy((string(ds_like_txt)));
+ TypeParam copy(this->ds_like_txt);
copy = this->rdata_ds_like;
EXPECT_EQ(0, copy.compare(this->rdata_ds_like));
// Check if the copied data is valid even after the original is deleted
TypeParam* copy2 = new TypeParam(this->rdata_ds_like);
- TypeParam copy3((string(ds_like_txt)));
+ TypeParam copy3(this->ds_like_txt);
copy3 = *copy2;
delete copy2;
EXPECT_EQ(0, copy3.compare(this->rdata_ds_like));
@@ -143,7 +143,7 @@ TYPED_TEST(Rdata_DS_LIKE_Test, getTag_DS_LIKE) {
TYPED_TEST(Rdata_DS_LIKE_Test, toWireRenderer) {
Rdata_DS_LIKE_Test<TypeParam>::renderer.skip(2);
- TypeParam rdata_ds_like(ds_like_txt);
+ TypeParam rdata_ds_like(this->ds_like_txt);
rdata_ds_like.toWire(this->renderer);
vector<unsigned char> data;
@@ -156,7 +156,7 @@ TYPED_TEST(Rdata_DS_LIKE_Test, toWireRenderer) {
}
TYPED_TEST(Rdata_DS_LIKE_Test, toWireBuffer) {
- TypeParam rdata_ds_like(ds_like_txt);
+ TypeParam rdata_ds_like(this->ds_like_txt);
rdata_ds_like.toWire(this->obuffer);
}
@@ -179,8 +179,33 @@ string ds_like_txt6("12892 5 2 F2E184C0E1D615D20EB3C223ACED3B03C773DD952D"
"5F0EB5C777586DE18DA6B555");
TYPED_TEST(Rdata_DS_LIKE_Test, compare) {
+ const string ds_like_txt1(
+ "12892 5 2 F1E184C0E1D615D20EB3C223ACED3B03C773DD952D"
+ "5F0EB5C777586DE18DA6B5");
+ // different tag
+ const string ds_like_txt2(
+ "12893 5 2 F1E184C0E1D615D20EB3C223ACED3B03C773DD952D"
+ "5F0EB5C777586DE18DA6B5");
+ // different algorithm
+ const string ds_like_txt3(
+ "12892 6 2 F1E184C0E1D615D20EB3C223ACED3B03C773DD952D"
+ "5F0EB5C777586DE18DA6B5");
+ // different digest type
+ const string ds_like_txt4(
+ "12892 5 3 F1E184C0E1D615D20EB3C223ACED3B03C773DD952D"
+ "5F0EB5C777586DE18DA6B5");
+ // different digest
+ const string ds_like_txt5(
+ "12892 5 2 F2E184C0E1D615D20EB3C223ACED3B03C773DD952D"
+ "5F0EB5C777586DE18DA6B5");
+ // different digest length
+ const string ds_like_txt6(
+ "12892 5 2 F2E184C0E1D615D20EB3C223ACED3B03C773DD952D"
+ "5F0EB5C777586DE18DA6B555");
+
// trivial case: self equivalence
- EXPECT_EQ(0, TypeParam(ds_like_txt).compare(TypeParam(ds_like_txt)));
+ EXPECT_EQ(0, TypeParam(this->ds_like_txt).
+ compare(TypeParam(this->ds_like_txt)));
// non-equivalence tests
EXPECT_LT(TypeParam(ds_like_txt1).compare(TypeParam(ds_like_txt2)), 0);
diff --git a/src/lib/dns/tests/rdata_nsec3_unittest.cc b/src/lib/dns/tests/rdata_nsec3_unittest.cc
index 0fec3eb..1f35713 100644
--- a/src/lib/dns/tests/rdata_nsec3_unittest.cc
+++ b/src/lib/dns/tests/rdata_nsec3_unittest.cc
@@ -17,7 +17,6 @@
#include <exceptions/exceptions.h>
#include <util/buffer.h>
-#include <util/encode/hex.h>
#include <dns/exceptions.h>
#include <dns/messagerenderer.h>
#include <dns/rdata.h>
@@ -35,7 +34,6 @@ using namespace std;
using namespace isc;
using namespace isc::dns;
using namespace isc::util;
-using namespace isc::util::encode;
using namespace isc::dns::rdata;
namespace {
@@ -43,55 +41,114 @@ namespace {
// Note: some tests can be shared with NSEC3PARAM. They are unified as
// typed tests defined in nsec3param_like_unittest.
class Rdata_NSEC3_Test : public RdataTest {
- // there's nothing to specialize
-public:
+protected:
Rdata_NSEC3_Test() :
nsec3_txt("1 1 1 D399EAAB H9RSFB7FPF2L8HG35CMPC765TDK23RP6 "
- "NS SOA RRSIG DNSKEY NSEC3PARAM"),
- nsec3_nosalt_txt("1 1 1 - H9RSFB7FPF2L8HG35CMPC765TDK23RP6 A" )
+ "A NS SOA"),
+ nsec3_nosalt_txt("1 1 1 - H9RSFB7FPF2L8HG35CMPC765TDK23RP6 A NS SOA"),
+ nsec3_notype_txt("1 1 1 D399EAAB H9RSFB7FPF2L8HG35CMPC765TDK23RP6"),
+ rdata_nsec3(nsec3_txt)
{}
+
+ void checkFromText_None(const string& rdata_str) {
+ checkFromText<generic::NSEC3, isc::Exception, isc::Exception>(
+ rdata_str, rdata_nsec3, false, false);
+ }
+
+ void checkFromText_InvalidText(const string& rdata_str) {
+ checkFromText<generic::NSEC3, InvalidRdataText, InvalidRdataText>(
+ rdata_str, rdata_nsec3, true, true);
+ }
+
+ void checkFromText_BadValue(const string& rdata_str) {
+ checkFromText<generic::NSEC3, BadValue, BadValue>(
+ rdata_str, rdata_nsec3, true, true);
+ }
+
+ void checkFromText_LexerError(const string& rdata_str) {
+ checkFromText
+ <generic::NSEC3, InvalidRdataText, MasterLexer::LexerError>(
+ rdata_str, rdata_nsec3, true, true);
+ }
+
+ void checkFromText_BadString(const string& rdata_str) {
+ checkFromText
+ <generic::NSEC3, InvalidRdataText, isc::Exception>(
+ rdata_str, rdata_nsec3, true, false);
+ }
+
const string nsec3_txt;
const string nsec3_nosalt_txt;
+ const string nsec3_notype_txt;
+ const generic::NSEC3 rdata_nsec3;
};
TEST_F(Rdata_NSEC3_Test, fromText) {
- // A normal case: the test constructor should successfully parse the
- // text and construct nsec3_txt. It will be tested against the wire format
- // representation in the createFromWire test.
-
- // hash that has the possible max length (see badText about the magic
- // numbers)
+ // Hash that has the possible max length
EXPECT_EQ(255, generic::NSEC3("1 1 1 D399EAAB " +
string((255 * 8) / 5, '0') +
" NS").getNext().size());
- // type bitmap is empty. it's possible and allowed for NSEC3.
- EXPECT_NO_THROW(generic::NSEC3(
- "1 1 1 D399EAAB H9RSFB7FPF2L8HG35CMPC765TDK23RP6"));
-}
-
-TEST_F(Rdata_NSEC3_Test, badText) {
- EXPECT_THROW(generic::NSEC3("1 1 1 ADDAFEEE "
- "0123456789ABCDEFGHIJKLMNOPQRSTUV "
- "BIFF POW SPOON"),
- InvalidRdataText);
- EXPECT_THROW(generic::NSEC3("1 1 1 ADDAFEEE "
- "WXYZWXYZWXYZ=WXYZWXYZ==WXYZWXYZW A NS SOA"),
- BadValue); // bad base32hex
- EXPECT_THROW(generic::NSEC3("1 1 1000000 ADDAFEEE "
- "0123456789ABCDEFGHIJKLMNOPQRSTUV A NS SOA"),
- InvalidRdataText);
-
- // Next hash shouldn't be padded
- EXPECT_THROW(generic::NSEC3("1 1 1 ADDAFEEE CPNMU=== A NS SOA"),
- InvalidRdataText);
-
// Hash is too long. Max = 255 bytes, base32-hex converts each 5 bytes
// of the original to 8 characters, so 260 * 8 / 5 is the smallest length
// of the encoded string that exceeds the max and doesn't require padding.
- EXPECT_THROW(generic::NSEC3("1 1 1 D399EAAB " + string((260 * 8) / 5, '0') +
- " NS"),
- InvalidRdataText);
+ checkFromText_InvalidText("1 1 1 D399EAAB " + string((260 * 8) / 5, '0') +
+ " A NS SOA");
+
+ // Type bitmap is empty. it's possible and allowed for NSEC3.
+ EXPECT_NO_THROW(const generic::NSEC3 rdata_notype_nsec3(nsec3_notype_txt));
+
+ // Empty salt is also okay.
+ EXPECT_NO_THROW(const generic::NSEC3 rdata_nosalt_nsec3(nsec3_nosalt_txt));
+
+ // Bad type mnemonics
+ checkFromText_InvalidText("1 1 1 D399EAAB H9RSFB7FPF2L8HG35CMPC765TDK23RP6"
+ " BIFF POW SPOON");
+
+ // Bad base32hex
+ checkFromText_BadValue("1 1 1 D399EAAB "
+ "WXYZWXYZWXYZ=WXYZWXYZ==WXYZWXYZW A NS SOA");
+
+ // Hash algorithm out of range
+ checkFromText_InvalidText("256 1 1 D399EAAB "
+ "H9RSFB7FPF2L8HG35CMPC765TDK23RP6 A NS SOA");
+
+ // Flags out of range
+ checkFromText_InvalidText("1 256 1 D399EAAB "
+ "H9RSFB7FPF2L8HG35CMPC765TDK23RP6 A NS SOA");
+
+ // Iterations out of range
+ checkFromText_InvalidText("1 1 65536 D399EAAB "
+ "H9RSFB7FPF2L8HG35CMPC765TDK23RP6 A NS SOA");
+
+ // Space is not allowed in salt or the next hash. This actually
+ // causes the Base32 decoder that parses the next hash that comes
+ // afterwards, to throw.
+ checkFromText_BadValue("1 1 1 D399 EAAB H9RSFB7FPF2L8"
+ "HG35CMPC765TDK23RP6 A NS SOA");
+
+ // Next hash must not contain padding (trailing '=' characters)
+ checkFromText_InvalidText("1 1 1 D399EAAB "
+ "AAECAwQFBgcICQoLDA0ODw== A NS SOA");
+
+ // String instead of number
+ checkFromText_LexerError("foo 1 1 D399EAAB "
+ "H9RSFB7FPF2L8HG35CMPC765TDK23RP6 A NS SOA");
+ checkFromText_LexerError("1 foo 1 D399EAAB "
+ "H9RSFB7FPF2L8HG35CMPC765TDK23RP6 A NS SOA");
+ checkFromText_LexerError("1 1 foo D399EAAB "
+ "H9RSFB7FPF2L8HG35CMPC765TDK23RP6 A NS SOA");
+
+ // Trailing garbage. This should cause only the string constructor
+ // to fail, but the lexer constructor must be able to continue
+ // parsing from it.
+ checkFromText_BadString(
+ "1 1 1 D399EAAB H9RSFB7FPF2L8HG35CMPC765TDK23RP6 A NS SOA ;comment\n"
+ "1 1 1 D399EAAB H9RSFB7FPF2L8HG35CMPC765TDK23RP6 A NS SOA");
+
+ // Unmatched parenthesis should cause a lexer error
+ checkFromText_LexerError("1 1 1 D399EAAB "
+ "H9RSFB7FPF2L8HG35CMPC765TDK23RP6 A ) NS SOA");
}
TEST_F(Rdata_NSEC3_Test, createFromWire) {
@@ -131,19 +188,18 @@ TEST_F(Rdata_NSEC3_Test, createFromWire) {
}
TEST_F(Rdata_NSEC3_Test, createFromLexer) {
- const generic::NSEC3 rdata_nsec3(nsec3_txt);
EXPECT_EQ(0, rdata_nsec3.compare(
*test::createRdataUsingLexer(RRType::NSEC3(), RRClass::IN(),
nsec3_txt)));
- // Exceptions cause NULL to be returned.
- EXPECT_FALSE(test::createRdataUsingLexer(RRType::NSEC3(), RRClass::IN(),
- "1 1 1 ADDAFEEE CPNMU=== "
- "A NS SOA"));
+ // empty salt is also okay.
+ const generic::NSEC3 rdata_nosalt_nsec3(nsec3_nosalt_txt);
+ EXPECT_EQ(0, rdata_nosalt_nsec3.compare(
+ *test::createRdataUsingLexer(RRType::NSEC3(), RRClass::IN(),
+ nsec3_nosalt_txt)));
}
TEST_F(Rdata_NSEC3_Test, assign) {
- generic::NSEC3 rdata_nsec3(nsec3_txt);
generic::NSEC3 other_nsec3 = rdata_nsec3;
EXPECT_EQ(0, rdata_nsec3.compare(other_nsec3));
}
diff --git a/src/lib/dns/tests/rdata_nsec3param_unittest.cc b/src/lib/dns/tests/rdata_nsec3param_unittest.cc
index 115d3d3..4fccbf3 100644
--- a/src/lib/dns/tests/rdata_nsec3param_unittest.cc
+++ b/src/lib/dns/tests/rdata_nsec3param_unittest.cc
@@ -16,8 +16,6 @@
#include <exceptions/exceptions.h>
-#include <util/encode/base32hex.h>
-#include <util/encode/hex.h>
#include <util/buffer.h>
#include <dns/messagerenderer.h>
#include <dns/rdata.h>
@@ -35,39 +33,107 @@ using namespace std;
using namespace isc;
using namespace isc::dns;
using namespace isc::util;
-using namespace isc::util::encode;
using namespace isc::dns::rdata;
namespace {
class Rdata_NSEC3PARAM_Test : public RdataTest {
-public:
- Rdata_NSEC3PARAM_Test() : nsec3param_txt("1 1 1 D399EAAB") {}
+protected:
+ Rdata_NSEC3PARAM_Test() :
+ nsec3param_txt("1 1 1 D399EAAB"),
+ nsec3param_nosalt_txt("1 1 1 -"),
+ rdata_nsec3param(nsec3param_txt)
+ {}
+
+ void checkFromText_None(const string& rdata_str) {
+ checkFromText<generic::NSEC3PARAM, isc::Exception, isc::Exception>(
+ rdata_str, rdata_nsec3param, false, false);
+ }
+
+ void checkFromText_InvalidText(const string& rdata_str) {
+ checkFromText<generic::NSEC3PARAM, InvalidRdataText, InvalidRdataText>(
+ rdata_str, rdata_nsec3param, true, true);
+ }
+
+ void checkFromText_BadValue(const string& rdata_str) {
+ checkFromText<generic::NSEC3PARAM, BadValue, BadValue>(
+ rdata_str, rdata_nsec3param, true, true);
+ }
+
+ void checkFromText_LexerError(const string& rdata_str) {
+ checkFromText
+ <generic::NSEC3PARAM, InvalidRdataText, MasterLexer::LexerError>(
+ rdata_str, rdata_nsec3param, true, true);
+ }
+
+ void checkFromText_BadString(const string& rdata_str,
+ const generic::NSEC3PARAM& rdata)
+ {
+ checkFromText
+ <generic::NSEC3PARAM, InvalidRdataText, isc::Exception>(
+ rdata_str, rdata, true, false);
+ }
+
const string nsec3param_txt;
+ const string nsec3param_nosalt_txt;
+ const generic::NSEC3PARAM rdata_nsec3param;
};
TEST_F(Rdata_NSEC3PARAM_Test, fromText) {
- // With a salt
- EXPECT_EQ(1, generic::NSEC3PARAM(nsec3param_txt).getHashalg());
- EXPECT_EQ(1, generic::NSEC3PARAM(nsec3param_txt).getFlags());
- // (salt is checked in the toText test)
+ // Empty salt is okay.
+ EXPECT_EQ(0, generic::NSEC3PARAM(nsec3param_nosalt_txt).getSalt().size());
+
+ // Salt is missing.
+ checkFromText_LexerError("1 1 1");
+
+ // Salt has whitespace within. This only fails in the string
+ // constructor, as the lexer constructor stops reading at the end of
+ // its RDATA.
+ const generic::NSEC3PARAM rdata_nsec3param2("1 1 1 D399");
+ checkFromText_BadString("1 1 1 D399 EAAB", rdata_nsec3param2);
+
+ // Hash algorithm out of range.
+ checkFromText_InvalidText("256 1 1 D399EAAB");
+
+ // Flags out of range.
+ checkFromText_InvalidText("1 256 1 D399EAAB");
+
+ // Iterations out of range.
+ checkFromText_InvalidText("1 1 65536 D399EAAB");
+
+ // Bad hex sequence
+ checkFromText_BadValue("1 1 256 D399EAABZOO");
- // With an empty salt
- EXPECT_EQ(0, generic::NSEC3PARAM("1 0 0 -").getSalt().size());
+ // String instead of number
+ checkFromText_LexerError("foo 1 256 D399EAAB");
+ checkFromText_LexerError("1 foo 256 D399EAAB");
+ checkFromText_LexerError("1 1 foo D399EAAB");
+
+ // Trailing garbage. This should cause only the string constructor
+ // to fail, but the lexer constructor must be able to continue
+ // parsing from it.
+ checkFromText_BadString("1 1 1 D399EAAB ; comment\n"
+ "1 1 1 D399EAAB", rdata_nsec3param);
}
TEST_F(Rdata_NSEC3PARAM_Test, toText) {
- const generic::NSEC3PARAM rdata_nsec3param(nsec3param_txt);
EXPECT_EQ(nsec3param_txt, rdata_nsec3param.toText());
-}
-TEST_F(Rdata_NSEC3PARAM_Test, badText) {
- // garbage space at the end
- EXPECT_THROW(generic::NSEC3PARAM("1 1 1 D399EAAB "),
- InvalidRdataText);
+ // Garbage space at the end should be ok. RFC5155 only forbids
+ // whitespace within the salt field, but any whitespace afterwards
+ // should be fine.
+ EXPECT_NO_THROW(generic::NSEC3PARAM("1 1 1 D399EAAB "));
+
+ // Hash algorithm in range.
+ EXPECT_NO_THROW(generic::NSEC3PARAM("255 1 1 D399EAAB"));
+
+ // Flags in range.
+ EXPECT_NO_THROW(generic::NSEC3PARAM("1 255 1 D399EAAB"));
+
+ // Iterations in range.
+ EXPECT_NO_THROW(generic::NSEC3PARAM("1 1 65535 D399EAAB"));
}
TEST_F(Rdata_NSEC3PARAM_Test, createFromWire) {
- const generic::NSEC3PARAM rdata_nsec3param(nsec3param_txt);
EXPECT_EQ(0, rdata_nsec3param.compare(
*rdataFactoryFromFile(RRType::NSEC3PARAM(), RRClass::IN(),
"rdata_nsec3param_fromWire1")));
@@ -87,15 +153,19 @@ TEST_F(Rdata_NSEC3PARAM_Test, createFromWire) {
}
TEST_F(Rdata_NSEC3PARAM_Test, createFromLexer) {
- const generic::NSEC3PARAM rdata_nsec3param(nsec3param_txt);
EXPECT_EQ(0, rdata_nsec3param.compare(
*test::createRdataUsingLexer(RRType::NSEC3PARAM(), RRClass::IN(),
nsec3param_txt)));
+
+ // empty salt is also okay.
+ const generic::NSEC3PARAM rdata_nosalt_nsec3param(nsec3param_nosalt_txt);
+ EXPECT_EQ(0, rdata_nosalt_nsec3param.compare(
+ *test::createRdataUsingLexer(RRType::NSEC3PARAM(), RRClass::IN(),
+ nsec3param_nosalt_txt)));
}
TEST_F(Rdata_NSEC3PARAM_Test, toWireRenderer) {
renderer.skip(2);
- const generic::NSEC3PARAM rdata_nsec3param(nsec3param_txt);
rdata_nsec3param.toWire(renderer);
vector<unsigned char> data;
@@ -106,13 +176,26 @@ TEST_F(Rdata_NSEC3PARAM_Test, toWireRenderer) {
}
TEST_F(Rdata_NSEC3PARAM_Test, toWireBuffer) {
- const generic::NSEC3PARAM rdata_nsec3param(nsec3param_txt);
rdata_nsec3param.toWire(obuffer);
+
+ vector<unsigned char> data;
+ UnitTestUtil::readWireData("rdata_nsec3param_fromWire1", data);
+ EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+ obuffer.getData(), obuffer.getLength(),
+ &data[2], data.size() - 2);
+}
+
+TEST_F(Rdata_NSEC3PARAM_Test, getHashAlg) {
+ EXPECT_EQ(1, rdata_nsec3param.getHashalg());
+}
+
+TEST_F(Rdata_NSEC3PARAM_Test, getFlags) {
+ EXPECT_EQ(1, rdata_nsec3param.getFlags());
}
TEST_F(Rdata_NSEC3PARAM_Test, assign) {
- generic::NSEC3PARAM rdata_nsec3param(nsec3param_txt);
- generic::NSEC3PARAM other_nsec3param = rdata_nsec3param;
+ generic::NSEC3PARAM other_nsec3param("1 1 1 -");
+ other_nsec3param = rdata_nsec3param;
EXPECT_EQ(0, rdata_nsec3param.compare(other_nsec3param));
}
diff --git a/src/lib/dns/tests/rdata_opt_unittest.cc b/src/lib/dns/tests/rdata_opt_unittest.cc
index 5699259..20ccfe4 100644
--- a/src/lib/dns/tests/rdata_opt_unittest.cc
+++ b/src/lib/dns/tests/rdata_opt_unittest.cc
@@ -84,6 +84,6 @@ TEST_F(Rdata_OPT_Test, compare) {
"rdata_opt_fromWire", 2)));
// comparison attempt between incompatible RR types should be rejected
- EXPECT_THROW(rdata_opt.compare(*RdataTest::rdata_nomatch), bad_cast);
+ EXPECT_THROW(rdata_opt.compare(*RdataTest::rdata_nomatch), bad_cast);
}
}
diff --git a/src/lib/dns/tests/rdata_rrsig_unittest.cc b/src/lib/dns/tests/rdata_rrsig_unittest.cc
index d758ff3..2d075ec 100644
--- a/src/lib/dns/tests/rdata_rrsig_unittest.cc
+++ b/src/lib/dns/tests/rdata_rrsig_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2010-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
@@ -36,7 +36,7 @@ using namespace isc::dns::rdata;
namespace {
class Rdata_RRSIG_Test : public RdataTest {
-public:
+protected:
Rdata_RRSIG_Test() :
rrsig_txt("A 5 4 43200 20100223214617 20100222214617 8496 isc.org. "
"evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
@@ -46,6 +46,49 @@ public:
rdata_rrsig(rrsig_txt)
{}
+ void checkFromText_None(const string& rdata_str) {
+ checkFromText<generic::RRSIG, isc::Exception, isc::Exception>(
+ rdata_str, rdata_rrsig, false, false);
+ }
+
+ void checkFromText_InvalidText(const string& rdata_str) {
+ checkFromText<generic::RRSIG, InvalidRdataText, InvalidRdataText>(
+ rdata_str, rdata_rrsig, true, true);
+ }
+
+ void checkFromText_InvalidType(const string& rdata_str) {
+ checkFromText<generic::RRSIG, InvalidRRType, InvalidRRType>(
+ rdata_str, rdata_rrsig, true, true);
+ }
+
+ void checkFromText_InvalidTime(const string& rdata_str) {
+ checkFromText<generic::RRSIG, InvalidTime, InvalidTime>(
+ rdata_str, rdata_rrsig, true, true);
+ }
+
+ void checkFromText_BadValue(const string& rdata_str) {
+ checkFromText<generic::RRSIG, BadValue, BadValue>(
+ rdata_str, rdata_rrsig, true, true);
+ }
+
+ void checkFromText_LexerError(const string& rdata_str) {
+ checkFromText
+ <generic::RRSIG, InvalidRdataText, MasterLexer::LexerError>(
+ rdata_str, rdata_rrsig, true, true);
+ }
+
+ void checkFromText_MissingOrigin(const string& rdata_str) {
+ checkFromText
+ <generic::RRSIG, MissingNameOrigin, MissingNameOrigin>(
+ rdata_str, rdata_rrsig, true, true);
+ }
+
+ void checkFromText_BadString(const string& rdata_str) {
+ checkFromText
+ <generic::RRSIG, InvalidRdataText, isc::Exception>(
+ rdata_str, rdata_rrsig, true, false);
+ }
+
const string rrsig_txt;
const generic::RRSIG rdata_rrsig;
};
@@ -53,52 +96,196 @@ public:
TEST_F(Rdata_RRSIG_Test, fromText) {
EXPECT_EQ(rrsig_txt, rdata_rrsig.toText());
EXPECT_EQ(isc::dns::RRType::A(), rdata_rrsig.typeCovered());
+
+ // Missing signature is OK
+ EXPECT_NO_THROW(const generic::RRSIG sig(
+ "A 5 4 43200 20100223214617 20100222214617 8496 isc.org."));
+
+ // Space in signature data is OK
+ checkFromText_None(
+ "A 5 4 43200 20100223214617 20100222214617 8496 isc.org. "
+ "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz "
+ "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/ "
+ "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU "
+ "f49t+sXKPzbipN9g+s1ZPiIyofc=");
+
+ // Multi-line signature data is OK, if enclosed in parentheses
+ checkFromText_None(
+ "A 5 4 43200 20100223214617 20100222214617 8496 isc.org. "
+ "( evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz\n"
+ "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/\n"
+ "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU\n"
+ "f49t+sXKPzbipN9g+s1ZPiIyofc= )");
+
+ // Trailing garbage. This should cause only the string constructor
+ // to fail, but the lexer constructor must be able to continue
+ // parsing from it.
+ checkFromText_BadString(
+ "A 5 4 43200 20100223214617 20100222214617 8496 isc.org. "
+ "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
+ "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
+ "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
+ "f49t+sXKPzbipN9g+s1ZPiIyofc= ; comment\n"
+ "A 5 4 43200 20100223214617 20100222214617 8496 isc.org. "
+ "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
+ "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
+ "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
+ "f49t+sXKPzbipN9g+s1ZPiIyofc=");
+}
+
+TEST_F(Rdata_RRSIG_Test, badText_missingFields) {
+ checkFromText_LexerError("A");
+ checkFromText_LexerError("A 5");
+ checkFromText_LexerError("A 5 4");
+ checkFromText_LexerError("A 5 4 43200");
+ checkFromText_LexerError("A 5 4 43200 20100223214617");
+ checkFromText_LexerError("A 5 4 43200 20100223214617 20100222214617");
+ checkFromText_LexerError("A 5 4 43200 20100223214617 20100222214617 "
+ "8496");
+}
+
+TEST_F(Rdata_RRSIG_Test, badText_coveredType) {
+ checkFromText_InvalidType("SPORK");
+}
+
+TEST_F(Rdata_RRSIG_Test, badText_algorithm) {
+ checkFromText_InvalidText(
+ "A 555 4 43200 "
+ "20100223214617 20100222214617 8496 isc.org. "
+ "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
+ "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
+ "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
+ "f49t+sXKPzbipN9g+s1ZPiIyofc=");
+ checkFromText_LexerError(
+ "A FIVE 4 43200 "
+ "20100223214617 20100222214617 8496 isc.org. "
+ "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
+ "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
+ "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
+ "f49t+sXKPzbipN9g+s1ZPiIyofc=");
}
-TEST_F(Rdata_RRSIG_Test, badText) {
- EXPECT_THROW(const generic::RRSIG sig("SPORK"), InvalidRdataText);
- EXPECT_THROW(const generic::RRSIG sig("A 555 4 43200 "
+TEST_F(Rdata_RRSIG_Test, badText_labels) {
+ checkFromText_InvalidText(
+ "A 5 4444 43200 "
"20100223214617 20100222214617 8496 isc.org. "
"evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
"diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
"NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
- "f49t+sXKPzbipN9g+s1ZPiIyofc="), InvalidRdataText);
- EXPECT_THROW(const generic::RRSIG sig("A 5 4444 43200 "
+ "f49t+sXKPzbipN9g+s1ZPiIyofc=");
+ checkFromText_LexerError(
+ "A 5 FOUR 43200 "
"20100223214617 20100222214617 8496 isc.org. "
"evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
"diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
"NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
- "f49t+sXKPzbipN9g+s1ZPiIyofc="), InvalidRdataText);
- EXPECT_THROW(const generic::RRSIG sig("A 5 4 999999999999 "
+ "f49t+sXKPzbipN9g+s1ZPiIyofc=");
+}
+
+TEST_F(Rdata_RRSIG_Test, badText_ttl) {
+ checkFromText_LexerError(
+ "A 5 4 999999999999 "
"20100223214617 20100222214617 8496 isc.org. "
"evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
"diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
"NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
- "f49t+sXKPzbipN9g+s1ZPiIyofc="), InvalidRdataText);
- EXPECT_THROW(const generic::RRSIG sig("A 5 4 43200 "
- "20100223 20100227 8496 isc.org. "
+ "f49t+sXKPzbipN9g+s1ZPiIyofc=");
+ checkFromText_LexerError(
+ "A 5 4 TTL "
+ "20100223214617 20100222214617 8496 isc.org. "
"evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
"diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
"NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
- "f49t+sXKPzbipN9g+s1ZPiIyofc="), InvalidTime);
- EXPECT_THROW(const generic::RRSIG sig("A 5 4 43200 "
+ "f49t+sXKPzbipN9g+s1ZPiIyofc=");
+
+ // alternate form of TTL is not okay
+ checkFromText_LexerError(
+ "A 5 4 12H 20100223214617 20100222214617 8496 isc.org. "
+ "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz "
+ "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/ "
+ "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU "
+ "f49t+sXKPzbipN9g+s1ZPiIyofc=");
+}
+
+TEST_F(Rdata_RRSIG_Test, badText_expiration) {
+ checkFromText_InvalidTime(
+ "A 5 4 43200 "
+ "201002232 20100222214617 8496 isc.org. "
+ "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
+ "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
+ "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
+ "f49t+sXKPzbipN9g+s1ZPiIyofc=");
+ checkFromText_InvalidTime(
+ "A 5 4 43200 "
+ "EXPIRATION 20100222214617 8496 isc.org. "
+ "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
+ "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
+ "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
+ "f49t+sXKPzbipN9g+s1ZPiIyofc=");
+}
+
+TEST_F(Rdata_RRSIG_Test, badText_inception) {
+ checkFromText_InvalidTime(
+ "A 5 4 43200 "
+ "20100223214617 20100227 8496 isc.org. "
+ "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
+ "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
+ "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
+ "f49t+sXKPzbipN9g+s1ZPiIyofc=");
+ checkFromText_InvalidTime(
+ "A 5 4 43200 "
+ "20100223214617 INCEPTION 8496 isc.org. "
+ "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
+ "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
+ "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
+ "f49t+sXKPzbipN9g+s1ZPiIyofc=");
+}
+
+TEST_F(Rdata_RRSIG_Test, badText_keytag) {
+ checkFromText_InvalidText(
+ "A 5 4 43200 "
"20100223214617 20100222214617 999999 isc.org. "
"evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
"diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
"NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
- "f49t+sXKPzbipN9g+s1ZPiIyofc="), InvalidRdataText);
- EXPECT_THROW(const generic::RRSIG sig(
+ "f49t+sXKPzbipN9g+s1ZPiIyofc=");
+ checkFromText_LexerError(
"A 5 4 43200 "
- "20100223214617 20100222214617 8496 isc.org. "
- "EEeeeeeeEEEeeeeeeGaaahAAAAAAAAHHHHHHHHHHH!="),
- BadValue); // bad base64 input
+ "20100223214617 20100222214617 TAG isc.org. "
+ "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
+ "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
+ "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
+ "f49t+sXKPzbipN9g+s1ZPiIyofc=");
+}
+
+TEST_F(Rdata_RRSIG_Test, badText_signer) {
+ checkFromText_MissingOrigin(
+ "A 5 4 43200 "
+ "20100223214617 20100222214617 8496 isc.org "
+ "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
+ "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
+ "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
+ "f49t+sXKPzbipN9g+s1ZPiIyofc=");
}
-TEST_F(Rdata_RRSIG_Test, DISABLED_badText) {
- // this currently fails
+TEST_F(Rdata_RRSIG_Test, badText_signature) {
+ checkFromText_BadValue(
+ "A 5 4 43200 "
+ "20100223214617 20100222214617 8496 isc.org. "
+ "EEeeeeeeEEEeeeeeeGaaahAAAAAAAAHHHHHHHHHHH!=");
+
// no space between the tag and signer
- EXPECT_THROW(generic::RRSIG("A 5 4 43200 20100223214617 20100222214617 "
- "8496isc.org. ofc="), InvalidRdataText);
+ checkFromText_LexerError(
+ "A 5 4 43200 20100223214617 20100222214617 "
+ "8496isc.org. ofc=");
+
+ // unterminated multi-line base64
+ checkFromText_LexerError(
+ "A 5 4 43200 20100223214617 20100222214617 8496 isc.org. "
+ "( evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz\n"
+ "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/\n"
+ "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU\n"
+ "f49t+sXKPzbipN9g+s1ZPiIyofc=");
}
TEST_F(Rdata_RRSIG_Test, createFromLexer) {
diff --git a/src/lib/dns/tests/rrcollator_unittest.cc b/src/lib/dns/tests/rrcollator_unittest.cc
index e66f87c..9123dc5 100644
--- a/src/lib/dns/tests/rrcollator_unittest.cc
+++ b/src/lib/dns/tests/rrcollator_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -59,10 +59,10 @@ protected:
txt_rdata_(createRdata(RRType::TXT(), rrclass_, "test")),
sig_rdata1_(createRdata(RRType::RRSIG(), rrclass_,
"A 5 3 3600 20000101000000 20000201000000 "
- "12345 example.com. FAKE\n")),
+ "12345 example.com. FAKE")),
sig_rdata2_(createRdata(RRType::RRSIG(), rrclass_,
"NS 5 3 3600 20000101000000 20000201000000 "
- "12345 example.com. FAKE\n"))
+ "12345 example.com. FAKE"))
{}
void checkRRset(const Name& expected_name, const RRClass& expected_class,
diff --git a/src/lib/dns/tests/rrset_unittest.cc b/src/lib/dns/tests/rrset_unittest.cc
index d16ce3c..a605caf 100644
--- a/src/lib/dns/tests/rrset_unittest.cc
+++ b/src/lib/dns/tests/rrset_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2010-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
@@ -289,7 +289,7 @@ protected:
rrset_rrsig = RRsetPtr(new RRset(test_name, RRClass::IN(),
RRType::RRSIG(), RRTTL(3600)));
rrset_rrsig->addRdata(generic::RRSIG("AAAA 5 3 7200 20100322084538 "
- "20100220084538 1 example.com "
+ "20100220084538 1 example.com. "
"FAKEFAKEFAKEFAKE"));
rrset_aaaa->addRRsig(rrset_rrsig);
}
diff --git a/src/lib/dns/tests/testdata/.gitignore b/src/lib/dns/tests/testdata/.gitignore
index e8879e1..a0a14f4 100644
--- a/src/lib/dns/tests/testdata/.gitignore
+++ b/src/lib/dns/tests/testdata/.gitignore
@@ -41,6 +41,8 @@
/rdata_minfo_toWire2.wire
/rdata_minfo_toWireUncompressed1.wire
/rdata_minfo_toWireUncompressed2.wire
+/rdata_dnskey_fromWire.wire
+/rdata_dnskey_empty_keydata_fromWire.wire
/rdata_nsec3_fromWire10.wire
/rdata_nsec3_fromWire11.wire
/rdata_nsec3_fromWire12.wire
diff --git a/src/lib/dns/tests/testdata/Makefile.am b/src/lib/dns/tests/testdata/Makefile.am
index 52acb7c..b6d7e35 100644
--- a/src/lib/dns/tests/testdata/Makefile.am
+++ b/src/lib/dns/tests/testdata/Makefile.am
@@ -16,6 +16,7 @@ BUILT_SOURCES += message_toText3.wire
BUILT_SOURCES += name_toWire5.wire name_toWire6.wire
BUILT_SOURCES += rdatafields1.wire rdatafields2.wire rdatafields3.wire
BUILT_SOURCES += rdatafields4.wire rdatafields5.wire rdatafields6.wire
+BUILT_SOURCES += rdata_dnskey_fromWire.wire rdata_dnskey_empty_keydata_fromWire.wire
BUILT_SOURCES += rdata_nsec_fromWire4.wire rdata_nsec_fromWire5.wire
BUILT_SOURCES += rdata_nsec_fromWire6.wire rdata_nsec_fromWire7.wire
BUILT_SOURCES += rdata_nsec_fromWire8.wire rdata_nsec_fromWire9.wire
@@ -101,7 +102,8 @@ EXTRA_DIST += name_toWire7 name_toWire8 name_toWire9
EXTRA_DIST += question_fromWire question_toWire1 question_toWire2
EXTRA_DIST += rdatafields1.spec rdatafields2.spec rdatafields3.spec
EXTRA_DIST += rdatafields4.spec rdatafields5.spec rdatafields6.spec
-EXTRA_DIST += rdata_cname_fromWire rdata_dname_fromWire rdata_dnskey_fromWire
+EXTRA_DIST += rdata_cname_fromWire rdata_dname_fromWire
+EXTRA_DIST += rdata_dnskey_fromWire.spec rdata_dnskey_empty_keydata_fromWire.spec
EXTRA_DIST += rdata_dhcid_fromWire rdata_dhcid_toWire
EXTRA_DIST += rdata_ds_fromWire rdata_in_a_fromWire rdata_in_aaaa_fromWire
EXTRA_DIST += rdata_mx_fromWire rdata_mx_toWire1 rdata_mx_toWire2
diff --git a/src/lib/dns/tests/testdata/example.org b/src/lib/dns/tests/testdata/example.org
index 4163fc0..2708ef4 100644
--- a/src/lib/dns/tests/testdata/example.org
+++ b/src/lib/dns/tests/testdata/example.org
@@ -13,5 +13,5 @@ example.org. 3600 IN SOA ( ; The SOA, split across lines for testing
; Some empty lines here. They are to make sure the loader can skip them.
www 3600 IN A 192.0.2.1 ; Test a relative name as well.
- 3600 IN AAAA 2001:db8::1 ; And initial whitespace hanling
+ 3600 IN AAAA 2001:db8::1 ; And initial whitespace handling
; Here be just some space, no RRs
diff --git a/src/lib/dns/tests/testdata/rdata_dnskey_empty_keydata_fromWire.spec b/src/lib/dns/tests/testdata/rdata_dnskey_empty_keydata_fromWire.spec
new file mode 100644
index 0000000..b65271d
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_dnskey_empty_keydata_fromWire.spec
@@ -0,0 +1,7 @@
+# DNSKEY test data with empty digest
+
+[custom]
+sections: dnskey
+
+[dnskey]
+digest:
diff --git a/src/lib/dns/tests/testdata/rdata_dnskey_fromWire b/src/lib/dns/tests/testdata/rdata_dnskey_fromWire
deleted file mode 100644
index b703da1..0000000
--- a/src/lib/dns/tests/testdata/rdata_dnskey_fromWire
+++ /dev/null
@@ -1,24 +0,0 @@
-# RDLENGTH = 265 bytes
- 01 09
-# DNSKEY, flags 257
- 01 01
-# protocol 3, algorithm 5
- 03 05
-# keydata:
- 04 40 00 00 03 a1 1d 00 c1 ae 14 1b b6 98 60 ab
- 6c 10 52 91 10 e6 de 03 b5 41 f1 a0 c5 45 bb 68
- 56 2c 33 2f a0 e3 11 5e 31 ab 86 10 9e 16 f0 19
- 8a 1e f2 24 77 fc 64 67 d6 ea 17 77 f2 15 c6 ff
- 1c a5 60 23 ba 2a ba 5b 76 88 f0 c7 c6 0c 5c b0
- 39 fe 40 3e bb 9d 16 20 bf 19 47 54 7a 29 36 ec
- 61 53 1f fd 0c 79 46 23 5b 3c 29 70 fa f4 fe 53
- c7 97 10 99 8e db 48 c8 4b 55 0b 82 ac b7 e3 b7
- 01 07 5c cc 9e 7c ff e0 b2 69 03 47 5a f4 26 ca
- 8f 70 36 e7 84 f9 d7 9b 0d 20 c7 30 b0 1f 3f db
- ed 84 eb 7f f3 66 b4 33 06 48 f4 06 b3 7f f4 17
- b1 8e 98 a4 b3 78 d1 85 96 ad 12 c5 e7 dd d4 f2
- e3 b4 74 f5 48 b1 e5 67 09 b7 ec 73 a9 9e fe ca
- cc 8b 28 e3 9e 75 2d fd 67 b4 83 9a c9 f6 78 0d
- 05 2a d4 29 c0 0e 8b 5d e1 b6 c3 e8 f1 9b 0d e8
- 03 c9 55 52 01 1f fe bc de 0b f6 c1 c8 13 6c 3b
- bd 1a 10 54 dd
diff --git a/src/lib/dns/tests/testdata/rdata_dnskey_fromWire.spec b/src/lib/dns/tests/testdata/rdata_dnskey_fromWire.spec
new file mode 100644
index 0000000..87e66db
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_dnskey_fromWire.spec
@@ -0,0 +1,7 @@
+# DNSKEY test data
+
+[custom]
+sections: dnskey
+
+[dnskey]
+digest: BEAAAAOhHQDBrhQbtphgq2wQUpEQ5t4DtUHxoMVFu2hWLDMvoOMRXjGrhhCeFvAZih7yJHf8ZGfW6hd38hXG/xylYCO6Krpbdojwx8YMXLA5/kA+u50WIL8ZR1R6KTbsYVMf/Qx5RiNbPClw+vT+U8eXEJmO20jIS1ULgqy347cBB1zMnnz/4LJpA0da9CbKj3A254T515sNIMcwsB8/2+2E63/zZrQzBkj0BrN/9Bexjpiks3jRhZatEsXn3dTy47R09Uix5WcJt+xzqZ7+ysyLKOOedS39Z7SDmsn2eA0FKtQpwA6LXeG2w+jxmw3oA8lVUgEf/rzeC/bByBNsO70aEFTd
diff --git a/src/lib/dns/tsigrecord.cc b/src/lib/dns/tsigrecord.cc
index 9dd3f78..ba30c0a 100644
--- a/src/lib/dns/tsigrecord.cc
+++ b/src/lib/dns/tsigrecord.cc
@@ -59,13 +59,14 @@ namespace {
// of the constructor below.
const any::TSIG&
castToTSIGRdata(const rdata::Rdata& rdata) {
- try {
- return (dynamic_cast<const any::TSIG&>(rdata));
- } catch (std::bad_cast&) {
+ const any::TSIG* tsig_rdata =
+ dynamic_cast<const any::TSIG*>(&rdata);
+ if (!tsig_rdata) {
isc_throw(DNSMessageFORMERR,
"TSIG record is being constructed from "
"incompatible RDATA:" << rdata.toText());
}
+ return (*tsig_rdata);
}
}
diff --git a/src/lib/log/Makefile.am b/src/lib/log/Makefile.am
index 56918e9..18d5f90 100644
--- a/src/lib/log/Makefile.am
+++ b/src/lib/log/Makefile.am
@@ -48,5 +48,7 @@ if USE_CLANGPP
libb10_log_la_CXXFLAGS += -Wno-error
endif
libb10_log_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
-libb10_log_la_LIBADD = $(top_builddir)/src/lib/util/libb10-util.la $(LOG4CPLUS_LIBS)
+libb10_log_la_LIBADD = $(top_builddir)/src/lib/util/libb10-util.la
+libb10_log_la_LIBADD += $(top_builddir)/src/lib/util/threads/libb10-threads.la
+libb10_log_la_LIBADD += $(LOG4CPLUS_LIBS)
libb10_log_la_LDFLAGS = -no-undefined -version-info 1:0:0
diff --git a/src/lib/log/log_dbglevels.h b/src/lib/log/log_dbglevels.h
index a459bed..ccf89b9 100644
--- a/src/lib/log/log_dbglevels.h
+++ b/src/lib/log/log_dbglevels.h
@@ -42,7 +42,7 @@
/// libraries.
///
/// This file defines a set of standard debug levels for use across all loggers.
-/// In this way users can have some expection of what will be output when
+/// In this way users can have some expectation of what will be output when
/// enabling debugging. Symbols are prefixed DBGLVL so as not to clash with
/// DBG_ symbols in the various modules.
///
diff --git a/src/lib/log/logger_impl.cc b/src/lib/log/logger_impl.cc
index 689795d..9363738 100644
--- a/src/lib/log/logger_impl.cc
+++ b/src/lib/log/logger_impl.cc
@@ -29,6 +29,7 @@
#include <log/logger_level.h>
#include <log/logger_level_impl.h>
#include <log/logger_name.h>
+#include <log/logger_manager.h>
#include <log/message_dictionary.h>
#include <log/message_types.h>
@@ -123,9 +124,12 @@ LoggerImpl::setInterprocessSync(isc::util::InterprocessSync* sync) {
void
LoggerImpl::outputRaw(const Severity& severity, const string& message) {
+ // Use a mutex locker for mutual exclusion from other threads in
+ // this process.
+ isc::util::thread::Mutex::Locker mutex_locker(LoggerManager::getMutex());
+
// Use an interprocess sync locker for mutual exclusion from other
// processes to avoid log messages getting interspersed.
-
InterprocessSyncLocker locker(*sync_);
if (!locker.lock()) {
diff --git a/src/lib/log/logger_manager.cc b/src/lib/log/logger_manager.cc
index 77893d0..0857441 100644
--- a/src/lib/log/logger_manager.cc
+++ b/src/lib/log/logger_manager.cc
@@ -121,25 +121,26 @@ LoggerManager::init(const std::string& root, isc::log::Severity severity,
// Check if there were any duplicate message IDs in the default dictionary
// and if so, log them. Log using the logging facility logger.
- vector<string>& duplicates = MessageInitializer::getDuplicates();
+ const vector<string>& duplicates = MessageInitializer::getDuplicates();
if (!duplicates.empty()) {
- // There are duplicates present. This will be listed in alphabetic
- // order of message ID, so they need to be sorted. This list itself may
- // contain duplicates; if so, the message ID is listed as many times as
+ // There are duplicates present. This list itself may contain
+ // duplicates; if so, the message ID is listed as many times as
// there are duplicates.
- sort(duplicates.begin(), duplicates.end());
- for (vector<string>::iterator i = duplicates.begin();
+ for (vector<string>::const_iterator i = duplicates.begin();
i != duplicates.end(); ++i) {
LOG_WARN(logger, LOG_DUPLICATE_MESSAGE_ID).arg(*i);
}
-
+ MessageInitializer::clearDuplicates();
}
// Replace any messages with local ones (if given)
if (file) {
readLocalMessageFile(file);
}
+
+ // Ensure that the mutex is constructed and ready at this point.
+ (void) getMutex();
}
@@ -194,5 +195,12 @@ LoggerManager::reset() {
LoggerManagerImpl::reset(initSeverity(), initDebugLevel());
}
+isc::util::thread::Mutex&
+LoggerManager::getMutex() {
+ static isc::util::thread::Mutex mutex;
+
+ return (mutex);
+}
+
} // namespace log
} // namespace isc
diff --git a/src/lib/log/logger_manager.h b/src/lib/log/logger_manager.h
index da49ae4..b09383e 100644
--- a/src/lib/log/logger_manager.h
+++ b/src/lib/log/logger_manager.h
@@ -16,6 +16,7 @@
#define LOGGER_MANAGER_H
#include "exceptions/exceptions.h"
+#include <util/threads/sync.h>
#include <log/logger_specification.h>
// Generated if, when updating the logging specification, an unknown
@@ -100,6 +101,9 @@ public:
/// an attempt is made to log a message before this is function is called,
/// the results will be dependent on the underlying logging package.
///
+ /// Any duplicate log IDs encountered are reported as warning, after which
+ /// the global duplicates vector is cleared
+ ///
/// \param root Name of the root logger. This should be set to the name of
/// the program.
/// \param severity Severity at which to log
@@ -129,6 +133,11 @@ public:
/// \param file Name of the local message file
static void readLocalMessageFile(const char* file);
+ /// \brief Return a process-global mutex that's used for mutual
+ /// exclusion among threads of a single process during logging
+ /// calls.
+ static isc::util::thread::Mutex& getMutex();
+
private:
/// \brief Initialize Processing
///
diff --git a/src/lib/log/logger_manager_impl.h b/src/lib/log/logger_manager_impl.h
index bcb2fc7..7a49820 100644
--- a/src/lib/log/logger_manager_impl.h
+++ b/src/lib/log/logger_manager_impl.h
@@ -55,7 +55,7 @@ public:
/// \brief Initialize Processing
///
- /// This resets the hierachy of loggers back to their defaults. This means
+ /// This resets the hierarchy of loggers back to their defaults. This means
/// that all non-root loggers (if they exist) are set to NOT_SET, and the
/// root logger reset to logging informational messages.
void processInit();
diff --git a/src/lib/log/logger_unittest_support.cc b/src/lib/log/logger_unittest_support.cc
index 4f02b07..9dc90fd 100644
--- a/src/lib/log/logger_unittest_support.cc
+++ b/src/lib/log/logger_unittest_support.cc
@@ -43,7 +43,7 @@ b10LoggerSeverity(isc::log::Severity defseverity) {
return (defseverity);
}
-// Get the debug level. This is defined by the envornment variable
+// Get the debug level. This is defined by the environment variable
// B10_LOGGER_DBGLEVEL. If not defined, a default value passed to the function
// is returned.
int
diff --git a/src/lib/log/message_initializer.cc b/src/lib/log/message_initializer.cc
index 3dd5da7..3277047 100644
--- a/src/lib/log/message_initializer.cc
+++ b/src/lib/log/message_initializer.cc
@@ -42,7 +42,13 @@ size_t& getIndex() {
return (index);
}
+// Return the duplicates singleton version (non-const for local use)
+std::vector<std::string>&
+getNonConstDuplicates() {
+ static std::vector<std::string> duplicates;
+ return (duplicates);
}
+} // end unnamed namespace
namespace isc {
@@ -67,7 +73,7 @@ MessageInitializer::getPendingCount() {
// into the global dictionary.
void
-MessageInitializer::loadDictionary() {
+MessageInitializer::loadDictionary(bool ignore_duplicates) {
MessageDictionary& global = MessageDictionary::globalDictionary();
for (size_t i = 0; i < getIndex(); ++i) {
@@ -75,8 +81,8 @@ MessageInitializer::loadDictionary() {
// Append the IDs in the list just loaded (the "repeats") to the
// global list of duplicate IDs.
- if (!repeats.empty()) {
- std::vector<std::string>& duplicates = getDuplicates();
+ if (!ignore_duplicates && !repeats.empty()) {
+ std::vector<std::string>& duplicates = getNonConstDuplicates();
duplicates.insert(duplicates.end(), repeats.begin(),
repeats.end());
}
@@ -88,11 +94,16 @@ MessageInitializer::loadDictionary() {
getIndex() = 0;
}
-// Return reference to duplicate array
+// Return reference to duplicates vector
+const std::vector<std::string>&
+MessageInitializer::getDuplicates() {
+ return (getNonConstDuplicates());
+}
-std::vector<std::string>& MessageInitializer::getDuplicates() {
- static std::vector<std::string> duplicates;
- return (duplicates);
+// Clear the duplicates vector
+void
+MessageInitializer::clearDuplicates() {
+ getNonConstDuplicates().clear();
}
} // namespace log
diff --git a/src/lib/log/message_initializer.h b/src/lib/log/message_initializer.h
index 3be973d..ae67484 100644
--- a/src/lib/log/message_initializer.h
+++ b/src/lib/log/message_initializer.h
@@ -43,6 +43,9 @@ namespace log {
/// All that needed is for the module containing the definitions to be
/// included in the execution unit.
///
+/// Dynamically loaded modules should call the initializer as well on the
+/// moment they are instantiated.
+///
/// To avoid static initialization fiasco problems, the initialization is
/// carried out in two stages:
/// - The constructor adds a pointer to the values array to a pre-defined array
@@ -93,7 +96,11 @@ public:
/// Loops through the internal array of pointers to message arrays
/// and adds the messages to the internal dictionary. This is called
/// during run-time initialization.
- static void loadDictionary();
+ ///
+ /// \param ignore_duplicates If true, duplicate IDs, and IDs already
+ /// loaded, are ignored instead of stored in the global duplicates
+ /// vector.
+ static void loadDictionary(bool ignore_duplicates = false);
/// \brief Return Duplicates
///
@@ -102,7 +109,12 @@ public:
///
/// \return List of duplicate message IDs when the global dictionary was
/// loaded. Note that the duplicates list itself may contain duplicates.
- static std::vector<std::string>& getDuplicates();
+ static const std::vector<std::string>& getDuplicates();
+
+ /// \brief Clear the static duplicates vector
+ ///
+ /// Empties the vector returned by getDuplicates()
+ static void clearDuplicates();
};
} // namespace log
diff --git a/src/lib/log/tests/Makefile.am b/src/lib/log/tests/Makefile.am
index 5683842..306d5f9 100644
--- a/src/lib/log/tests/Makefile.am
+++ b/src/lib/log/tests/Makefile.am
@@ -25,6 +25,7 @@ logger_example_CPPFLAGS = $(AM_CPPFLAGS)
logger_example_LDFLAGS = $(AM_LDFLAGS)
logger_example_LDADD = $(top_builddir)/src/lib/log/libb10-log.la
logger_example_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
+logger_example_LDADD += $(top_builddir)/src/lib/util/threads/libb10-threads.la
logger_example_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
logger_example_LDADD += $(AM_LDADD) $(LOG4CPLUS_LIBS)
@@ -34,6 +35,7 @@ init_logger_test_CPPFLAGS = $(AM_CPPFLAGS)
init_logger_test_LDFLAGS = $(AM_LDFLAGS)
init_logger_test_LDADD = $(top_builddir)/src/lib/log/libb10-log.la
init_logger_test_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
+init_logger_test_LDADD += $(top_builddir)/src/lib/util/threads/libb10-threads.la
init_logger_test_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
init_logger_test_LDADD += $(AM_LDADD) $(LOG4CPLUS_LIBS)
@@ -43,6 +45,7 @@ buffer_logger_test_CPPFLAGS = $(AM_CPPFLAGS)
buffer_logger_test_LDFLAGS = $(AM_LDFLAGS)
buffer_logger_test_LDADD = $(top_builddir)/src/lib/log/libb10-log.la
buffer_logger_test_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
+buffer_logger_test_LDADD += $(top_builddir)/src/lib/util/threads/libb10-threads.la
buffer_logger_test_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
buffer_logger_test_LDADD += $(AM_LDADD) $(LOG4CPLUS_LIBS)
@@ -53,6 +56,7 @@ logger_lock_test_CPPFLAGS = $(AM_CPPFLAGS)
logger_lock_test_LDFLAGS = $(AM_LDFLAGS)
logger_lock_test_LDADD = $(top_builddir)/src/lib/log/libb10-log.la
logger_lock_test_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
+logger_lock_test_LDADD += $(top_builddir)/src/lib/util/threads/libb10-threads.la
logger_lock_test_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
logger_lock_test_LDADD += $(AM_LDADD) $(LOG4CPLUS_LIBS)
@@ -71,6 +75,7 @@ AM_CPPFLAGS += $(GTEST_INCLUDES) $(LOG4CPLUS_INCLUDES)
AM_LDFLAGS += $(GTEST_LDFLAGS)
AM_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
+AM_LDADD += $(top_builddir)/src/lib/util/threads/libb10-threads.la
AM_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
AM_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
AM_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
@@ -126,11 +131,17 @@ initializer_unittests_2_LDFLAGS = $(AM_LDFLAGS)
noinst_PROGRAMS += $(TESTS)
endif
+noinst_SCRIPTS = console_test.sh
+noinst_SCRIPTS += destination_test.sh
+noinst_SCRIPTS += init_logger_test.sh
+noinst_SCRIPTS += local_file_test.sh
+noinst_SCRIPTS += logger_lock_test.sh
+noinst_SCRIPTS += severity_test.sh
+
# Additional test using the shell. These are principally tests
# where the global logging environment is affected, and where the
# output needs to be compared with stored output (where "cut" and
# "diff" are useful utilities).
-
check-local:
$(SHELL) $(abs_builddir)/console_test.sh
$(SHELL) $(abs_builddir)/destination_test.sh
@@ -139,11 +150,3 @@ check-local:
$(SHELL) $(abs_builddir)/local_file_test.sh
$(SHELL) $(abs_builddir)/logger_lock_test.sh
$(SHELL) $(abs_builddir)/severity_test.sh
-
-noinst_SCRIPTS = console_test.sh
-noinst_SCRIPTS += destination_test.sh
-noinst_SCRIPTS += init_logger_test.sh
-noinst_SCRIPTS += buffer_logger_test.sh
-noinst_SCRIPTS += local_file_test.sh
-noinst_SCRIPTS += logger_lock_test.sh
-noinst_SCRIPTS += severity_test.sh
diff --git a/src/lib/log/tests/logger_lock_test.cc b/src/lib/log/tests/logger_lock_test.cc
index d63989c..7fed5c7 100644
--- a/src/lib/log/tests/logger_lock_test.cc
+++ b/src/lib/log/tests/logger_lock_test.cc
@@ -14,6 +14,7 @@
#include <log/macros.h>
#include <log/logger_support.h>
+#include <log/logger_manager.h>
#include <log/log_messages.h>
#include "util/interprocess_sync.h"
#include "log_test_messages.h"
@@ -21,6 +22,7 @@
using namespace std;
using namespace isc::log;
+using isc::util::thread::Mutex;
class MockLoggingSync : public isc::util::InterprocessSync {
public:
@@ -31,6 +33,15 @@ public:
protected:
virtual bool lock() {
+ // We first check if the logger acquired a lock on the
+ // LoggerManager mutex.
+ try {
+ // This lock attempt is non-blocking.
+ Mutex::Locker locker(LoggerManager::getMutex(), false);
+ } catch (Mutex::Locker::AlreadyLocked& e) {
+ cout << "FIELD1 FIELD2 LOGGER_LOCK_TEST: MUTEXLOCK\n";
+ }
+
cout << "FIELD1 FIELD2 LOGGER_LOCK_TEST: LOCK\n";
return (true);
}
diff --git a/src/lib/log/tests/logger_lock_test.sh.in b/src/lib/log/tests/logger_lock_test.sh.in
index c664c91..147998c 100755
--- a/src/lib/log/tests/logger_lock_test.sh.in
+++ b/src/lib/log/tests/logger_lock_test.sh.in
@@ -31,6 +31,7 @@ passfail() {
echo -n "Testing that logger acquires and releases locks correctly:"
cat > $tempfile << .
+LOGGER_LOCK_TEST: MUTEXLOCK
LOGGER_LOCK_TEST: LOCK
INFO [bind10.log] LOG_LOCK_TEST_MESSAGE this is a test message.
LOGGER_LOCK_TEST: UNLOCK
diff --git a/src/lib/log/tests/message_dictionary_unittest.cc b/src/lib/log/tests/message_dictionary_unittest.cc
index b8bded3..065641a 100644
--- a/src/lib/log/tests/message_dictionary_unittest.cc
+++ b/src/lib/log/tests/message_dictionary_unittest.cc
@@ -189,9 +189,8 @@ TEST_F(MessageDictionaryTest, GlobalTest) {
// new symbol.
TEST_F(MessageDictionaryTest, GlobalLoadTest) {
- vector<string>& duplicates = MessageInitializer::getDuplicates();
- ASSERT_EQ(1, duplicates.size());
- EXPECT_EQ(string("LOG_DUPLICATE_NAMESPACE"), duplicates[0]);
+ // There were duplicates but the vector should be cleared in init() now
+ ASSERT_EQ(0, MessageInitializer::getDuplicates().size());
string text = MessageDictionary::globalDictionary().getText("NEWSYM");
EXPECT_EQ(string("new symbol added"), text);
diff --git a/src/lib/log/tests/message_initializer_1_unittest.cc b/src/lib/log/tests/message_initializer_1_unittest.cc
index 994174c..761231d 100644
--- a/src/lib/log/tests/message_initializer_1_unittest.cc
+++ b/src/lib/log/tests/message_initializer_1_unittest.cc
@@ -77,3 +77,37 @@ TEST(MessageInitializerTest1, MessageTest) {
EXPECT_EQ(string("global message five"), global.getText("GLOBAL5"));
EXPECT_EQ(string("global message six"), global.getText("GLOBAL6"));
}
+
+TEST(MessageInitializerTest1, Duplicates) {
+ // Original set should not have dupes
+ ASSERT_EQ(0, MessageInitializer::getDuplicates().size());
+
+ // This just defines 1, but we'll add it a number of times
+ const char* dupe[] = {
+ "DUPE", "dupe",
+ NULL
+ };
+ const MessageInitializer init_message_initializer_unittest_1(dupe);
+ const MessageInitializer init_message_initializer_unittest_2(dupe);
+
+ MessageInitializer::loadDictionary();
+ // Should be a dupe now
+ ASSERT_EQ(1, MessageInitializer::getDuplicates().size());
+
+ // clear them
+ MessageInitializer::clearDuplicates();
+ ASSERT_EQ(0, MessageInitializer::getDuplicates().size());
+
+ // Do it again to make sure, let's explicitly provide false now
+ const MessageInitializer init_message_initializer_unittest_3(dupe);
+ MessageInitializer::loadDictionary(false);
+ ASSERT_EQ(1, MessageInitializer::getDuplicates().size());
+
+ // Loading with ignore_duplicates=true should result in no (reported)
+ // dupes
+ MessageInitializer::clearDuplicates();
+ ASSERT_EQ(0, MessageInitializer::getDuplicates().size());
+ const MessageInitializer init_message_initializer_unittest_4(dupe);
+ MessageInitializer::loadDictionary(true);
+ ASSERT_EQ(0, MessageInitializer::getDuplicates().size());
+}
diff --git a/src/lib/nsas/nameserver_entry.cc b/src/lib/nsas/nameserver_entry.cc
index bca8f73..8bed10d 100644
--- a/src/lib/nsas/nameserver_entry.cc
+++ b/src/lib/nsas/nameserver_entry.cc
@@ -91,7 +91,7 @@ NameserverEntry::getAddresses(AddressVector& addresses,
if (!has_address_[family] && expect_address_[family]) {
return IN_PROGRESS;
}
- // If we do not expect the address, then fall trough to READY
+ // If we do not expect the address, then fall through to READY
case EXPIRED: // If expired_ok, we pretend to be ready
case READY:
if (!has_address_[family]) {
@@ -149,7 +149,7 @@ NameserverEntry::setAddressRTT(const IOAddress& address, uint32_t rtt) {
}
}
- // Hack. C++ does not allow ++ on enums, enumerating trough them is pain
+ // Hack. C++ does not allow ++ on enums, enumerating through them is pain
switch (family) {
case V4_ONLY: family = V6_ONLY; break;
default: return;
diff --git a/src/lib/nsas/tests/nameserver_entry_unittest.cc b/src/lib/nsas/tests/nameserver_entry_unittest.cc
index 3aca08f..e0ec0ad 100644
--- a/src/lib/nsas/tests/nameserver_entry_unittest.cc
+++ b/src/lib/nsas/tests/nameserver_entry_unittest.cc
@@ -59,10 +59,10 @@ protected:
};
private:
/**
- * \short Fills an rrset into the NameserverEntry trough resolver.
+ * \short Fills an rrset into the NameserverEntry through resolver.
*
* This function is used when we want to pass data to a NameserverEntry
- * trough the resolver.
+ * through the resolver.
* \param resolver The resolver used by the NameserverEntry
* \param index Index of the query in the resolver.
* \param set The answer. If the pointer is empty, it is taken
@@ -79,7 +79,7 @@ private:
}
}
protected:
- /// Fills the nameserver entry with data trough ask IP
+ /// Fills the nameserver entry with data through ask IP
void fillNSEntry(boost::shared_ptr<NameserverEntry> entry,
RRsetPtr rrv4, RRsetPtr rrv6)
{
diff --git a/src/lib/nsas/tests/zone_entry_unittest.cc b/src/lib/nsas/tests/zone_entry_unittest.cc
index 3de965e..8754906 100644
--- a/src/lib/nsas/tests/zone_entry_unittest.cc
+++ b/src/lib/nsas/tests/zone_entry_unittest.cc
@@ -101,7 +101,7 @@ protected:
/**
* \short Function returning a new zone.
*
- * Convenience funcion, just creating a new zone, to shorten the code.
+ * Convenience function, just creating a new zone, to shorten the code.
*/
boost::shared_ptr<InheritedZoneEntry> getZone() {
return (boost::shared_ptr<InheritedZoneEntry>(new InheritedZoneEntry(
@@ -228,7 +228,7 @@ TEST_F(ZoneEntryTest, ChangedNS) {
EXPECT_EQ(Fetchable::IN_PROGRESS, zone->getState());
EXPECT_NO_THROW(resolver_->provideNS(0, rr_single_));
// It should not be answered yet, it should ask for the IP addresses
- // (trough the NameserverEntry there)
+ // (through the NameserverEntry there)
EXPECT_TRUE(callback_->successes_.empty());
EXPECT_EQ(0, callback_->unreachable_count_);
EXPECT_TRUE(resolver_->asksIPs(ns_name_, 1, 2));
diff --git a/src/lib/nsas/zone_entry.cc b/src/lib/nsas/zone_entry.cc
index 8a72e5f..6d15533 100644
--- a/src/lib/nsas/zone_entry.cc
+++ b/src/lib/nsas/zone_entry.cc
@@ -93,7 +93,7 @@ class ZoneEntry::ResolverCallback :
* If there are in the hash table, it is used. If not, they are
* created. This might still fail, if the list is empty.
*
- * It then calls process, to go trough the list of nameservers,
+ * It then calls process, to go through the list of nameservers,
* examining them and seeing if some addresses are already there
* and to ask for the rest of them.
*/
@@ -389,7 +389,7 @@ class ZoneEntry::NameserverCallback : public NameserverEntry::Callback {
* \short Callback method.
*
* This is called by NameserverEntry when the change happens.
- * We just call process to go trough relevant nameservers and call
+ * We just call process to go through relevant nameservers and call
* any callbacks we can.
*/
virtual void operator()(NameserverPtr ns) {
@@ -451,8 +451,8 @@ ZoneEntry::process(AddressFamily family,
* one handle it when we return to it.
*
* If we didn't do it, one instance would call "resolve". If it
- * was from cache, it would imediatelly recurse back to another
- * process (trough the nameserver callback, etc), which would
+ * was from cache, it would immediately recurse back to another
+ * process (through the nameserver callback, etc), which would
* take that only one nameserver and trigger all callbacks.
* Only then would resolve terminate and we could ask for the
* second nameserver. This way, we first receive all the
diff --git a/src/lib/nsas/zone_entry.h b/src/lib/nsas/zone_entry.h
index 7e5ab5b..b0c26c3 100644
--- a/src/lib/nsas/zone_entry.h
+++ b/src/lib/nsas/zone_entry.h
@@ -125,7 +125,7 @@ protected:
// Which nameservers didn't have any of our callbacks yet
std::set<NameserverPtr> nameservers_not_asked_;
/*
- * Callbacks. For each fimily type one vector, so we can process
+ * Callbacks. For each family type one vector, so we can process
* them separately.
*/
std::vector<boost::shared_ptr<AddressRequestCallback> >
@@ -139,7 +139,7 @@ private:
/**
* \short Process all the callbacks that can be processed
*
- * The purpose of this funtion is to ask all nameservers for their IP
+ * The purpose of this function is to ask all nameservers for their IP
* addresses and execute all callbacks that can be executed. It is
* called whenever new callback appears and there's a chance it could
* be answered or when new information is available (list of nameservers,
diff --git a/src/lib/python/bind10_config.py.in b/src/lib/python/bind10_config.py.in
index 7ef660b..e56e908 100644
--- a/src/lib/python/bind10_config.py.in
+++ b/src/lib/python/bind10_config.py.in
@@ -33,7 +33,7 @@ def reload():
SYSCONFPATH="@sysconfdir@/@PACKAGE@".replace('${prefix}', PREFIX)
# B10_FROM_SOURCE is set in the environment for internal tests and
- # an experimental run without installagion. In that case we need to
+ # an experimental run without installation. In that case we need to
# specialize some configuration variables, generally so that they refer
# to somewhere in the source tree instead of the appropriate places
# after installation.
diff --git a/src/lib/python/isc/acl/tests/Makefile.am b/src/lib/python/isc/acl/tests/Makefile.am
index e0a1895..efe3664 100644
--- a/src/lib/python/isc/acl/tests/Makefile.am
+++ b/src/lib/python/isc/acl/tests/Makefile.am
@@ -7,7 +7,7 @@ EXTRA_DIST = $(PYTESTS)
# required by loadable python modules.
LIBRARY_PATH_PLACEHOLDER =
if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/acl/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/acl/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/util/threads/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
endif
# test using command-line arguments, so use check-local target instead of TESTS
diff --git a/src/lib/python/isc/bind10/component.py b/src/lib/python/isc/bind10/component.py
index 2efb376..4621fae 100644
--- a/src/lib/python/isc/bind10/component.py
+++ b/src/lib/python/isc/bind10/component.py
@@ -490,7 +490,7 @@ class Component(BaseComponent):
class Configurator:
"""
This thing keeps track of configuration changes and starts and stops
- components as it goes. It also handles the inital startup and final
+ components as it goes. It also handles the initial startup and final
shutdown.
Note that this will allow you to stop (by invoking reconfigure) a core
diff --git a/src/lib/python/isc/bind10/sockcreator.py b/src/lib/python/isc/bind10/sockcreator.py
index dec22c1..bb4250a 100644
--- a/src/lib/python/isc/bind10/sockcreator.py
+++ b/src/lib/python/isc/bind10/sockcreator.py
@@ -251,7 +251,7 @@ class Creator(Parser):
"""Function used before running a program that needs to run as a
different user."""
# Put us into a separate process group so we don't get
- # SIGINT signals on Ctrl-C (b10-init will shut everthing down by
+ # SIGINT signals on Ctrl-C (b10-init will shut everything down by
# other means).
os.setpgrp()
diff --git a/src/lib/python/isc/bind10/tests/Makefile.am b/src/lib/python/isc/bind10/tests/Makefile.am
index 196a8b9..12fb948 100644
--- a/src/lib/python/isc/bind10/tests/Makefile.am
+++ b/src/lib/python/isc/bind10/tests/Makefile.am
@@ -9,7 +9,7 @@ EXTRA_DIST = $(PYTESTS)
# required by loadable python modules.
LIBRARY_PATH_PLACEHOLDER =
if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/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/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/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:$$$(ENV_LIBRARY_PATH)
endif
# test using command-line arguments, so use check-local target instead of TESTS
diff --git a/src/lib/python/isc/bind10/tests/sockcreator_test.py b/src/lib/python/isc/bind10/tests/sockcreator_test.py
index f67781c..e87387c 100644
--- a/src/lib/python/isc/bind10/tests/sockcreator_test.py
+++ b/src/lib/python/isc/bind10/tests/sockcreator_test.py
@@ -306,7 +306,7 @@ class WrapTests(unittest.TestCase):
p1.close()
p1 = socket.fromfd(t2.read_fd(), socket.AF_UNIX, socket.SOCK_STREAM)
- # Now, pass some data trough the socket
+ # Now, pass some data through the socket
p1.send(b'A')
data = p2.recv(1)
self.assertEqual(b'A', data)
diff --git a/src/lib/python/isc/bind10/tests/socket_cache_test.py b/src/lib/python/isc/bind10/tests/socket_cache_test.py
index 1e1d83a..6a53a27 100644
--- a/src/lib/python/isc/bind10/tests/socket_cache_test.py
+++ b/src/lib/python/isc/bind10/tests/socket_cache_test.py
@@ -58,7 +58,7 @@ class SocketTest(Test):
def test_init(self):
"""
- Checks the intrnals of the cache just after the creation.
+ Checks the internals of the cache just after the creation.
"""
self.assertEqual('UDP', self.__socket.protocol)
self.assertEqual(self.__address, self.__socket.address)
@@ -357,7 +357,7 @@ class SocketCacheTest(Test):
}
self.__cache._live_tokens = set(['t1', 't2', 't3'])
self.assertEqual([], self._closes)
- # We cheat here little bit, the t3 doesn't exist enywhere else, but
+ # We cheat here little bit, the t3 doesn't exist anywhere else, but
# we need to check the app isn't removed too soon and it shouldn't
# matter anywhere else, so we just avoid the tiresome filling in
self.__cache._active_apps = {1: set(['t1', 't3']), 2: set(['t2'])}
diff --git a/src/lib/python/isc/cc/Makefile.am b/src/lib/python/isc/cc/Makefile.am
index f7c5b00..fe7d747 100644
--- a/src/lib/python/isc/cc/Makefile.am
+++ b/src/lib/python/isc/cc/Makefile.am
@@ -7,6 +7,7 @@ pylogmessagedir = $(pyexecdir)/isc/log_messages/
CLEANFILES = $(PYTHON_LOGMSGPKG_DIR)/work/pycc_messages.py
CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/pycc_messages.pyc
+CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/pycc_messages.pyo
EXTRA_DIST = pycc_messages.mes proto_defs.py
diff --git a/src/lib/python/isc/cc/cc_generated/Makefile.am b/src/lib/python/isc/cc/cc_generated/Makefile.am
index 87e49c1..bc8d478 100644
--- a/src/lib/python/isc/cc/cc_generated/Makefile.am
+++ b/src/lib/python/isc/cc/cc_generated/Makefile.am
@@ -26,6 +26,7 @@ CLEANDIRS = __pycache__
CLEANFILES = proto_defs.py __init__.py
CLEANFILES += proto_defs.pyc __init__.pyc
+CLEANFILES += proto_defs.pyo __init__.pyo
clean-local:
rm -rf $(CLEANDIRS)
diff --git a/src/lib/python/isc/cc/data.py b/src/lib/python/isc/cc/data.py
index 636e9a9..3411411 100644
--- a/src/lib/python/isc/cc/data.py
+++ b/src/lib/python/isc/cc/data.py
@@ -217,7 +217,7 @@ def set(element, identifier, value):
id, list_indices = split_identifier_list_indices(id_parts[-1])
if list_indices is None:
- # value can be an empty list or dict, so check for None eplicitely
+ # value can be an empty list or dict, so check for None explicitly
if value is not None:
cur_el[id] = value
else:
@@ -231,7 +231,7 @@ def set(element, identifier, value):
if len(cur_el) <= list_index:
raise DataNotFoundError("List index at " + identifier + " out of range")
cur_el = cur_el[list_index]
- # value can be an empty list or dict, so check for None eplicitely
+ # value can be an empty list or dict, so check for None explicitly
list_index = list_indices[-1]
if type(cur_el) != list:
raise DataTypeError("Element at " + identifier + " is not a list")
diff --git a/src/lib/python/isc/cc/session.py b/src/lib/python/isc/cc/session.py
index 036c078..636dd08 100644
--- a/src/lib/python/isc/cc/session.py
+++ b/src/lib/python/isc/cc/session.py
@@ -57,17 +57,19 @@ class Session:
try:
self._socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
self._socket.connect(self.socket_file)
- self.sendmsg({ "type": "getlname" })
+ self.sendmsg({ CC_HEADER_TYPE: CC_COMMAND_GET_LNAME })
env, msg = self.recvmsg(False)
if not env:
raise ProtocolError("Could not get local name")
- self._lname = msg["lname"]
+ self._lname = msg[CC_PAYLOAD_LNAME]
if not self._lname:
raise ProtocolError("Could not get local name")
logger.debug(logger.DBGLVL_TRACE_BASIC, PYCC_LNAME_RECEIVED,
self._lname)
except socket.error as se:
- raise SessionError(se)
+ if self._socket:
+ self._socket.close()
+ raise SessionError(se)
@property
def lname(self):
@@ -125,9 +127,10 @@ class Session:
if len(self._queue) > 0:
i = 0;
for env, msg in self._queue:
- if seq != None and "reply" in env and seq == env["reply"]:
+ if seq != None and CC_HEADER_REPLY in env and \
+ seq == env[CC_HEADER_REPLY]:
return self._queue.pop(i)
- elif seq == None and "reply" not in env:
+ elif seq == None and CC_HEADER_REPLY not in env:
return self._queue.pop(i)
else:
i = i + 1
@@ -141,7 +144,9 @@ class Session:
if data_length > 0:
env = isc.cc.message.from_wire(data[2:header_length+2])
msg = isc.cc.message.from_wire(data[header_length + 2:])
- if (seq == None and "reply" not in env) or (seq != None and "reply" in env and seq == env["reply"]):
+ if (seq == None and CC_HEADER_REPLY not in env) or \
+ (seq != None and CC_HEADER_REPLY in env and
+ seq == env[CC_HEADER_REPLY]):
return env, msg
else:
self._queue.append((env,msg))
@@ -248,18 +253,18 @@ class Session:
self._sequence += 1
return self._sequence
- def group_subscribe(self, group, instance = "*"):
+ def group_subscribe(self, group, instance=CC_INSTANCE_WILDCARD):
self.sendmsg({
- "type": "subscribe",
- "group": group,
- "instance": instance,
+ CC_HEADER_TYPE: CC_COMMAND_SUBSCRIBE,
+ CC_HEADER_GROUP: group,
+ CC_HEADER_INSTANCE: instance,
})
- def group_unsubscribe(self, group, instance = "*"):
+ def group_unsubscribe(self, group, instance=CC_INSTANCE_WILDCARD):
self.sendmsg({
- "type": "unsubscribe",
- "group": group,
- "instance": instance,
+ CC_HEADER_TYPE: CC_COMMAND_UNSUBSCRIBE,
+ CC_HEADER_GROUP: group,
+ CC_HEADER_INSTANCE: instance,
})
def group_sendmsg(self, msg, group, instance=CC_INSTANCE_WILDCARD,
@@ -308,13 +313,13 @@ class Session:
def group_reply(self, routing, msg):
seq = self._next_sequence()
self.sendmsg({
- "type": "send",
- "from": self._lname,
- "to": routing["from"],
- "group": routing["group"],
- "instance": routing["instance"],
- "seq": seq,
- "reply": routing["seq"],
+ CC_HEADER_TYPE: CC_COMMAND_SEND,
+ CC_HEADER_FROM: self._lname,
+ CC_HEADER_TO: routing[CC_HEADER_FROM],
+ CC_HEADER_GROUP: routing[CC_HEADER_GROUP],
+ CC_HEADER_INSTANCE: routing[CC_HEADER_INSTANCE],
+ CC_HEADER_SEQ: seq,
+ CC_HEADER_REPLY: routing[CC_HEADER_SEQ],
}, isc.cc.message.to_wire(msg))
return seq
diff --git a/src/lib/python/isc/cc/tests/Makefile.am b/src/lib/python/isc/cc/tests/Makefile.am
index 4c2acc0..7b4288c 100644
--- a/src/lib/python/isc/cc/tests/Makefile.am
+++ b/src/lib/python/isc/cc/tests/Makefile.am
@@ -10,7 +10,7 @@ EXTRA_DIST += test_session.py
# required by loadable python modules.
LIBRARY_PATH_PLACEHOLDER =
if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/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/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/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/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
endif
# test using command-line arguments, so use check-local target instead of TESTS
diff --git a/src/lib/python/isc/config/ccsession.py b/src/lib/python/isc/config/ccsession.py
index b592d8d..e801309 100644
--- a/src/lib/python/isc/config/ccsession.py
+++ b/src/lib/python/isc/config/ccsession.py
@@ -82,22 +82,22 @@ def parse_answer(msg):
containing an error message"""
if type(msg) != dict:
raise ModuleCCSessionError("Answer message is not a dict: " + str(msg))
- if 'result' not in msg:
+ if CC_PAYLOAD_RESULT not in msg:
raise ModuleCCSessionError("answer message does not contain 'result' element")
- elif type(msg['result']) != list:
+ elif type(msg[CC_PAYLOAD_RESULT]) != list:
raise ModuleCCSessionError("wrong result type in answer message")
- elif len(msg['result']) < 1:
+ elif len(msg[CC_PAYLOAD_RESULT]) < 1:
raise ModuleCCSessionError("empty result list in answer message")
- elif type(msg['result'][0]) != int:
+ elif type(msg[CC_PAYLOAD_RESULT][0]) != int:
raise ModuleCCSessionError("wrong rcode type in answer message")
else:
- if len(msg['result']) > 1:
- if (msg['result'][0] != CC_REPLY_SUCCESS and
- type(msg['result'][1]) != str):
+ if len(msg[CC_PAYLOAD_RESULT]) > 1:
+ if (msg[CC_PAYLOAD_RESULT][0] != CC_REPLY_SUCCESS and
+ type(msg[CC_PAYLOAD_RESULT][1]) != str):
raise ModuleCCSessionError("rcode in answer message is non-zero, value is not a string")
- return msg['result'][0], msg['result'][1]
+ return msg[CC_PAYLOAD_RESULT][0], msg[CC_PAYLOAD_RESULT][1]
else:
- return msg['result'][0], None
+ return msg[CC_PAYLOAD_RESULT][0], None
def create_answer(rcode, arg = None):
"""Creates an answer packet for config&commands. rcode must be an
@@ -109,9 +109,9 @@ def create_answer(rcode, arg = None):
if rcode != CC_REPLY_SUCCESS and type(arg) != str:
raise ModuleCCSessionError("arg in create_answer for rcode != 0 must be a string describing the error")
if arg != None:
- return { 'result': [ rcode, arg ] }
+ return { CC_PAYLOAD_RESULT: [ rcode, arg ] }
else:
- return { 'result': [ rcode ] }
+ return { CC_PAYLOAD_RESULT: [ rcode ] }
# 'fixed' commands
"""Fixed names for command and configuration messages"""
@@ -133,7 +133,7 @@ def parse_command(msg):
string. If it is not, this function returns None, None"""
if type(msg) == dict and len(msg.items()) == 1:
cmd, value = msg.popitem()
- if cmd == "command" and type(value) == list:
+ if cmd == CC_PAYLOAD_COMMAND and type(value) == list:
if len(value) == 1 and type(value[0]) == str:
return value[0], None
elif len(value) > 1 and type(value[0]) == str:
@@ -150,7 +150,7 @@ def create_command(command_name, params = None):
cmd = [ command_name ]
if params:
cmd.append(params)
- msg = { 'command': cmd }
+ msg = { CC_PAYLOAD_COMMAND: cmd }
return msg
def default_logconfig_handler(new_config, config_data):
@@ -194,7 +194,7 @@ class ModuleCCSession(ConfigData):
it will read the system-wide Logging configuration and call
the logger manager to apply it. It will also inform the
logger manager when the logging configuration gets updated.
- The module does not need to do anything except intializing
+ The module does not need to do anything except initializing
its loggers, and provide log messages. Defaults to true.
socket_file: If cc_session was none, this optional argument
@@ -215,7 +215,7 @@ class ModuleCCSession(ConfigData):
self._session = Session(socket_file)
else:
self._session = cc_session
- self._session.group_subscribe(self._module_name, "*")
+ self._session.group_subscribe(self._module_name, CC_INSTANCE_WILDCARD)
self._remote_module_configs = {}
self._remote_module_callbacks = {}
@@ -228,7 +228,8 @@ class ModuleCCSession(ConfigData):
# If the CC Session obejct has been closed, it returns
# immediately.
if self._session._closed: return
- self._session.group_unsubscribe(self._module_name, "*")
+ self._session.group_unsubscribe(self._module_name,
+ CC_INSTANCE_WILDCARD)
for module_name in self._remote_module_configs:
self._session.group_unsubscribe(module_name)
@@ -294,10 +295,10 @@ class ModuleCCSession(ConfigData):
functions if present. Responds on the channel if the
handler returns a message."""
# should we default to an answer? success-by-default? unhandled error?
- if msg is not None and not 'result' in msg:
+ if msg is not None and not CC_PAYLOAD_RESULT in msg:
answer = None
try:
- module_name = env['group']
+ module_name = env[CC_HEADER_GROUP]
cmd, arg = isc.config.ccsession.parse_command(msg)
if cmd == COMMAND_CONFIG_UPDATE:
new_config = arg
diff --git a/src/lib/python/isc/config/cfgmgr.py b/src/lib/python/isc/config/cfgmgr.py
index 82c8e6c..d100aa8 100644
--- a/src/lib/python/isc/config/cfgmgr.py
+++ b/src/lib/python/isc/config/cfgmgr.py
@@ -28,6 +28,7 @@ import tempfile
import json
import errno
from isc.cc import data
+from isc.cc.proto_defs import *
from isc.config import ccsession, config_data, module_spec
from isc.util.file import path_search
import bind10_config
@@ -441,7 +442,7 @@ class ConfigManager:
# polymorphism instead of branching. But it just might turn out it
# will get solved by itself when we move everything to virtual modules
# (which is possible solution to the offline configuration problem)
- # or when we solve the incorect behaviour here when a config is
+ # or when we solve the incorrect behaviour here when a config is
# rejected (spying modules don't know it was rejected and some modules
# might have been committed already).
if module_name in self.virtual_modules:
@@ -603,7 +604,7 @@ class ConfigManager:
# ignore 'None' value (even though they should not occur)
# and messages that are answers to questions we did
# not ask
- if msg is not None and not 'result' in msg:
+ if msg is not None and not CC_PAYLOAD_RESULT in msg:
answer = self.handle_msg(msg);
# Only respond if there actually is something to respond with
if answer is not None:
diff --git a/src/lib/python/isc/config/tests/Makefile.am b/src/lib/python/isc/config/tests/Makefile.am
index cb59e6f..8c79c84 100644
--- a/src/lib/python/isc/config/tests/Makefile.am
+++ b/src/lib/python/isc/config/tests/Makefile.am
@@ -8,7 +8,7 @@ EXTRA_DIST += unittest_fakesession.py
# required by loadable python modules.
LIBRARY_PATH_PLACEHOLDER =
if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/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/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/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/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
endif
# test using command-line arguments, so use check-local target instead of TESTS
diff --git a/src/lib/python/isc/config/tests/cfgmgr_test.py b/src/lib/python/isc/config/tests/cfgmgr_test.py
index d99fb86..ee6c98d 100644
--- a/src/lib/python/isc/config/tests/cfgmgr_test.py
+++ b/src/lib/python/isc/config/tests/cfgmgr_test.py
@@ -201,7 +201,7 @@ class TestConfigManager(unittest.TestCase):
def test_paths(self):
"""
- Test data_path and database filename is passed trough to
+ Test data_path and database filename is passed through to
underlying ConfigManagerData.
"""
cm = ConfigManager("datapath", "filename", self.fake_session)
@@ -527,7 +527,7 @@ class TestConfigManager(unittest.TestCase):
self.fake_session.get_message("Cmdctl", None))
# but if the 'stopping' module is either unknown or not running,
- # no followup message should be sent
+ # no follow-up message should be sent
self._handle_msg_helper({ "command":
[ "stopping",
{ "module_name": "NoSuchModule" } ] },
@@ -541,7 +541,7 @@ class TestConfigManager(unittest.TestCase):
# We run the same three times, with different return values
def single_test(value, returnFunc, expectedResult):
# Because closures can't assign to closed-in variables, we pass
- # it trough self
+ # it through self
self.called_with = None
def check_test(new_data):
self.called_with = new_data
diff --git a/src/lib/python/isc/datasrc/client_inc.cc b/src/lib/python/isc/datasrc/client_inc.cc
index 7b6fac5..a59ff85 100644
--- a/src/lib/python/isc/datasrc/client_inc.cc
+++ b/src/lib/python/isc/datasrc/client_inc.cc
@@ -177,7 +177,7 @@ datasource, or when an internal error occurs.\n\
\n\
The default implementation throws isc.datasrc.NotImplemented. This allows for\n\
easy and fast deployment of minimal custom data sources, where the\n\
-user/implementator doesn't have to care about anything else but the\n\
+user/implementer doesn't have to care about anything else but the\n\
actual queries. Also, in some cases, it isn't possible to traverse the\n\
zone from logic point of view (eg. dynamically generated zone data).\n\
\n\
diff --git a/src/lib/python/isc/datasrc/client_python.cc b/src/lib/python/isc/datasrc/client_python.cc
index a30ae38..3eed80e 100644
--- a/src/lib/python/isc/datasrc/client_python.cc
+++ b/src/lib/python/isc/datasrc/client_python.cc
@@ -25,7 +25,7 @@
#include <datasrc/client.h>
#include <datasrc/factory.h>
#include <datasrc/database.h>
-#include <datasrc/data_source.h>
+#include <datasrc/exceptions.h>
#include <datasrc/sqlite3_accessor.h>
#include <datasrc/zone_iterator.h>
#include <datasrc/client_list.h>
@@ -162,7 +162,7 @@ DataSourceClient_getIterator(PyObject* po_self, PyObject* args) {
try {
bool separate_rrs = false;
if (separate_rrs_obj != NULL) {
- // store result in local var so we can explicitely check for
+ // store result in local var so we can explicitly check for
// -1 error return value
int separate_rrs_true = PyObject_IsTrue(separate_rrs_obj);
if (separate_rrs_true == 1) {
diff --git a/src/lib/python/isc/datasrc/datasrc.cc b/src/lib/python/isc/datasrc/datasrc.cc
index c183af9..f2cbae3 100644
--- a/src/lib/python/isc/datasrc/datasrc.cc
+++ b/src/lib/python/isc/datasrc/datasrc.cc
@@ -23,6 +23,8 @@
#include <datasrc/sqlite3_accessor.h>
#include <datasrc/zone_loader.h>
+#include <log/message_initializer.h>
+
#include "datasrc.h"
#include "client_python.h"
#include "finder_python.h"
@@ -276,6 +278,8 @@ PyModuleDef iscDataSrc = {
PyMODINIT_FUNC
PyInit_datasrc(void) {
+ isc::log::MessageInitializer::loadDictionary();
+
PyObject* mod = PyModule_Create(&iscDataSrc);
if (mod == NULL) {
return (NULL);
diff --git a/src/lib/python/isc/datasrc/finder_python.cc b/src/lib/python/isc/datasrc/finder_python.cc
index 05c44c9..ef1bcc1 100644
--- a/src/lib/python/isc/datasrc/finder_python.cc
+++ b/src/lib/python/isc/datasrc/finder_python.cc
@@ -24,7 +24,7 @@
#include <datasrc/client.h>
#include <datasrc/database.h>
-#include <datasrc/data_source.h>
+#include <datasrc/exceptions.h>
#include <datasrc/sqlite3_accessor.h>
#include <datasrc/zone_iterator.h>
#include <datasrc/zone.h>
diff --git a/src/lib/python/isc/datasrc/tests/Makefile.am b/src/lib/python/isc/datasrc/tests/Makefile.am
index eb82972..a86c2b4 100644
--- a/src/lib/python/isc/datasrc/tests/Makefile.am
+++ b/src/lib/python/isc/datasrc/tests/Makefile.am
@@ -22,7 +22,7 @@ CLEANFILES += $(abs_builddir)/zoneloadertest.sqlite3
# of scope for this ticket
LIBRARY_PATH_PLACEHOLDER = $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/datasrc/.libs:
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/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/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/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
else
LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
endif
diff --git a/src/lib/python/isc/datasrc/tests/datasrc_test.py b/src/lib/python/isc/datasrc/tests/datasrc_test.py
index 4cc4e9b..1649fc1 100644
--- a/src/lib/python/isc/datasrc/tests/datasrc_test.py
+++ b/src/lib/python/isc/datasrc/tests/datasrc_test.py
@@ -33,8 +33,11 @@ WRITE_ZONE_DB_FILE = TESTDATA_WRITE_PATH + "rwtest.sqlite3.copied"
READ_ZONE_DB_CONFIG = "{ \"database_file\": \"" + READ_ZONE_DB_FILE + "\" }"
WRITE_ZONE_DB_CONFIG = "{ \"database_file\": \"" + WRITE_ZONE_DB_FILE + "\"}"
-
-STATIC_ZONE_CONFIG = '"' + TESTDATA_PATH + "static.zone" + '"'
+# In-memory data source must be built via client list.
+INMEMORY_ZONE_CONFIG = '''[{
+ "type": "MasterFiles",
+ "cache-enable": true,
+ "params": {"example.com": "''' + TESTDATA_PATH + 'example.com"}}]'
def add_rrset(rrset_list, name, rrclass, rrtype, ttl, rdatas):
rrset_to_add = isc.dns.RRset(name, rrclass, rrtype, ttl)
@@ -563,11 +566,10 @@ class DataSrcUpdater(unittest.TestCase):
self.assertEqual(updater_refs, sys.getrefcount(updater))
def test_two_modules(self):
- # load two modules, and check if they don't interfere; as the
- # memory datasource module no longer exists, we check the static
- # datasource instead (as that uses the memory datasource
- # anyway).
- dsc_static = isc.datasrc.DataSourceClient("static", STATIC_ZONE_CONFIG)
+ # load two modules, and check if they don't interfere.
+ clist = isc.datasrc.ConfigurableClientList(isc.dns.RRClass.IN)
+ clist.configure(INMEMORY_ZONE_CONFIG, True)
+ dsc_static = clist.find(isc.dns.Name("example.com"), True, False)[0]
dsc_sql = isc.datasrc.DataSourceClient("sqlite3", READ_ZONE_DB_CONFIG)
# check if exceptions are working
@@ -593,7 +595,7 @@ class DataSrcUpdater(unittest.TestCase):
self.assertEqual(finder.NXDOMAIN, result)
def test_update_delete_abort(self):
- # we don't do enything with this one, just making sure loading two
+ # we don't do anything with this one, just making sure loading two
# datasources
dsc = isc.datasrc.DataSourceClient("sqlite3", WRITE_ZONE_DB_CONFIG)
@@ -724,10 +726,9 @@ class DataSrcUpdater(unittest.TestCase):
self.assertTrue(dsc.delete_zone(zone_name))
def test_create_zone_not_implemented(self):
- # As the memory datasource module no longer exists, we check the
- # static datasource instead (as that uses the memory datasource
- # anyway).
- dsc = isc.datasrc.DataSourceClient("static", STATIC_ZONE_CONFIG)
+ clist = isc.datasrc.ConfigurableClientList(isc.dns.RRClass.IN)
+ clist.configure(INMEMORY_ZONE_CONFIG, True)
+ dsc = clist.find(isc.dns.Name("example.com"), True, False)[0]
self.assertRaises(isc.datasrc.NotImplemented, dsc.create_zone,
isc.dns.Name("example.com"))
diff --git a/src/lib/python/isc/datasrc/tests/zone_loader_test.py b/src/lib/python/isc/datasrc/tests/zone_loader_test.py
index e437e36..4811aba 100644
--- a/src/lib/python/isc/datasrc/tests/zone_loader_test.py
+++ b/src/lib/python/isc/datasrc/tests/zone_loader_test.py
@@ -34,7 +34,11 @@ ORIG_DB_FILE = TESTDATA_PATH + '/example.com.sqlite3'
DB_FILE = TESTDATA_WRITE_PATH + '/zoneloadertest.sqlite3'
DB_CLIENT_CONFIG = '{ "database_file": "' + DB_FILE + '" }'
DB_SOURCE_CLIENT_CONFIG = '{ "database_file": "' + SOURCE_DB_FILE + '" }'
-STATIC_ZONE_CONFIG = '"' + TESTDATA_PATH + "/static.zone" + '"'
+# In-memory data source must be built via client list.
+INMEMORY_ZONE_CONFIG = '''[{
+ "type": "MasterFiles",
+ "cache-enable": true,
+ "params": {"example.com": "''' + TESTDATA_PATH + '/example.com"}}]'
ORIG_SOA_TXT = 'example.com. 3600 IN SOA master.example.com. ' +\
'admin.example.com. 1234 3600 1800 2419200 7200\n'
@@ -216,15 +220,11 @@ class ZoneLoaderTests(unittest.TestCase):
self.client, zone_name, self.source_client)
def test_no_ds_load_support(self):
- # As the memory datasource module no longer exists, we check the
- # static datasource instead (as that uses the memory datasource
- # anyway).
- #
- # This may change in the future, but ATM, the static ds does not
- # support the API the zone loader uses (it has direct load
- # calls).
- inmem_client = isc.datasrc.DataSourceClient('static',
- STATIC_ZONE_CONFIG);
+ # This may change in the future, but ATM, in-memory ds does not
+ # support the API the zone loader uses.
+ clist = isc.datasrc.ConfigurableClientList(isc.dns.RRClass.IN)
+ clist.configure(INMEMORY_ZONE_CONFIG, True)
+ inmem_client = clist.find(isc.dns.Name("example.com"), True, False)[0]
self.assertRaises(isc.datasrc.NotImplemented,
isc.datasrc.ZoneLoader,
inmem_client, self.test_name, self.test_file)
@@ -239,8 +239,10 @@ class ZoneLoaderTests(unittest.TestCase):
# For ds->ds loading, wrong class is detected upon construction
# Need a bit of the extended setup for CH source client
clientlist = isc.datasrc.ConfigurableClientList(isc.dns.RRClass.CH)
- clientlist.configure('[ { "type": "static", "params": "' +
- STATIC_ZONE_FILE +'" } ]', False)
+ clientlist.configure('[ { "type": "MasterFiles", ' +
+ '"cache-enable": true, ' +
+ '"params": {"BIND": "' +
+ STATIC_ZONE_FILE + '" }} ]', True)
self.source_client, _, _ = clientlist.find(isc.dns.Name("bind."),
False, False)
self.assertRaises(isc.dns.InvalidParameter, isc.datasrc.ZoneLoader,
diff --git a/src/lib/python/isc/datasrc/updater_python.cc b/src/lib/python/isc/datasrc/updater_python.cc
index e61db75..331eafa 100644
--- a/src/lib/python/isc/datasrc/updater_python.cc
+++ b/src/lib/python/isc/datasrc/updater_python.cc
@@ -24,7 +24,7 @@
#include <datasrc/client.h>
#include <datasrc/database.h>
-#include <datasrc/data_source.h>
+#include <datasrc/exceptions.h>
#include <datasrc/sqlite3_accessor.h>
#include <datasrc/zone.h>
diff --git a/src/lib/python/isc/ddns/session.py b/src/lib/python/isc/ddns/session.py
index 3368523..febdfdc 100644
--- a/src/lib/python/isc/ddns/session.py
+++ b/src/lib/python/isc/ddns/session.py
@@ -656,7 +656,7 @@ class UpdateSession:
'''
# For a number of cases, we may need to remove data in the zone
# (note; SOA is handled separately by __do_update, so that one
- # is explicitely ignored here)
+ # is explicitly ignored here)
if rrset.get_type() == RRType.SOA:
return
result, orig_rrset, _ = self.__diff.find(rrset.get_name(),
diff --git a/src/lib/python/isc/ddns/tests/Makefile.am b/src/lib/python/isc/ddns/tests/Makefile.am
index 4235a2b..9374dde 100644
--- a/src/lib/python/isc/ddns/tests/Makefile.am
+++ b/src/lib/python/isc/ddns/tests/Makefile.am
@@ -6,7 +6,7 @@ CLEANFILES = $(builddir)/rwtest.sqlite3.copied
# 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/acl/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER = $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/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
# test using command-line arguments, so use check-local target instead of TESTS
diff --git a/src/lib/python/isc/ddns/tests/session_tests.py b/src/lib/python/isc/ddns/tests/session_tests.py
index bc25310..4880d72 100644
--- a/src/lib/python/isc/ddns/tests/session_tests.py
+++ b/src/lib/python/isc/ddns/tests/session_tests.py
@@ -209,7 +209,7 @@ class SessionTestBase(unittest.TestCase):
def tearDown(self):
# With the Updater created in _get_update_zone, and tests
# doing all kinds of crazy stuff, one might get database locked
- # errors if it doesn't clean up explicitely after each test
+ # errors if it doesn't clean up explicitly after each test
self._session = None
def check_response(self, msg, expected_rcode):
diff --git a/src/lib/python/isc/log/tests/Makefile.am b/src/lib/python/isc/log/tests/Makefile.am
index ec29b7a..14f10a2 100644
--- a/src/lib/python/isc/log/tests/Makefile.am
+++ b/src/lib/python/isc/log/tests/Makefile.am
@@ -8,7 +8,7 @@ EXTRA_DIST = console.out check_output.sh $(PYTESTS_NOGEN)
# 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/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/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/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
endif
# test using command-line arguments, so use check-local target instead of TESTS
diff --git a/src/lib/python/isc/net/tests/Makefile.am b/src/lib/python/isc/net/tests/Makefile.am
index dd94946..44f6063 100644
--- a/src/lib/python/isc/net/tests/Makefile.am
+++ b/src/lib/python/isc/net/tests/Makefile.am
@@ -6,7 +6,7 @@ EXTRA_DIST = $(PYTESTS)
# required by loadable python modules.
LIBRARY_PATH_PLACEHOLDER =
if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/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/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/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/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
endif
# test using command-line arguments, so use check-local target instead of TESTS
diff --git a/src/lib/python/isc/notify/notify_out.py b/src/lib/python/isc/notify/notify_out.py
index d1ec2e9..a030a51 100644
--- a/src/lib/python/isc/notify/notify_out.py
+++ b/src/lib/python/isc/notify/notify_out.py
@@ -26,6 +26,7 @@ from isc.net import addr
import isc
from isc.log_messages.notify_out_messages import *
from isc.statistics import Counters
+from isc.util.address_formatter import AddressFormatter
logger = isc.log.Logger("notify_out")
@@ -40,7 +41,6 @@ ZONE_XFRIN_FAILED = 'zone_xfrin_failed'
_MAX_NOTIFY_NUM = 30
_MAX_NOTIFY_TRY_NUM = 5
-_EVENT_NONE = 0
_EVENT_READ = 1
_EVENT_TIMEOUT = 2
_NOTIFY_TIMEOUT = 1
@@ -210,7 +210,8 @@ class NotifyOut:
for name_ in not_replied_zones:
if not_replied_zones[name_].notify_timeout <= time.time():
- self._zone_notify_handler(not_replied_zones[name_], _EVENT_TIMEOUT)
+ self._zone_notify_handler(not_replied_zones[name_],
+ _EVENT_TIMEOUT)
def dispatcher(self, daemon=False):
"""Spawns a thread that will handle notify related events.
@@ -420,31 +421,57 @@ class NotifyOut:
return replied_zones, not_replied_zones
def _zone_notify_handler(self, zone_notify_info, event_type):
- '''Notify handler for one zone. The first notify message is
- always triggered by the event "_EVENT_TIMEOUT" since when
- one zone prepares to notify its slaves, its notify_timeout
- is set to now, which is used to trigger sending notify
- message when dispatcher() scanning zones. '''
+ """Notify handler for one zone.
+
+ For the event type of _EVENT_READ, this method reads a new notify
+ response message from the corresponding socket. If it succeeds
+ and the response is the expected one, it will send another notify
+ to the next slave for the zone (if any) or the next zone (if any)
+ waiting for its turn of sending notifies.
+
+ In the case of _EVENT_TIMEOUT, or if the read fails or the response
+ is not an expected one in the case of _EVENT_READ, this method will
+ resend the notify request to the same slave up to _MAX_NOTIFY_TRY_NUM
+ times. If it reaches the max, it will swith to the next slave or
+ the next zone like the successful case above.
+
+ The first notify message is always triggered by the event
+ "_EVENT_TIMEOUT" since when one zone prepares to notify its slaves,
+ its notify_timeout is set to now, which is used to trigger sending
+ notify message when dispatcher() scanning zones.
+
+ Parameters:
+ zone_notify_info(ZoneNotifyInfo): the notify context for the event
+ event_type(int): either _EVENT_READ or _EVENT_TIMEOUT constant
+
+ """
tgt = zone_notify_info.get_current_notify_target()
if event_type == _EVENT_READ:
+ # Note: _get_notify_reply() should also check the response's
+ # source address (see #2924). When it's done the following code
+ # should also be adjusted a bit.
reply = self._get_notify_reply(zone_notify_info.get_socket(), tgt)
if reply is not None:
- if self._handle_notify_reply(zone_notify_info, reply, tgt):
+ if (self._handle_notify_reply(zone_notify_info, reply, tgt) ==
+ _REPLY_OK):
self._notify_next_target(zone_notify_info)
- elif event_type == _EVENT_TIMEOUT and zone_notify_info.notify_try_num > 0:
- logger.info(NOTIFY_OUT_TIMEOUT, tgt[0], tgt[1])
+ else:
+ assert event_type == _EVENT_TIMEOUT
+ if zone_notify_info.notify_try_num > 0:
+ logger.info(NOTIFY_OUT_TIMEOUT, AddressFormatter(tgt))
tgt = zone_notify_info.get_current_notify_target()
if tgt:
zone_notify_info.notify_try_num += 1
if zone_notify_info.notify_try_num > _MAX_NOTIFY_TRY_NUM:
- logger.warn(NOTIFY_OUT_RETRY_EXCEEDED, tgt[0], tgt[1],
+ logger.warn(NOTIFY_OUT_RETRY_EXCEEDED, AddressFormatter(tgt),
_MAX_NOTIFY_TRY_NUM)
self._notify_next_target(zone_notify_info)
else:
- # set exponential backoff according rfc1996 section 3.6
- retry_timeout = _NOTIFY_TIMEOUT * pow(2, zone_notify_info.notify_try_num)
+ # set exponential backoff according to rfc1996 section 3.6
+ retry_timeout = (_NOTIFY_TIMEOUT *
+ pow(2, zone_notify_info.notify_try_num))
zone_notify_info.notify_timeout = time.time() + retry_timeout
self._send_notify_message_udp(zone_notify_info, tgt)
@@ -487,15 +514,14 @@ class NotifyOut:
elif zone_notify_info.get_socket().family == socket.AF_INET6:
self._counters.inc('zones', zone_notify_info.zone_name,
'notifyoutv6')
- logger.info(NOTIFY_OUT_SENDING_NOTIFY, addrinfo[0],
- addrinfo[1])
+ logger.info(NOTIFY_OUT_SENDING_NOTIFY, AddressFormatter(addrinfo))
except (socket.error, addr.InvalidAddress) as err:
- logger.error(NOTIFY_OUT_SOCKET_ERROR, addrinfo[0],
- addrinfo[1], err)
+ logger.error(NOTIFY_OUT_SOCKET_ERROR, AddressFormatter(addrinfo),
+ err)
return False
except addr.InvalidAddress as iae:
- logger.error(NOTIFY_OUT_INVALID_ADDRESS, addrinfo[0],
- addrinfo[1], iae)
+ logger.error(NOTIFY_OUT_INVALID_ADDRESS,
+ AddressFormatter(addrinfo), iae)
return False
return True
@@ -537,47 +563,56 @@ class NotifyOut:
return soa_rrset
def _handle_notify_reply(self, zone_notify_info, msg_data, from_addr):
- '''Parse the notify reply message.
- rcode will not checked here, If we get the response
- from the slave, it means the slaves has got the notify.'''
+ """Parse the notify reply message.
+
+ rcode will not be checked here; if we get the response
+ from the slave, it means the slave got the notify.
+
+ """
msg = Message(Message.PARSE)
try:
msg.from_wire(msg_data)
if not msg.get_header_flag(Message.HEADERFLAG_QR):
- logger.warn(NOTIFY_OUT_REPLY_QR_NOT_SET, from_addr[0],
- from_addr[1])
+ logger.warn(NOTIFY_OUT_REPLY_QR_NOT_SET,
+ AddressFormatter(from_addr))
return _BAD_QR
if msg.get_qid() != zone_notify_info.notify_msg_id:
- logger.warn(NOTIFY_OUT_REPLY_BAD_QID, from_addr[0],
- from_addr[1], msg.get_qid(),
+ logger.warn(NOTIFY_OUT_REPLY_BAD_QID,
+ AddressFormatter(from_addr), msg.get_qid(),
zone_notify_info.notify_msg_id)
return _BAD_QUERY_ID
question = msg.get_question()[0]
if question.get_name() != Name(zone_notify_info.zone_name):
- logger.warn(NOTIFY_OUT_REPLY_BAD_QUERY_NAME, from_addr[0],
- from_addr[1], question.get_name().to_text(),
+ logger.warn(NOTIFY_OUT_REPLY_BAD_QUERY_NAME,
+ AddressFormatter(from_addr),
+ question.get_name().to_text(),
Name(zone_notify_info.zone_name).to_text())
return _BAD_QUERY_NAME
if msg.get_opcode() != Opcode.NOTIFY:
- logger.warn(NOTIFY_OUT_REPLY_BAD_OPCODE, from_addr[0],
- from_addr[1], msg.get_opcode().to_text())
+ logger.warn(NOTIFY_OUT_REPLY_BAD_OPCODE,
+ AddressFormatter(from_addr),
+ msg.get_opcode().to_text())
return _BAD_OPCODE
except Exception as err:
# We don't care what exception, just report it?
logger.error(NOTIFY_OUT_REPLY_UNCAUGHT_EXCEPTION, err)
return _BAD_REPLY_PACKET
+ logger.debug(logger.DBGLVL_TRACE_BASIC, NOTIFY_OUT_REPLY_RECEIVED,
+ zone_notify_info.zone_name, zone_notify_info.zone_class,
+ AddressFormatter(from_addr), msg.get_rcode())
+
return _REPLY_OK
def _get_notify_reply(self, sock, tgt_addr):
try:
msg, addr = sock.recvfrom(512)
except socket.error as err:
- logger.error(NOTIFY_OUT_SOCKET_RECV_ERROR, tgt_addr[0],
- tgt_addr[1], err)
+ logger.error(NOTIFY_OUT_SOCKET_RECV_ERROR,
+ AddressFormatter(tgt_addr), err)
return None
return msg
diff --git a/src/lib/python/isc/notify/notify_out_messages.mes b/src/lib/python/isc/notify/notify_out_messages.mes
index 3bc0f38..fd08f43 100644
--- a/src/lib/python/isc/notify/notify_out_messages.mes
+++ b/src/lib/python/isc/notify/notify_out_messages.mes
@@ -27,7 +27,7 @@ because notify_out first identifies a list of available zones before
this process. So this means some critical inconsistency in the data
source or software bug.
-% NOTIFY_OUT_INVALID_ADDRESS invalid address %1#%2: %3
+% NOTIFY_OUT_INVALID_ADDRESS invalid address %1: %2
The notify_out library tried to send a notify message to the given
address, but it appears to be an invalid address. The configuration
for secondary nameservers might contain a typographic error, or a
@@ -35,31 +35,36 @@ different BIND 10 module has forgotten to validate its data before
sending this module a notify command. As such, this should normally
not happen, and points to an oversight in a different module.
-% NOTIFY_OUT_REPLY_BAD_OPCODE bad opcode in notify reply from %1#%2: %3
+% NOTIFY_OUT_REPLY_BAD_OPCODE bad opcode in notify reply from %1: %2
The notify_out library sent a notify message to the nameserver at
the given address, but the response did not have the opcode set to
NOTIFY. The opcode in the response is printed. Since there was a
response, no more notifies will be sent to this server for this
notification event.
-% NOTIFY_OUT_REPLY_BAD_QID bad QID in notify reply from %1#%2: got %3, should be %4
+% NOTIFY_OUT_REPLY_BAD_QID bad QID in notify reply from %1: got %2, should be %3
The notify_out library sent a notify message to the nameserver at
the given address, but the query id in the response does not match
the one we sent. Since there was a response, no more notifies will
be sent to this server for this notification event.
-% NOTIFY_OUT_REPLY_BAD_QUERY_NAME bad query name in notify reply from %1#%2: got %3, should be %4
+% NOTIFY_OUT_REPLY_BAD_QUERY_NAME bad query name in notify reply from %1: got %2, should be %3
The notify_out library sent a notify message to the nameserver at
the given address, but the query name in the response does not match
the one we sent. Since there was a response, no more notifies will
be sent to this server for this notification event.
-% NOTIFY_OUT_REPLY_QR_NOT_SET QR flags set to 0 in reply to notify from %1#%2
+% NOTIFY_OUT_REPLY_QR_NOT_SET QR flags set to 0 in reply to notify from %1
The notify_out library sent a notify message to the namesever at the
given address, but the reply did not have the QR bit set to one.
Since there was a response, no more notifies will be sent to this
server for this notification event.
+% NOTIFY_OUT_REPLY_RECEIVED Zone %1/%2: notify response from %3: %4
+The notify_out library sent a notify message to the nameserver at
+the given address, and received a response. Its Rcode will be shown,
+too.
+
% NOTIFY_OUT_REPLY_UNCAUGHT_EXCEPTION uncaught exception: %1
There was an uncaught exception in the handling of a notify reply
message, either in the message parser, or while trying to extract data
@@ -70,27 +75,27 @@ explicitly. Please file a bug report. Since there was a response,
no more notifies will be sent to this server for this notification
event.
-% NOTIFY_OUT_RETRY_EXCEEDED notify to %1#%2: number of retries (%3) exceeded
+% NOTIFY_OUT_RETRY_EXCEEDED notify to %1: number of retries (%2) exceeded
The maximum number of retries for the notify target has been exceeded.
Either the address of the secondary nameserver is wrong, or it is not
responding.
-% NOTIFY_OUT_SENDING_NOTIFY sending notify to %1#%2
+% NOTIFY_OUT_SENDING_NOTIFY sending notify to %1
A notify message is sent to the secondary nameserver at the given
address.
-% NOTIFY_OUT_SOCKET_ERROR socket error sending notify to %1#%2: %3
+% NOTIFY_OUT_SOCKET_ERROR socket error sending notify to %1: %2
There was a network error while trying to send a notify message to
the given address. The address might be unreachable. The socket
error is printed and should provide more information.
-% NOTIFY_OUT_SOCKET_RECV_ERROR socket error reading notify reply from %1#%2: %3
+% NOTIFY_OUT_SOCKET_RECV_ERROR socket error reading notify reply from %1: %2
There was a network error while trying to read a notify reply
message from the given address. The socket error is printed and should
provide more information.
-% NOTIFY_OUT_TIMEOUT retry notify to %1#%2
-The notify message to the given address (noted as address#port) has
+% NOTIFY_OUT_TIMEOUT retry notify to %1
+The notify message to the given address (noted as address:port) has
timed out, and the message will be resent until the max retry limit
is reached.
diff --git a/src/lib/python/isc/notify/tests/Makefile.am b/src/lib/python/isc/notify/tests/Makefile.am
index 3af5991..d964880 100644
--- a/src/lib/python/isc/notify/tests/Makefile.am
+++ b/src/lib/python/isc/notify/tests/Makefile.am
@@ -11,7 +11,7 @@ EXTRA_DIST += testdata/multisoa.example
# 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/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/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/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
else
# Some systems need the ds path even if not all paths are necessary
LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/datasrc/.libs
diff --git a/src/lib/python/isc/notify/tests/notify_out_test.py b/src/lib/python/isc/notify/tests/notify_out_test.py
index 3b2324d..e2b8d27 100644
--- a/src/lib/python/isc/notify/tests/notify_out_test.py
+++ b/src/lib/python/isc/notify/tests/notify_out_test.py
@@ -25,10 +25,32 @@ from isc.dns import *
TESTDATA_SRCDIR = os.getenv("TESTDATASRCDIR")
+def get_notify_msgdata(zone_name, qid=0):
+ """A helper function to generate a notify response in wire format.
+
+ Parameters:
+ zone_name(isc.dns.Name()) The zone name for the notify. Used as the
+ question name.
+ qid (int): The QID of the response. In most test cases a value of 0 is
+ expected.
+
+ """
+ m = Message(Message.RENDER)
+ m.set_opcode(Opcode.NOTIFY)
+ m.set_rcode(Rcode.NOERROR)
+ m.set_qid(qid)
+ m.set_header_flag(Message.HEADERFLAG_QR)
+ m.add_question(Question(zone_name, RRClass.IN, RRType.SOA))
+
+ renderer = MessageRenderer()
+ m.to_wire(renderer)
+ return renderer.get_data()
+
# our fake socket, where we can read and insert messages
class MockSocket():
def __init__(self):
self._local_sock, self._remote_sock = socket.socketpair()
+ self.__raise_on_recv = False # see set_raise_on_recv()
def connect(self, to):
pass
@@ -44,6 +66,8 @@ class MockSocket():
return self._local_sock.send(data)
def recvfrom(self, length):
+ if self.__raise_on_recv:
+ raise socket.error('fake error')
data = self._local_sock.recv(length)
return (data, None)
@@ -51,6 +75,14 @@ class MockSocket():
def remote_end(self):
return self._remote_sock
+ def set_raise_on_recv(self, on):
+ """A helper to force recvfrom() to raise an exception or cancel it.
+
+ The next call to recvfrom() will result in an exception iff parameter
+ 'on' (bool) is set to True.
+ """
+ self.__raise_on_recv = on
+
# We subclass the ZoneNotifyInfo class we're testing here, only
# to override the create_socket() method.
class MockZoneNotifyInfo(notify_out.ZoneNotifyInfo):
@@ -79,12 +111,12 @@ class TestZoneNotifyInfo(unittest.TestCase):
def test_set_next_notify_target(self):
self.info.notify_slaves.append(('127.0.0.1', 53))
- self.info.notify_slaves.append(('1.1.1.1', 5353))
+ self.info.notify_slaves.append(('192.0.2.1', 5353))
self.info.prepare_notify_out()
self.assertEqual(self.info.get_current_notify_target(), ('127.0.0.1', 53))
self.info.set_next_notify_target()
- self.assertEqual(self.info.get_current_notify_target(), ('1.1.1.1', 5353))
+ self.assertEqual(self.info.get_current_notify_target(), ('192.0.2.1', 5353))
self.info.set_next_notify_target()
self.assertIsNone(self.info.get_current_notify_target())
@@ -105,14 +137,18 @@ class TestNotifyOut(unittest.TestCase):
net_info = self._notify._notify_infos[('example.net.', 'IN')]
net_info.notify_slaves.append(('127.0.0.1', 53))
- net_info.notify_slaves.append(('1.1.1.1', 5353))
+ net_info.notify_slaves.append(('192.0.2.1', 5353))
com_info = self._notify._notify_infos[('example.com.', 'IN')]
- com_info.notify_slaves.append(('1.1.1.1', 5353))
+ com_info.notify_slaves.append(('192.0.2.1', 5353))
com_ch_info = self._notify._notify_infos[('example.com.', 'CH')]
- com_ch_info.notify_slaves.append(('1.1.1.1', 5353))
+ com_ch_info.notify_slaves.append(('192.0.2.1', 5353))
+ # Keep the original library version in case a test case replaces it
+ self.__time_time_orig = notify_out.time.time
def tearDown(self):
self._notify._counters.clear_all()
+ # restore the original time.time() in case it was replaced.
+ notify_out.time.time = self.__time_time_orig
def test_send_notify(self):
notify_out._MAX_NOTIFY_NUM = 2
@@ -221,7 +257,7 @@ class TestNotifyOut(unittest.TestCase):
info = self._notify._notify_infos[('example.net.', 'IN')]
self._notify._notify_next_target(info)
self.assertEqual(0, info.notify_try_num)
- self.assertEqual(info.get_current_notify_target(), ('1.1.1.1', 5353))
+ self.assertEqual(info.get_current_notify_target(), ('192.0.2.1', 5353))
self.assertEqual(2, self._notify.notify_num)
self.assertEqual(1, len(self._notify._waiting_zones))
@@ -328,39 +364,86 @@ class TestNotifyOut(unittest.TestCase):
'zones', 'example.net.', 'notifyoutv4')
def test_zone_notify_handler(self):
- old_send_msg = self._notify._send_notify_message_udp
- def _fake_send_notify_message_udp(va1, va2):
+ sent_addrs = []
+ def _fake_send_notify_message_udp(notify_info, addrinfo):
+ sent_addrs.append(addrinfo)
pass
+ notify_out.time.time = lambda: 42
self._notify._send_notify_message_udp = _fake_send_notify_message_udp
self._notify.send_notify('example.net.')
- self._notify.send_notify('example.com.')
- notify_out._MAX_NOTIFY_NUM = 2
- self._notify.send_notify('example.org.')
example_net_info = self._notify._notify_infos[('example.net.', 'IN')]
- example_net_info.prepare_notify_out()
+ # On timeout, the request will be resent until try_num reaches the max
+ self.assertEqual([], sent_addrs)
example_net_info.notify_try_num = 2
- self._notify._zone_notify_handler(example_net_info, notify_out._EVENT_TIMEOUT)
+ self._notify._zone_notify_handler(example_net_info,
+ notify_out._EVENT_TIMEOUT)
self.assertEqual(3, example_net_info.notify_try_num)
-
- time1 = example_net_info.notify_timeout
- self._notify._zone_notify_handler(example_net_info, notify_out._EVENT_TIMEOUT)
- self.assertEqual(4, example_net_info.notify_try_num)
- self.assertGreater(example_net_info.notify_timeout, time1 + 2) # bigger than 2 seconds
-
+ self.assertEqual([('127.0.0.1', 53)], sent_addrs)
+ # the timeout time will be set to "current time(=42)"+2**(new try_num)
+ self.assertEqual(42 + 2**3, example_net_info.notify_timeout)
+
+ # If try num exceeds max, the next slave will be tried (and then
+ # next zone, but for this test it sufficies to check the former case)
+ example_net_info.notify_try_num = 5
+ self._notify._zone_notify_handler(example_net_info,
+ notify_out._EVENT_TIMEOUT)
+ self.assertEqual(0, example_net_info.notify_try_num) # should be reset
+ self.assertEqual(('192.0.2.1', 5353), example_net_info._notify_current)
+
+ # Possible event is "read" or "timeout".
cur_tgt = example_net_info._notify_current
example_net_info.notify_try_num = notify_out._MAX_NOTIFY_TRY_NUM
- self._notify._zone_notify_handler(example_net_info, notify_out._EVENT_NONE)
- self.assertNotEqual(cur_tgt, example_net_info._notify_current)
+ self.assertRaises(AssertionError, self._notify._zone_notify_handler,
+ example_net_info, notify_out._EVENT_TIMEOUT + 1)
- cur_tgt = example_net_info._notify_current
+ def test_zone_notify_read_handler(self):
+ """Similar to the previous test, but focus on the READ events.
+
+ """
+ sent_addrs = []
+ def _fake_send_notify_message_udp(notify_info, addrinfo):
+ sent_addrs.append(addrinfo)
+ pass
+ self._notify._send_notify_message_udp = _fake_send_notify_message_udp
+ self._notify.send_notify('example.net.')
+
+ example_net_info = self._notify._notify_infos[('example.net.', 'IN')]
example_net_info.create_socket('127.0.0.1')
- # dns message, will result in bad_qid, but what we are testing
- # here is whether handle_notify_reply is called correctly
- example_net_info._sock.remote_end().send(b'\x2f\x18\xa0\x00\x00\x01\x00\x00\x00\x00\x00\x00\x07example\03com\x00\x00\x06\x00\x01')
- self._notify._zone_notify_handler(example_net_info, notify_out._EVENT_READ)
- self.assertNotEqual(cur_tgt, example_net_info._notify_current)
+
+ # A successful case: an expected notify response is received, and
+ # another notify will be sent to the next slave immediately.
+ example_net_info._sock.remote_end().send(
+ get_notify_msgdata(Name('example.net')))
+ self._notify._zone_notify_handler(example_net_info,
+ notify_out._EVENT_READ)
+ self.assertEqual(1, example_net_info.notify_try_num)
+ expected_sent_addrs = [('192.0.2.1', 5353)]
+ self.assertEqual(expected_sent_addrs, sent_addrs)
+ self.assertEqual(('192.0.2.1', 5353), example_net_info._notify_current)
+
+ # response's QID doesn't match. the request will be resent.
+ example_net_info._sock.remote_end().send(
+ get_notify_msgdata(Name('example.net'), qid=1))
+ self._notify._zone_notify_handler(example_net_info,
+ notify_out._EVENT_READ)
+ self.assertEqual(2, example_net_info.notify_try_num)
+ expected_sent_addrs.append(('192.0.2.1', 5353))
+ self.assertEqual(expected_sent_addrs, sent_addrs)
+ self.assertEqual(('192.0.2.1', 5353), example_net_info._notify_current)
+
+ # emulate exception from socket.recvfrom(). It will have the same
+ # effect as a bad response.
+ example_net_info._sock.set_raise_on_recv(True)
+ example_net_info._sock.remote_end().send(
+ get_notify_msgdata(Name('example.net')))
+ self._notify._zone_notify_handler(example_net_info,
+ notify_out._EVENT_READ)
+ self.assertEqual(3, example_net_info.notify_try_num)
+ expected_sent_addrs.append(('192.0.2.1', 5353))
+ self.assertEqual(expected_sent_addrs, sent_addrs)
+ self.assertEqual(('192.0.2.1', 5353), example_net_info._notify_current)
def test_get_notify_slaves_from_ns(self):
records = self._notify._get_notify_slaves_from_ns(Name('example.net.'),
diff --git a/src/lib/python/isc/server_common/dns_tcp.py b/src/lib/python/isc/server_common/dns_tcp.py
index 9ce94fe..2cbd47a 100644
--- a/src/lib/python/isc/server_common/dns_tcp.py
+++ b/src/lib/python/isc/server_common/dns_tcp.py
@@ -91,7 +91,7 @@ class DNSTCPSendBuffer:
A Python binary object that corresponds to a part of the TCP
DNS message data starting at the specified position. It may
or may not contain all remaining data from that position.
- If the given position is beyond the end of the enrire data,
+ If the given position is beyond the end of the entire data,
None will be returned.
'''
@@ -267,7 +267,7 @@ class DNSTCPContext:
when this object is deallocated, but Python seems to expect socket
objects should be explicitly closed before deallocation. So it's
generally advisable for the user of this object to call this method
- explictily when it doesn't need the context.
+ explicitly when it doesn't need the context.
This method can be called more than once or can be called after
other I/O related methods have returned CLOSED; it's compatible
@@ -277,4 +277,4 @@ class DNSTCPContext:
if self.__sock is None:
return
self.__sock.close()
- self.__sock = None # prevent furhter operation
+ self.__sock = None # prevent further operation
diff --git a/src/lib/python/isc/server_common/tests/Makefile.am b/src/lib/python/isc/server_common/tests/Makefile.am
index 82cd854..fff57d6 100644
--- a/src/lib/python/isc/server_common/tests/Makefile.am
+++ b/src/lib/python/isc/server_common/tests/Makefile.am
@@ -6,7 +6,7 @@ EXTRA_DIST = $(PYTESTS)
# required by loadable python modules.
LIBRARY_PATH_PLACEHOLDER =
if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/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/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/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/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
endif
# test using command-line arguments, so use check-local target instead of TESTS
diff --git a/src/lib/python/isc/statistics/counters.py b/src/lib/python/isc/statistics/counters.py
index 8138ab6..279c14b 100644
--- a/src/lib/python/isc/statistics/counters.py
+++ b/src/lib/python/isc/statistics/counters.py
@@ -217,8 +217,8 @@ class Counters():
zones/example.com./ixfrreqv6
zones/example.com./xfrsuccess
zones/example.com./xfrfail
- zones/example.com./time_to_ixfr
- zones/example.com./time_to_axfr
+ zones/example.com./last_ixfr_duration
+ zones/example.com./last_axfr_duration
ixfr_running
axfr_running
socket/unixdomain/open
@@ -327,7 +327,9 @@ class Counters():
def start_timer(self, *args):
"""Starts a timer which is identified by args and keeps it
running until stop_timer() is called. It acquires a lock to
- support multi-threaded use."""
+ support multi-threaded use. If the specified timer is already
+ started but not yet stopped, the last start time is
+ overwritten."""
identifier = _concat(*args)
with self._rlock:
if self._disabled: return
diff --git a/src/lib/python/isc/statistics/tests/Makefile.am b/src/lib/python/isc/statistics/tests/Makefile.am
index 8dcc296..c38e0f5 100644
--- a/src/lib/python/isc/statistics/tests/Makefile.am
+++ b/src/lib/python/isc/statistics/tests/Makefile.am
@@ -9,7 +9,7 @@ EXTRA_DIST += testdata/test_spec3.spec
# 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/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/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/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
else
# Some systems need the ds path even if not all paths are necessary
LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/datasrc/.libs
diff --git a/src/lib/python/isc/statistics/tests/counters_test.py b/src/lib/python/isc/statistics/tests/counters_test.py
index 395a959..5567dda 100644
--- a/src/lib/python/isc/statistics/tests/counters_test.py
+++ b/src/lib/python/isc/statistics/tests/counters_test.py
@@ -197,7 +197,7 @@ class BaseTestCounters():
# for per-zone counters
for name in self.counters._zones_item_list:
args = (self._perzone_prefix, TEST_ZONE_NAME_STR, name)
- if name.find('time_to_') == 0:
+ if name.find('last_') == 0 and name.endswith('_duration'):
self.counters.start_timer(*args)
self.counters.stop_timer(*args)
self.assertGreaterEqual(self.counters.get(*args), 0.0)
@@ -278,7 +278,7 @@ class BaseTestCounters():
# setting all counters to zero
for name in self.counters._zones_item_list:
args = (self._perzone_prefix, TEST_ZONE_NAME_STR, name)
- if name.find('time_to_') == 0:
+ if name.find('last_') == 0 and name.endswith('_duration'):
zero = 0.0
else:
zero = 0
diff --git a/src/lib/python/isc/statistics/tests/testdata/test_spec3.spec b/src/lib/python/isc/statistics/tests/testdata/test_spec3.spec
index c97a09a..6c06f69 100644
--- a/src/lib/python/isc/statistics/tests/testdata/test_spec3.spec
+++ b/src/lib/python/isc/statistics/tests/testdata/test_spec3.spec
@@ -19,8 +19,8 @@
"ixfrreqv6": 0,
"xfrsuccess": 0,
"xfrfail": 0,
- "time_to_ixfr": 0.0,
- "time_to_axfr": 0.0
+ "last_ixfr_duration": 0.0,
+ "last_axfr_duration": 0.0
}
},
"item_title": "Zone names",
@@ -98,20 +98,20 @@
"item_description": "Number of zone transfer requests failed"
},
{
- "item_name": "time_to_ixfr",
+ "item_name": "last_ixfr_duration",
"item_type": "real",
"item_optional": false,
"item_default": 0.0,
- "item_title": "Time to IXFR",
- "item_description": "Elapsed time in seconds to do the last IXFR"
+ "item_title": "Last IXFR duration",
+ "item_description": "Duration of the last IXFR. 0.0 means no successful IXFR done."
},
{
- "item_name": "time_to_axfr",
+ "item_name": "last_axfr_duration",
"item_type": "real",
"item_optional": false,
"item_default": 0.0,
- "item_title": "Time to AXFR",
- "item_description": "Elapsed time in seconds to do the last AXFR"
+ "item_title": "Last AXFR duration",
+ "item_description": "Duration of the last AXFR. 0.0 means no successful AXFR done."
}
]
}
diff --git a/src/lib/python/isc/sysinfo/sysinfo.py b/src/lib/python/isc/sysinfo/sysinfo.py
index 099ac89..7bf0669 100644
--- a/src/lib/python/isc/sysinfo/sysinfo.py
+++ b/src/lib/python/isc/sysinfo/sysinfo.py
@@ -415,7 +415,7 @@ class SysInfoFreeBSD(SysInfoFreeBSDOSX):
self._platform_is_smp = True # the value doesn't matter
except subprocess.CalledProcessError:
# if this variable isn't defined we should see this exception.
- # intepret it as an indication of non-SMP kernel.
+ # interpret it as an indication of non-SMP kernel.
self._platform_is_smp = False
except OSError:
pass
diff --git a/src/lib/python/isc/sysinfo/tests/Makefile.am b/src/lib/python/isc/sysinfo/tests/Makefile.am
index 9759c77..c0b3f10 100644
--- a/src/lib/python/isc/sysinfo/tests/Makefile.am
+++ b/src/lib/python/isc/sysinfo/tests/Makefile.am
@@ -5,7 +5,7 @@ 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/acl/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER = $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/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
# test using command-line arguments, so use check-local target instead of TESTS
diff --git a/src/lib/python/isc/util/Makefile.am b/src/lib/python/isc/util/Makefile.am
index 3eaaa12..eaeedd8 100644
--- a/src/lib/python/isc/util/Makefile.am
+++ b/src/lib/python/isc/util/Makefile.am
@@ -1,6 +1,7 @@
SUBDIRS = . cio tests
-python_PYTHON = __init__.py process.py socketserver_mixin.py file.py
+python_PYTHON = __init__.py process.py socketserver_mixin.py file.py
+python_PYTHON += address_formatter.py
pythondir = $(pyexecdir)/isc/util
diff --git a/src/lib/python/isc/util/address_formatter.py b/src/lib/python/isc/util/address_formatter.py
new file mode 100644
index 0000000..35c1e00
--- /dev/null
+++ b/src/lib/python/isc/util/address_formatter.py
@@ -0,0 +1,82 @@
+# 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 socket
+
+class AddressFormatter:
+ """
+ A utility class to convert an IP address with a port number to a
+ string.
+
+ It takes a tuple (or list) containing and address string and a port
+ number, and optionally a family.
+
+ If the family is IPv4, the __str__ output will be
+ <address>:<port>
+ If the family is IPv6, the __str__ output will be
+ [<address>]:<port>
+
+ If family is not given, the __str__ method will try to figure it out
+ itself, by checking for the ':' character in the address string.
+
+ This class is designed to delay the conversion until it's explicitly
+ requested, so the conversion doesn't happen if the corresponding log
+ message is suppressed because of its log level (which is often the case
+ for debug messages).
+
+ Note: this optimization comes with the cost of instantiating the
+ formatter object itself. It's not really clear which overhead is
+ heavier, and we may conclude it's actually better to just generate
+ the strings unconditionally. Alternatively, we can make the stored
+ address of this object replaceable so that this object can be reused.
+ Right now this is an open issue.
+
+ See also ClientFormatter in the ddns.logger code, which does something
+ similar but based on other criteria (and optional extra value).
+ """
+ def __init__(self, addr, family=None):
+ self.__addr = addr
+ self.__family = family
+
+ def __addr_v4(self):
+ return self.__addr[0] + ':' + str(self.__addr[1])
+
+ def __addr_v6(self):
+ return '[' + self.__addr[0] + ']:' + str(self.__addr[1])
+
+ def __format_addr(self):
+ # Some basic sanity checks, should we leave this out for efficiency?
+ # (especially strings produce unexpected results)
+ if isinstance(self.__addr, str) or\
+ not hasattr(self.__addr, "__getitem__"):
+ raise ValueError("Address must be a list or tuple")
+
+ if self.__family is not None:
+ if self.__family == socket.AF_INET6:
+ return self.__addr_v6()
+ elif self.__family == socket.AF_INET:
+ return self.__addr_v4()
+ else:
+ raise ValueError("Unknown address family: " +
+ str(self.__family))
+ else:
+ if self.__addr[0].find(':') >= 0:
+ return self.__addr_v6()
+ else:
+ return self.__addr_v4()
+
+ def __str__(self):
+ return self.__format_addr()
+
diff --git a/src/lib/python/isc/util/cio/tests/Makefile.am b/src/lib/python/isc/util/cio/tests/Makefile.am
index 3429009..0330ef6 100644
--- a/src/lib/python/isc/util/cio/tests/Makefile.am
+++ b/src/lib/python/isc/util/cio/tests/Makefile.am
@@ -6,7 +6,7 @@ EXTRA_DIST = $(PYTESTS)
# required by loadable python modules.
LIBRARY_PATH_PLACEHOLDER =
if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/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/io/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/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/util/io/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
endif
# test using command-line arguments, so use check-local target instead of TESTS
diff --git a/src/lib/python/isc/util/socketserver_mixin.py b/src/lib/python/isc/util/socketserver_mixin.py
index e954fe1..da04b6f 100644
--- a/src/lib/python/isc/util/socketserver_mixin.py
+++ b/src/lib/python/isc/util/socketserver_mixin.py
@@ -39,7 +39,7 @@ class NoPollMixIn:
socketserver.BaseServer or some derived classes of it, and it must
be placed before the corresponding socketserver class. In
addition, the constructor of this mix-in must be called
- explicitely in the derived class. For example, a basic TCP server
+ explicitly in the derived class. For example, a basic TCP server
without the problem of polling is created as follows:
class MyServer(NoPollMixIn, socketserver.TCPServer):
diff --git a/src/lib/python/isc/util/tests/Makefile.am b/src/lib/python/isc/util/tests/Makefile.am
index 3b882b4..4df6947 100644
--- a/src/lib/python/isc/util/tests/Makefile.am
+++ b/src/lib/python/isc/util/tests/Makefile.am
@@ -1,18 +1,19 @@
PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
PYTESTS = process_test.py socketserver_mixin_test.py file_test.py
+PYTESTS += address_formatter_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/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/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/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
endif
# test using command-line arguments, so use check-local target instead of TESTS
check-local:
if ENABLE_PYTHON_COVERAGE
- touch $(abs_top_srcdir)/.coverage
+ touch $(abs_top_srcdir)/.coverage
rm -f .coverage
${LN_S} $(abs_top_srcdir)/.coverage .coverage
endif
diff --git a/src/lib/python/isc/util/tests/address_formatter_test.py b/src/lib/python/isc/util/tests/address_formatter_test.py
new file mode 100644
index 0000000..295b7c3
--- /dev/null
+++ b/src/lib/python/isc/util/tests/address_formatter_test.py
@@ -0,0 +1,68 @@
+# 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 socket
+import unittest
+from isc.util.address_formatter import AddressFormatter
+
+class AddressFormatterTest(unittest.TestCase):
+ def test_v4(self):
+ self.assertEqual("127.0.0.1:123",
+ str(AddressFormatter(("127.0.0.1", 123))))
+ self.assertEqual("127.0.0.1:123",
+ str(AddressFormatter(("127.0.0.1", 123), None)))
+ self.assertEqual("192.0.2.1:1",
+ str(AddressFormatter(("192.0.2.1", 1))))
+
+ def test_v6(self):
+ self.assertEqual("[::1]:123",
+ str(AddressFormatter(("::1", 123))));
+ self.assertEqual("[::1]:123",
+ str(AddressFormatter(("::1", 123), None)))
+ self.assertEqual("[2001:db8::]:1",
+ str(AddressFormatter(("2001:db8::", 1))))
+
+ def test_force_family_good(self):
+ self.assertEqual("127.0.0.1:123",
+ str(AddressFormatter(("127.0.0.1", 123),
+ socket.AF_INET)))
+ self.assertEqual("[::1]:123",
+ str(AddressFormatter(("::1", 123),
+ socket.AF_INET6)))
+
+ def test_force_family_bad(self):
+ """
+ These results are 'bad' as in they don't return the value as
+ specified by our guidelines, since the internal check is skipped if
+ the family is given
+ """
+ self.assertEqual("[127.0.0.1]:123",
+ str(AddressFormatter(("127.0.0.1", 123),
+ socket.AF_INET6)))
+ self.assertEqual("::1:123",
+ str(AddressFormatter(("::1", 123),
+ socket.AF_INET)))
+
+ def test_bad_values(self):
+ self.assertRaises(ValueError, str, AddressFormatter("string"))
+ self.assertRaises(ValueError, str, AddressFormatter(None))
+ self.assertRaises(ValueError, str, AddressFormatter(1))
+ self.assertRaises(ValueError, str, AddressFormatter(("::1", 123), 1))
+ self.assertRaises(ValueError, str, AddressFormatter(("::1", 123), 1))
+
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/src/lib/python/isc/xfrin/diff.py b/src/lib/python/isc/xfrin/diff.py
index 4c97267..31f5f25 100644
--- a/src/lib/python/isc/xfrin/diff.py
+++ b/src/lib/python/isc/xfrin/diff.py
@@ -31,7 +31,7 @@ from isc.log_messages.libxfrin_messages import *
class NoSuchZone(Exception):
"""
- This is raised if a diff for non-existant zone is being created.
+ This is raised if a diff for non-existent zone is being created.
"""
pass
diff --git a/src/lib/python/isc/xfrin/tests/Makefile.am b/src/lib/python/isc/xfrin/tests/Makefile.am
index 459efc3..89769d0 100644
--- a/src/lib/python/isc/xfrin/tests/Makefile.am
+++ b/src/lib/python/isc/xfrin/tests/Makefile.am
@@ -6,7 +6,7 @@ EXTRA_DIST = $(PYTESTS)
# required by loadable python modules.
LIBRARY_PATH_PLACEHOLDER =
if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/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/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/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/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
endif
# test using command-line arguments, so use check-local target instead of TESTS
diff --git a/src/lib/resolve/recursive_query.cc b/src/lib/resolve/recursive_query.cc
index 8d4ae58..55dc4da 100644
--- a/src/lib/resolve/recursive_query.cc
+++ b/src/lib/resolve/recursive_query.cc
@@ -930,7 +930,7 @@ private:
asio::deadline_timer client_timer;
asio::deadline_timer lookup_timer;
- // Make FowardQuery deletes itself safely. for more information see
+ // Make ForwardQuery deletes itself safely. for more information see
// the comments of outstanding_events in RunningQuery.
size_t outstanding_events_;
diff --git a/src/lib/resolve/tests/recursive_query_unittest.cc b/src/lib/resolve/tests/recursive_query_unittest.cc
index e5e46e1..acbbb03 100644
--- a/src/lib/resolve/tests/recursive_query_unittest.cc
+++ b/src/lib/resolve/tests/recursive_query_unittest.cc
@@ -971,7 +971,7 @@ TEST_F(RecursiveQueryTest, CachedNS) {
// point and ask NSAS for it. NSAS will in turn ask resolver for NS record
// of the delegation point. We then pick it up from the fake resolver
// and check it is the correct one. This checks the delegation point
- // travels safely trough the whole path there (it would be enough to check
+ // travels safely through the whole path there (it would be enough to check
// it up to NSAS, but replacing NSAS is more complicated, so we just
// include in the test as well for simplicity).
diff --git a/src/lib/server_common/client.cc b/src/lib/server_common/client.cc
index e6383d6..3cc2a07 100644
--- a/src/lib/server_common/client.cc
+++ b/src/lib/server_common/client.cc
@@ -57,8 +57,14 @@ Client::getRequestSourceIPAddress() const {
std::string
Client::toText() const {
std::stringstream ss;
- ss << impl_->request_.getRemoteEndpoint().getAddress().toText()
- << '#' << impl_->request_.getRemoteEndpoint().getPort();
+ const asiolink::IOAddress& addr =
+ impl_->request_.getRemoteEndpoint().getAddress();
+ if (addr.isV6()) {
+ ss << '[' << addr.toText() << ']';
+ } else {
+ ss << addr.toText();
+ }
+ ss << ':' << impl_->request_.getRemoteEndpoint().getPort();
return (ss.str());
}
diff --git a/src/lib/server_common/client.h b/src/lib/server_common/client.h
index 912e7a6..4e344f0 100644
--- a/src/lib/server_common/client.h
+++ b/src/lib/server_common/client.h
@@ -119,7 +119,7 @@ public:
///
/// (In the initial implementation) the format of the resulting string
/// is as follows:
- /// \code <IP address>#<port>
+ /// \code <IP address>:<port>
/// \endcode
/// The IP address is the textual representation of the client's IP
/// address, which is the source address of the request the client has
diff --git a/src/lib/server_common/portconfig.cc b/src/lib/server_common/portconfig.cc
index b214ef5..7bc6876 100644
--- a/src/lib/server_common/portconfig.cc
+++ b/src/lib/server_common/portconfig.cc
@@ -117,8 +117,14 @@ installListenAddresses(const AddressList& new_addresses,
try {
LOG_DEBUG(logger, DBG_TRACE_BASIC, SRVCOMM_SET_LISTEN);
BOOST_FOREACH(const AddressPair& addr, new_addresses) {
+ string addr_str;
+ if (addr.first.find(':') != string::npos) {
+ addr_str = "[" + addr.first + "]";
+ } else {
+ addr_str = addr.first;
+ }
LOG_DEBUG(logger, DBG_TRACE_VALUES, SRVCOMM_ADDRESS_VALUE).
- arg(addr.first).arg(addr.second);
+ arg(addr_str).arg(addr.second);
}
setAddresses(service, new_addresses, server_options);
address_store = new_addresses;
@@ -129,7 +135,7 @@ installListenAddresses(const AddressList& new_addresses,
* set successuflly or none of them will be used. whether this
* behavior is user desired, maybe we need revisited it later. And
* if address setting is more smarter, it should check whether some
- * part of the new address already in used to avoid interuption the
+ * part of the new address already in used to avoid interrupting the
* service.
*
* If the address setting still failed, we can live with it, since
diff --git a/src/lib/server_common/server_common_messages.mes b/src/lib/server_common/server_common_messages.mes
index 22ce0f3..ba235c9 100644
--- a/src/lib/server_common/server_common_messages.mes
+++ b/src/lib/server_common/server_common_messages.mes
@@ -72,7 +72,7 @@ which it is running. The server will continue running to allow
reconfiguration, but will not be listening on any address or port until
an administrator does so.
-% SRVCOMM_ADDRESS_VALUE address to set: %1#%2
+% SRVCOMM_ADDRESS_VALUE address to set: %1:%2
Debug message. This lists one address and port value of the set of
addresses we are going to listen on (eg. there will be one log message
per pair). This appears only after SRVCOMM_SET_LISTEN, but might
diff --git a/src/lib/server_common/tests/client_unittest.cc b/src/lib/server_common/tests/client_unittest.cc
index 08c24ba..14f6fbc 100644
--- a/src/lib/server_common/tests/client_unittest.cc
+++ b/src/lib/server_common/tests/client_unittest.cc
@@ -92,8 +92,8 @@ TEST_F(ClientTest, constructIPv6) {
}
TEST_F(ClientTest, toText) {
- EXPECT_EQ("192.0.2.1#53214", client4->toText());
- EXPECT_EQ("2001:db8::1#53216", client6->toText());
+ EXPECT_EQ("192.0.2.1:53214", client4->toText());
+ EXPECT_EQ("[2001:db8::1]:53216", client6->toText());
}
// test operator<<. We simply confirm it appends the result of toText().
diff --git a/src/lib/server_common/tests/socket_requestor_test.cc b/src/lib/server_common/tests/socket_requestor_test.cc
index 8da545e..b5f6dfc 100644
--- a/src/lib/server_common/tests/socket_requestor_test.cc
+++ b/src/lib/server_common/tests/socket_requestor_test.cc
@@ -243,7 +243,7 @@ TEST_F(SocketRequestorTest, testBadRequestAnswers) {
const std::string max_len(sizeof(sock_un.sun_path) - 1, 'x');
addAnswer("foo", max_len);
// The failure should NOT contain 'too long'
- // (explicitly checking for existance of nonexistence of 'too long',
+ // (explicitly checking for existence of nonexistence of 'too long',
// as opposed to the actual error, since 'too long' is a value we set).
try {
doRequest();
diff --git a/src/lib/testutils/mockups.h b/src/lib/testutils/mockups.h
index 8ba2287..58b39ee 100644
--- a/src/lib/testutils/mockups.h
+++ b/src/lib/testutils/mockups.h
@@ -40,15 +40,16 @@ public:
MockSession() :
// by default we return a simple "success" message.
msg_(isc::data::Element::fromJSON("{\"result\": [0, \"SUCCESS\"]}")),
- send_ok_(true), receive_ok_(true)
+ send_ok_(true), receive_ok_(true), answer_wanted_(false)
{}
virtual void establish(const char*) {}
virtual void disconnect() {}
- virtual int group_sendmsg(isc::data::ConstElementPtr msg, std::string group,
- std::string, std::string, bool)
+ virtual int group_sendmsg(isc::data::ConstElementPtr msg,
+ std::string group,
+ std::string, std::string, bool want_answer)
{
if (!send_ok_) {
isc_throw(isc::cc::SessionError,
@@ -57,6 +58,7 @@ public:
sent_msg_ = msg;
msg_dest_ = group;
+ answer_wanted_ = want_answer;
return (0);
}
@@ -93,8 +95,12 @@ public:
void disableSend() { send_ok_ = false; }
void disableReceive() { receive_ok_ = false; }
- isc::data::ConstElementPtr getSentMessage() { return (sent_msg_); }
- std::string getMessageDest() { return (msg_dest_); }
+ isc::data::ConstElementPtr getSentMessage() const { return (sent_msg_); }
+ std::string getMessageDest() const { return (msg_dest_); }
+
+ /// \brief Return the value of want_answer parameter of the previous call
+ /// to group_sendmsg().
+ bool wasAnswerWanted() const { return (answer_wanted_); }
private:
isc::data::ConstElementPtr sent_msg_;
@@ -102,6 +108,7 @@ private:
isc::data::ConstElementPtr msg_;
bool send_ok_;
bool receive_ok_;
+ bool answer_wanted_;
};
// This mock object does nothing except for recording passed parameters
diff --git a/src/lib/util/encode/base_n.cc b/src/lib/util/encode/base_n.cc
index f5f930a..8c813c3 100644
--- a/src/lib/util/encode/base_n.cc
+++ b/src/lib/util/encode/base_n.cc
@@ -40,6 +40,15 @@ namespace isc {
namespace util {
namespace encode {
+// Some versions of clang cannot handle exceptions in unnamed namespaces
+// so this exception is defined in an 'internal' namespace
+namespace clang_unnamed_namespace_workaround {
+// An internally caught exception to unify a few possible cases of the same
+// error.
+class IncompleteBaseInput : public std::exception {
+};
+} // end namespace internal
+
// In the following anonymous namespace, we provide a generic framework
// to encode/decode baseN format. We use the following tools:
// - boost base64_from_binary/binary_from_base64: provide mapping table for
@@ -144,11 +153,6 @@ private:
bool in_pad_;
};
-// An internally caught exception to unify a few possible cases of the same
-// error.
-class IncompleteBaseInput : public std::exception {
-};
-
// DecodeNormalizer is an input iterator intended to be used as a filter
// between the encoded baseX stream and binary_from_baseXX.
// A DecodeNormalizer object is configured with three string iterators
@@ -217,7 +221,9 @@ public:
// but in that case we need to catch incomplete baseX input in
// a different way. It's done via char_count_ and after the
// completion of decoding.
- throw IncompleteBaseInput(); // throw this now and convert it
+
+ // throw this now and convert it
+ throw clang_unnamed_namespace_workaround::IncompleteBaseInput();
}
if (*base_ == BASE_PADDING_CHAR) {
// Padding can only happen at the end of the input string. We can
@@ -378,9 +384,10 @@ BaseNTransformer<BitsPerChunk, BaseZeroCode, Encoder, Decoder>::decode(
// a multiple of 8; otherwise the decoder reaches the end of input
// with some incomplete bits of data, which is invalid.
if (((char_count * BitsPerChunk) % 8) != 0) {
- throw IncompleteBaseInput(); // catch this immediately below
+ // catch this immediately below
+ throw clang_unnamed_namespace_workaround::IncompleteBaseInput();
}
- } catch (const IncompleteBaseInput&) {
+ } catch (const clang_unnamed_namespace_workaround::IncompleteBaseInput&) {
// we unify error handling for incomplete input here.
isc_throw(BadValue, "Incomplete input for " << algorithm
<< ": " << input);
diff --git a/src/lib/util/filename.h b/src/lib/util/filename.h
index a4ba47c..a265396 100644
--- a/src/lib/util/filename.h
+++ b/src/lib/util/filename.h
@@ -41,10 +41,10 @@ namespace util {
/// directory specification. Unless this class becomes more widely-used on
/// Windows, there is no point in adding redundant code.
///
-/// Name - everthing from the character after the last "/" up to but not
+/// Name - everything from the character after the last "/" up to but not
/// including the last ".".
///
-/// Extension - everthing from the right-most "." (after the right-most "/") to
+/// Extension - everything from the right-most "." (after the right-most "/") to
/// the end of the string. If there is no "." after the last "/", there is
/// no file extension.
///
diff --git a/src/lib/util/memory_segment.h b/src/lib/util/memory_segment.h
index e62c9df..a613fd9 100644
--- a/src/lib/util/memory_segment.h
+++ b/src/lib/util/memory_segment.h
@@ -17,6 +17,8 @@
#include <exceptions/exceptions.h>
+#include <utility>
+
#include <stdlib.h>
namespace isc {
@@ -116,6 +118,8 @@ public:
/// requested storage.
/// \throw MemorySegmentGrown The memory segment doesn't have sufficient
/// space for the requested size and has grown internally.
+ /// \throw MemorySegmentError An attempt was made to allocate
+ /// storage on a read-only memory segment.
///
/// \param size The size of the memory requested in bytes.
/// \return Returns pointer to the memory allocated.
@@ -153,7 +157,7 @@ public:
/// \return Returns <code>true</code> if all allocated memory (including
/// names associated by memory addresses by \c setNamedAddress()) was
/// deallocated, <code>false</code> otherwise.
- virtual bool allMemoryDeallocated() const = 0;
+ virtual bool allMemoryDeallocated() = 0;
/// \brief Associate specified address in the segment with a given name.
///
@@ -176,7 +180,8 @@ public:
/// as \c addr even if it wouldn't be considered to "belong to" the
/// segment in its normal sense; it can be used to indicate that memory
/// has not been allocated for the specified name. A subsequent call
- /// to \c getNamedAddress() will return NULL for that name.
+ /// to \c getNamedAddress() will return NamedAddressResult(true, NULL)
+ /// for that name.
///
/// \note Naming an address is intentionally separated from allocation
/// so that, for example, one module of a program can name a memory
@@ -228,6 +233,9 @@ public:
return (setNamedAddressImpl(name, addr));
}
+ /// \brief Type definition for result returned by getNamedAddress()
+ typedef std::pair<bool, void*> NamedAddressResult;
+
/// \brief Return the address in the segment that has the given name.
///
/// This method returns the memory address in the segment corresponding
@@ -245,8 +253,10 @@ public:
///
/// \param name A C string of which the segment memory address is to be
/// returned. Must not be NULL.
- /// \return The address associated with the name, or NULL if not found.
- void* getNamedAddress(const char* name) {
+ /// \return An std::pair containing a bool (set to true if the name
+ /// was found, or false otherwise) and the address associated with
+ /// the name (which is undefined if the name was not found).
+ NamedAddressResult getNamedAddress(const char* name) const {
// This public method implements common validation. The actual
// work specific to the derived segment is delegated to the
// corresponding protected method.
@@ -288,7 +298,7 @@ protected:
virtual bool setNamedAddressImpl(const char* name, void* addr) = 0;
/// \brief Implementation of getNamedAddress beyond common validation.
- virtual void* getNamedAddressImpl(const char* name) = 0;
+ virtual NamedAddressResult getNamedAddressImpl(const char* name) const = 0;
/// \brief Implementation of clearNamedAddress beyond common validation.
virtual bool clearNamedAddressImpl(const char* name) = 0;
diff --git a/src/lib/util/memory_segment_local.cc b/src/lib/util/memory_segment_local.cc
index 81548fd..ec6ee66 100644
--- a/src/lib/util/memory_segment_local.cc
+++ b/src/lib/util/memory_segment_local.cc
@@ -47,17 +47,18 @@ MemorySegmentLocal::deallocate(void* ptr, size_t size) {
}
bool
-MemorySegmentLocal::allMemoryDeallocated() const {
+MemorySegmentLocal::allMemoryDeallocated() {
return (allocated_size_ == 0 && named_addrs_.empty());
}
-void*
-MemorySegmentLocal::getNamedAddressImpl(const char* name) {
- std::map<std::string, void*>::iterator found = named_addrs_.find(name);
+MemorySegment::NamedAddressResult
+MemorySegmentLocal::getNamedAddressImpl(const char* name) const {
+ std::map<std::string, void*>::const_iterator found =
+ named_addrs_.find(name);
if (found != named_addrs_.end()) {
- return (found->second);
+ return (NamedAddressResult(true, found->second));
}
- return (0);
+ return (NamedAddressResult(false, NULL));
}
bool
diff --git a/src/lib/util/memory_segment_local.h b/src/lib/util/memory_segment_local.h
index 1db55a0..d3e556a 100644
--- a/src/lib/util/memory_segment_local.h
+++ b/src/lib/util/memory_segment_local.h
@@ -64,13 +64,13 @@ public:
///
/// \return Returns <code>true</code> if all allocated memory was
/// deallocated, <code>false</code> otherwise.
- virtual bool allMemoryDeallocated() const;
+ virtual bool allMemoryDeallocated();
/// \brief Local segment version of getNamedAddress.
///
/// There's a small chance this method could throw std::bad_alloc.
/// It should be considered a fatal error.
- virtual void* getNamedAddressImpl(const char* name);
+ virtual NamedAddressResult getNamedAddressImpl(const char* name) const;
/// \brief Local segment version of setNamedAddress.
///
diff --git a/src/lib/util/memory_segment_mapped.cc b/src/lib/util/memory_segment_mapped.cc
index b74fbd4..8c76e74 100644
--- a/src/lib/util/memory_segment_mapped.cc
+++ b/src/lib/util/memory_segment_mapped.cc
@@ -44,6 +44,15 @@ using boost::interprocess::offset_ptr;
namespace isc {
namespace util {
+
+namespace { // unnamed namespace
+
+const char* const RESERVED_NAMED_ADDRESS_STORAGE_NAME =
+ "_RESERVED_NAMED_ADDRESS_STORAGE";
+
+} // end of unnamed namespace
+
+
// Definition of class static constant so it can be referenced by address
// or reference.
const size_t MemorySegmentMapped::INITIAL_SIZE;
@@ -98,6 +107,7 @@ struct MemorySegmentMapped::Impl {
// confirm there's no other user and there won't either.
lock_.reset(new boost::interprocess::file_lock(filename.c_str()));
checkWriter();
+ reserveMemory();
}
// Constructor for open-or-write (and read-write) mode
@@ -108,6 +118,7 @@ struct MemorySegmentMapped::Impl {
lock_(new boost::interprocess::file_lock(filename.c_str()))
{
checkWriter();
+ reserveMemory();
}
// Constructor for existing segment, either read-only or read-write
@@ -123,6 +134,37 @@ struct MemorySegmentMapped::Impl {
} else {
checkWriter();
}
+ reserveMemory();
+ }
+
+ void reserveMemory() {
+ if (!read_only_) {
+ // Reserve a named address for use during
+ // setNamedAddress(). Though this will almost always succeed
+ // on the first try during construction, it may require
+ // multiple attempts later during a call from
+ // allMemoryDeallocated() when the segment has been in use
+ // for a while.
+ while (true) {
+ const offset_ptr<void>* reserved_storage =
+ base_sgmt_->find_or_construct<offset_ptr<void> >(
+ RESERVED_NAMED_ADDRESS_STORAGE_NAME, std::nothrow)();
+
+ if (reserved_storage) {
+ break;
+ }
+
+ growSegment();
+ }
+ }
+ }
+
+ void freeReservedMemory() {
+ if (!read_only_) {
+ const bool deleted = base_sgmt_->destroy<offset_ptr<void> >
+ (RESERVED_NAMED_ADDRESS_STORAGE_NAME);
+ assert(deleted);
+ }
}
// Internal helper to grow the underlying mapped segment.
@@ -233,6 +275,7 @@ MemorySegmentMapped::MemorySegmentMapped(const std::string& filename,
MemorySegmentMapped::~MemorySegmentMapped() {
if (impl_->base_sgmt_ && !impl_->read_only_) {
+ impl_->freeReservedMemory();
impl_->base_sgmt_->flush(); // note: this is exception free
}
delete impl_;
@@ -281,18 +324,22 @@ MemorySegmentMapped::deallocate(void* ptr, size_t) {
}
bool
-MemorySegmentMapped::allMemoryDeallocated() const {
- return (impl_->base_sgmt_->all_memory_deallocated());
+MemorySegmentMapped::allMemoryDeallocated() {
+ impl_->freeReservedMemory();
+ const bool result = impl_->base_sgmt_->all_memory_deallocated();
+ impl_->reserveMemory();
+
+ return (result);
}
-void*
-MemorySegmentMapped::getNamedAddressImpl(const char* name) {
+MemorySegment::NamedAddressResult
+MemorySegmentMapped::getNamedAddressImpl(const char* name) const {
offset_ptr<void>* storage =
impl_->base_sgmt_->find<offset_ptr<void> >(name).first;
if (storage) {
- return (storage->get());
+ return (NamedAddressResult(true, storage->get()));
}
- return (NULL);
+ return (NamedAddressResult(false, NULL));
}
bool
@@ -305,13 +352,27 @@ MemorySegmentMapped::setNamedAddressImpl(const char* name, void* addr) {
isc_throw(MemorySegmentError, "address is out of segment: " << addr);
}
+ // Temporarily save the passed addr into pre-allocated offset_ptr in
+ // case there are any relocations caused by allocations.
+ offset_ptr<void>* reserved_storage =
+ impl_->base_sgmt_->find<offset_ptr<void> >(
+ RESERVED_NAMED_ADDRESS_STORAGE_NAME).first;
+ assert(reserved_storage);
+ *reserved_storage = addr;
+
bool grown = false;
while (true) {
offset_ptr<void>* storage =
impl_->base_sgmt_->find_or_construct<offset_ptr<void> >(
name, std::nothrow)();
if (storage) {
- *storage = addr;
+ // Move the address from saved offset_ptr into the
+ // newly-allocated storage.
+ reserved_storage =
+ impl_->base_sgmt_->find<offset_ptr<void> >(
+ RESERVED_NAMED_ADDRESS_STORAGE_NAME).first;
+ assert(reserved_storage);
+ *storage = *reserved_storage;
return (grown);
}
diff --git a/src/lib/util/memory_segment_mapped.h b/src/lib/util/memory_segment_mapped.h
index c6c79a1..f5596f6 100644
--- a/src/lib/util/memory_segment_mapped.h
+++ b/src/lib/util/memory_segment_mapped.h
@@ -38,9 +38,12 @@ namespace util {
/// Multiple processes can open multiple segments for the same file in
/// read-only mode at the same time. But there shouldn't be more than
/// one process that opens segments for the same file in read-write mode
-/// at the same time. Likewise, if one process opens a segment for a file
-/// there shouldn't be any other process that opens a segment for the file
-/// in read-only mode. This class tries to detect any violation of this
+/// at the same time. Likewise, if one process opens a segment for a
+/// file in read-write mode, there shouldn't be any other process that
+/// opens a segment for the file in read-only mode. If one or more
+/// processes open segments for a file in read-only mode, there
+/// shouldn't be any other process that opens a segment for the file in
+/// read-write mode. This class tries to detect any violation of this
/// restriction, but this does not intend to provide 100% safety. It's
/// generally the user's responsibility to ensure this condition.
///
@@ -179,7 +182,7 @@ public:
/// read-only mode; in that case MemorySegmentError will be thrown.
virtual void deallocate(void* ptr, size_t size);
- virtual bool allMemoryDeallocated() const;
+ virtual bool allMemoryDeallocated();
/// \brief Mapped segment version of setNamedAddress.
///
@@ -199,7 +202,7 @@ public:
/// \brief Mapped segment version of getNamedAddress.
///
/// This version never throws.
- virtual void* getNamedAddressImpl(const char* name);
+ virtual NamedAddressResult getNamedAddressImpl(const char* name) const;
/// \brief Mapped segment version of clearNamedAddress.
///
diff --git a/src/lib/util/python/gen_wiredata.py.in b/src/lib/util/python/gen_wiredata.py.in
index c219e25..b3858b6 100755
--- a/src/lib/util/python/gen_wiredata.py.in
+++ b/src/lib/util/python/gen_wiredata.py.in
@@ -138,7 +138,7 @@ the type. But there are some common configurable entries. See the
description of the RR class. The most important one would be "as_rr".
It controls whether the entry should be treated as an RR (with name,
type, class and TTL) or only as an RDATA. By default as_rr is
-"False", so if an entry is to be intepreted as an RR, an as_rr entry
+"False", so if an entry is to be interpreted as an RR, an as_rr entry
must be explicitly specified with a value of "True".
Another common entry is "rdlen". It specifies the RDLEN field value
@@ -325,7 +325,7 @@ What you are expected to do is as follows:
examples.
"""
-import configparser, re, time, socket, sys
+import configparser, re, time, socket, sys, base64
from datetime import datetime
from optparse import OptionParser
@@ -413,6 +413,11 @@ def encode_string(name, len=None):
return '%0.*x' % (len * 2, name)
return ''.join(['%02x' % ord(ch) for ch in name])
+def encode_bytes(name, len=None):
+ if type(name) is int and len is not None:
+ return '%0.*x' % (len * 2, name)
+ return ''.join(['%02x' % ch for ch in name])
+
def count_namelabels(name):
if name == '.': # special case
return 0
@@ -762,7 +767,7 @@ class TXT(RR):
value will be used. If this parameter isn't specified either,
the length of the string will be used. Note that it means
this parameter (or any stringlenN) doesn't have to be specified
- unless you want to intentially build a broken character string.
+ unless you want to intentionally build a broken character string.
- string (string): the default string. If nstring >= 1 and the
corresponding stringN isn't specified in the spec file, this
string will be used.
@@ -888,6 +893,42 @@ class AFSDB(RR):
f.write('# SUBTYPE=%d SERVER=%s\n' % (self.subtype, self.server))
f.write('%04x %s\n' % (self.subtype, server_wire))
+class DNSKEY(RR):
+ '''Implements rendering DNSKEY RDATA in the test data format.
+
+ Configurable parameters are as follows (see code below for the
+ default values):
+ - flags (16-bit int): The flags field.
+ - protocol (8-bit int): The protocol field.
+ - algorithm (8-bit int): The algorithm field.
+ - digest (string): The key digest field.
+ '''
+ flags = 257
+ protocol = 3
+ algorithm = 5
+ digest = 'AAECAwQFBgcICQoLDA0ODw=='
+
+ def dump(self, f):
+ decoded_digest = base64.b64decode(bytes(self.digest, 'ascii'))
+ if self.rdlen is None:
+ self.rdlen = 4 + len(decoded_digest)
+ else:
+ self.rdlen = int(self.rdlen)
+
+ self.dump_header(f, self.rdlen)
+
+ f.write('# FLAGS=%d\n' % (self.flags))
+ f.write('%04x\n' % (self.flags))
+
+ f.write('# PROTOCOL=%d\n' % (self.protocol))
+ f.write('%02x\n' % (self.protocol))
+
+ f.write('# ALGORITHM=%d\n' % (self.algorithm))
+ f.write('%02x\n' % (self.algorithm))
+
+ f.write('# DIGEST=%s\n' % (self.digest))
+ f.write('%s\n' % (encode_bytes(decoded_digest)))
+
class NSECBASE(RR):
'''Implements rendering NSEC/NSEC3 type bitmaps commonly used for
these RRs. The NSEC and NSEC3 classes will be inherited from this
diff --git a/src/lib/util/random/random_number_generator.h b/src/lib/util/random/random_number_generator.h
index f0c0fb3..3f2a907 100644
--- a/src/lib/util/random/random_number_generator.h
+++ b/src/lib/util/random/random_number_generator.h
@@ -18,6 +18,7 @@
#include <algorithm>
#include <cmath>
#include <numeric>
+#include <vector>
#include <exceptions/exceptions.h>
@@ -59,7 +60,9 @@ public:
///
/// \param min The minimum number in the range
/// \param max The maximum number in the range
- UniformRandomIntegerGenerator(int min, int max):
+ /// \param seed A seed for the RNG. If 0 is passed, the current time
+ /// is used.
+ UniformRandomIntegerGenerator(int min, int max, unsigned int seed = 0):
min_(std::min(min, max)), max_(std::max(min, max)),
dist_(min_, max_), generator_(rng_, dist_)
{
@@ -72,7 +75,10 @@ public:
}
// Init with the current time
- rng_.seed(time(NULL));
+ if (seed == 0) {
+ seed = time(NULL);
+ }
+ rng_.seed(seed);
}
/// \brief Generate uniformly distributed integer
diff --git a/src/lib/util/tests/fd_share_tests.cc b/src/lib/util/tests/fd_share_tests.cc
index b8000e1..bb69204 100644
--- a/src/lib/util/tests/fd_share_tests.cc
+++ b/src/lib/util/tests/fd_share_tests.cc
@@ -66,7 +66,7 @@ TEST(FDShare, transfer) {
if (close(pipes[0])) {
exit(1);
}
- // Send "data" trough the received fd, close it and be done
+ // Send "data" through the received fd, close it and be done
if (!write_data(fd, "data", 4) || close(fd) == -1) {
exit(1);
}
diff --git a/src/lib/util/tests/io_utilities_unittest.cc b/src/lib/util/tests/io_utilities_unittest.cc
index 4293c7e..8e0c232 100644
--- a/src/lib/util/tests/io_utilities_unittest.cc
+++ b/src/lib/util/tests/io_utilities_unittest.cc
@@ -14,7 +14,7 @@
/// \brief Test of asiolink utilties
///
-/// Tests the fuctionality of the asiolink utilities code by comparing them
+/// Tests the functionality of the asiolink utilities code by comparing them
/// with the equivalent methods in isc::dns::[Input/Output]Buffer.
#include <cstddef>
diff --git a/src/lib/util/tests/memory_segment_common_unittest.cc b/src/lib/util/tests/memory_segment_common_unittest.cc
index 3810e0a..fac0559 100644
--- a/src/lib/util/tests/memory_segment_common_unittest.cc
+++ b/src/lib/util/tests/memory_segment_common_unittest.cc
@@ -30,9 +30,8 @@ checkSegmentNamedAddress(MemorySegment& segment, bool out_of_segment_ok) {
// NULL name is not allowed.
EXPECT_THROW(segment.getNamedAddress(NULL), InvalidParameter);
- // If the name does not exist, NULL should be returned.
- EXPECT_EQ(static_cast<void*>(NULL),
- segment.getNamedAddress("test address"));
+ // If the name does not exist, false should be returned.
+ EXPECT_FALSE(segment.getNamedAddress("test address").first);
// Now set it
void* ptr32 = segment.allocate(sizeof(uint32_t));
@@ -44,29 +43,32 @@ checkSegmentNamedAddress(MemorySegment& segment, bool out_of_segment_ok) {
EXPECT_THROW(segment.setNamedAddress(NULL, ptr32), InvalidParameter);
// we can now get it; the stored value should be intact.
- EXPECT_EQ(ptr32, segment.getNamedAddress("test address"));
- EXPECT_EQ(test_val, *static_cast<const uint32_t*>(ptr32));
+ MemorySegment::NamedAddressResult result =
+ segment.getNamedAddress("test address");
+ EXPECT_TRUE(result.first);
+ EXPECT_EQ(test_val, *static_cast<const uint32_t*>(result.second));
// Override it.
void* ptr16 = segment.allocate(sizeof(uint16_t));
const uint16_t test_val16 = 4200;
*static_cast<uint16_t*>(ptr16) = test_val16;
EXPECT_FALSE(segment.setNamedAddress("test address", ptr16));
- EXPECT_EQ(ptr16, segment.getNamedAddress("test address"));
- EXPECT_EQ(test_val16, *static_cast<const uint16_t*>(ptr16));
+ result = segment.getNamedAddress("test address");
+ EXPECT_TRUE(result.first);
+ EXPECT_EQ(test_val16, *static_cast<const uint16_t*>(result.second));
// Clear it. Then we won't be able to find it any more.
EXPECT_TRUE(segment.clearNamedAddress("test address"));
- EXPECT_EQ(static_cast<void*>(NULL),
- segment.getNamedAddress("test address"));
+ EXPECT_FALSE(segment.getNamedAddress("test address").first);
// duplicate attempt of clear will result in false as it doesn't exist.
EXPECT_FALSE(segment.clearNamedAddress("test address"));
// Setting NULL is okay.
EXPECT_FALSE(segment.setNamedAddress("null address", NULL));
- EXPECT_EQ(static_cast<void*>(NULL),
- segment.getNamedAddress("null address"));
+ result = segment.getNamedAddress("null address");
+ EXPECT_TRUE(result.first);
+ EXPECT_FALSE(result.second);
// If the underlying implementation performs explicit check against
// out-of-segment address, confirm the behavior.
diff --git a/src/lib/util/tests/memory_segment_mapped_unittest.cc b/src/lib/util/tests/memory_segment_mapped_unittest.cc
index bd37a73..861e6de 100644
--- a/src/lib/util/tests/memory_segment_mapped_unittest.cc
+++ b/src/lib/util/tests/memory_segment_mapped_unittest.cc
@@ -288,12 +288,14 @@ void
checkNamedData(const std::string& name, const std::vector<uint8_t>& data,
MemorySegment& sgmt, bool delete_after_check = false)
{
- void* dp = sgmt.getNamedAddress(name.c_str());
- ASSERT_TRUE(dp);
- EXPECT_EQ(0, std::memcmp(dp, &data[0], data.size()));
+ const MemorySegment::NamedAddressResult result =
+ sgmt.getNamedAddress(name.c_str());
+ ASSERT_TRUE(result.first);
+ ASSERT_TRUE(result.second);
+ EXPECT_EQ(0, std::memcmp(result.second, &data[0], data.size()));
if (delete_after_check) {
- sgmt.deallocate(dp, data.size());
+ sgmt.deallocate(result.second, data.size());
sgmt.clearNamedAddress(name.c_str());
}
}
@@ -310,10 +312,10 @@ TEST_F(MemorySegmentMappedTest, namedAddress) {
segment_.reset(); // close it before opening another one
segment_.reset(new MemorySegmentMapped(mapped_file));
- EXPECT_NE(static_cast<void*>(NULL),
- segment_->getNamedAddress("test address"));
- EXPECT_EQ(test_val16, *static_cast<const uint16_t*>(
- segment_->getNamedAddress("test address")));
+ MemorySegment::NamedAddressResult result =
+ segment_->getNamedAddress("test address");
+ ASSERT_TRUE(result.first);
+ EXPECT_EQ(test_val16, *static_cast<const uint16_t*>(result.second));
// try to set an unusually long name. We re-create the file so
// creating the name would cause allocation failure and trigger internal
@@ -324,8 +326,9 @@ TEST_F(MemorySegmentMappedTest, namedAddress) {
const std::string long_name(1025, 'x'); // definitely larger than segment
// setNamedAddress should return true, indicating segment has grown.
EXPECT_TRUE(segment_->setNamedAddress(long_name.c_str(), NULL));
- EXPECT_EQ(static_cast<void*>(NULL),
- segment_->getNamedAddress(long_name.c_str()));
+ result = segment_->getNamedAddress(long_name.c_str());
+ EXPECT_TRUE(result.first);
+ EXPECT_FALSE(result.second);
// Check contents pointed by named addresses survive growing and
// shrinking segment.
@@ -411,10 +414,12 @@ TEST_F(MemorySegmentMappedTest, multiProcess) {
EXPECT_EQ(0, from_parent);
MemorySegmentMapped sgmt(mapped_file);
- void* ptr_child = sgmt.getNamedAddress("test address");
- EXPECT_TRUE(ptr_child);
- if (ptr_child) {
- const uint32_t val = *static_cast<const uint32_t*>(ptr_child);
+ const MemorySegment::NamedAddressResult result =
+ sgmt.getNamedAddress("test address");
+ ASSERT_TRUE(result.first);
+ EXPECT_TRUE(result.second);
+ if (result.second) {
+ const uint32_t val = *static_cast<const uint32_t*>(result.second);
EXPECT_EQ(424242, val);
// tell the parent whether it succeeded. 0 means it did,
// 0xff means it failed.
@@ -426,9 +431,11 @@ TEST_F(MemorySegmentMappedTest, multiProcess) {
// parent: open another read-only segment, then tell the child to open
// its own segment.
segment_.reset(new MemorySegmentMapped(mapped_file));
- ptr = segment_->getNamedAddress("test address");
- ASSERT_TRUE(ptr);
- EXPECT_EQ(424242, *static_cast<const uint32_t*>(ptr));
+ const MemorySegment::NamedAddressResult result =
+ segment_->getNamedAddress("test address");
+ ASSERT_TRUE(result.first);
+ ASSERT_TRUE(result.second);
+ EXPECT_EQ(424242, *static_cast<const uint32_t*>(result.second));
const char some_data = 0;
EXPECT_EQ(1, write(pipe_to_child.getWriteFD(), &some_data,
sizeof(some_data)));
@@ -461,7 +468,14 @@ TEST_F(MemorySegmentMappedTest, shrink) {
EXPECT_EQ(shrinked_size, segment_->getSize());
// Check that the segment is still usable after shrink.
- void* p = segment_->allocate(sizeof(uint32_t));
+ void *p = NULL;
+ while (!p) {
+ try {
+ p = segment_->allocate(sizeof(uint32_t));
+ } catch (const MemorySegmentGrown&) {
+ // Do nothing. Just try again.
+ }
+ }
segment_->deallocate(p, sizeof(uint32_t));
}
@@ -478,9 +492,11 @@ TEST_F(MemorySegmentMappedTest, violateReadOnly) {
if (!isc::util::unittests::runningOnValgrind()) {
EXPECT_DEATH_IF_SUPPORTED({
MemorySegmentMapped segment_ro(mapped_file);
- EXPECT_TRUE(segment_ro.getNamedAddress("test address"));
- *static_cast<uint32_t*>(
- segment_ro.getNamedAddress("test address")) = 0;
+ const MemorySegment::NamedAddressResult result =
+ segment_ro.getNamedAddress("test address");
+ ASSERT_TRUE(result.first);
+ ASSERT_TRUE(result.second);
+ *static_cast<uint32_t*>(result.second) = 0;
}, "");
}
@@ -488,10 +504,12 @@ TEST_F(MemorySegmentMappedTest, violateReadOnly) {
// attempts are prohibited. When detectable it must result in an
// exception.
MemorySegmentMapped segment_ro(mapped_file);
- ptr = segment_ro.getNamedAddress("test address");
- EXPECT_NE(static_cast<void*>(NULL), ptr);
+ const MemorySegment::NamedAddressResult result =
+ segment_ro.getNamedAddress("test address");
+ ASSERT_TRUE(result.first);
+ EXPECT_NE(static_cast<void*>(NULL), result.second);
- EXPECT_THROW(segment_ro.deallocate(ptr, 4), MemorySegmentError);
+ EXPECT_THROW(segment_ro.deallocate(result.second, 4), MemorySegmentError);
EXPECT_THROW(segment_ro.allocate(16), MemorySegmentError);
// allocation that would otherwise require growing the segment; permission
diff --git a/src/lib/util/tests/random_number_generator_unittest.cc b/src/lib/util/tests/random_number_generator_unittest.cc
index 23d5b88..2c6bec8 100644
--- a/src/lib/util/tests/random_number_generator_unittest.cc
+++ b/src/lib/util/tests/random_number_generator_unittest.cc
@@ -14,14 +14,16 @@
#include <config.h>
+#include <util/random/random_number_generator.h>
+
#include <gtest/gtest.h>
#include <boost/shared_ptr.hpp>
-#include <algorithm>
#include <iostream>
-#include <vector>
+#include <climits>
-#include <util/random/random_number_generator.h>
+#include <sys/types.h>
+#include <unistd.h>
namespace isc {
namespace util {
@@ -42,9 +44,9 @@ public:
}
virtual ~UniformRandomIntegerGeneratorTest(){}
- int gen() { return gen_(); }
- int max() const { return max_; }
- int min() const { return min_; }
+ int gen() { return (gen_()); }
+ int max() const { return (max_); }
+ int min() const { return (min_); }
private:
UniformRandomIntegerGenerator gen_;
@@ -82,7 +84,22 @@ TEST_F(UniformRandomIntegerGeneratorTest, IntegerRange) {
vector<int>::iterator it = unique(numbers.begin(), numbers.end());
// make sure the numbers are in range [min, max]
- ASSERT_EQ(it - numbers.begin(), max() - min() + 1);
+ ASSERT_EQ(it - numbers.begin(), max() - min() + 1);
+}
+
+TEST_F(UniformRandomIntegerGeneratorTest, withSeed) {
+ // Test that two generators with the same seed return the same
+ // sequence.
+ UniformRandomIntegerGenerator gen1(0, INT_MAX, getpid());
+ vector<int> numbers;
+ for (int i = 0; i < 1024; ++i) {
+ numbers.push_back(gen1());
+ }
+
+ UniformRandomIntegerGenerator gen2(0, INT_MAX, getpid());
+ for (int i = 0; i < 1024; ++i) {
+ EXPECT_EQ(numbers[i], gen2());
+ }
}
/// \brief Test Fixture Class for weighted random number generator
@@ -99,7 +116,8 @@ public:
TEST_F(WeightedRandomIntegerGeneratorTest, Constructor) {
vector<double> probabilities;
- // If no probabilities is provided, the smallest integer will always be generated
+ // If no probabilities is provided, the smallest integer will always
+ // be generated
WeightedRandomIntegerGenerator gen(probabilities, 123);
for (int i = 0; i < 100; ++i) {
ASSERT_EQ(gen(), 123);
diff --git a/src/lib/util/threads/Makefile.am b/src/lib/util/threads/Makefile.am
index 121e4ab..7ab6f32 100644
--- a/src/lib/util/threads/Makefile.am
+++ b/src/lib/util/threads/Makefile.am
@@ -7,6 +7,7 @@ AM_CPPFLAGS += $(BOOST_INCLUDES) $(MULTITHREADING_FLAG)
lib_LTLIBRARIES = libb10-threads.la
libb10_threads_la_SOURCES = sync.h sync.cc
libb10_threads_la_SOURCES += thread.h thread.cc
-libb10_threads_la_LIBADD = $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
+libb10_threads_la_LIBADD = $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
+libb10_threads_la_LIBADD += $(PTHREAD_LDFLAGS)
CLEANFILES = *.gcno *.gcda
diff --git a/src/lib/util/threads/sync.cc b/src/lib/util/threads/sync.cc
index 46a5646..0e0ec08 100644
--- a/src/lib/util/threads/sync.cc
+++ b/src/lib/util/threads/sync.cc
@@ -169,6 +169,26 @@ Mutex::lock() {
#endif // ENABLE_DEBUG
}
+bool
+Mutex::tryLock() {
+ assert(impl_ != NULL);
+ const int result = pthread_mutex_trylock(&impl_->mutex);
+ // In the case of pthread_mutex_trylock(), if it is called on a
+ // locked mutex from the same thread, some platforms (such as fedora
+ // and debian) return EBUSY whereas others (such as centos 5) return
+ // EDEADLK. We return false and don't pass the lock attempt in both
+ // cases.
+ if (result == EBUSY || result == EDEADLK) {
+ return (false);
+ } else if (result != 0) {
+ isc_throw(isc::InvalidOperation, std::strerror(result));
+ }
+#ifdef ENABLE_DEBUG
+ postLockAction(); // Only in debug mode
+#endif // ENABLE_DEBUG
+ return (true);
+}
+
void
Mutex::unlock() {
assert(impl_ != NULL);
diff --git a/src/lib/util/threads/sync.h b/src/lib/util/threads/sync.h
index 87c78be..c0bc1a9 100644
--- a/src/lib/util/threads/sync.h
+++ b/src/lib/util/threads/sync.h
@@ -15,6 +15,8 @@
#ifndef B10_THREAD_SYNC_H
#define B10_THREAD_SYNC_H
+#include <exceptions/exceptions.h>
+
#include <boost/noncopyable.hpp>
#include <cstdlib> // for NULL.
@@ -77,17 +79,34 @@ public:
/// of function no matter by what means.
class Locker : boost::noncopyable {
public:
+ /// \brief Exception thrown when the mutex is already locked and
+ /// a non-blocking locker is attempted around it.
+ struct AlreadyLocked : public isc::InvalidParameter {
+ AlreadyLocked(const char* file, size_t line, const char* what) :
+ isc::InvalidParameter(file, line, what)
+ {}
+ };
+
/// \brief Constructor.
///
- /// Locks the mutex. May block for extended period of time.
+ /// Locks the mutex. May block for extended period of time if
+ /// \c block is true.
///
/// \throw isc::InvalidOperation when OS reports error. This usually
/// means an attempt to use the mutex in a wrong way (locking
/// a mutex second time from the same thread, for example).
- Locker(Mutex& mutex) :
+ /// \throw AlreadyLocked if \c block is false and the mutex is
+ /// already locked.
+ Locker(Mutex& mutex, bool block = true) :
mutex_(mutex)
{
- mutex.lock();
+ if (block) {
+ mutex.lock();
+ } else {
+ if (!mutex.tryLock()) {
+ isc_throw(AlreadyLocked, "The mutex is already locked");
+ }
+ }
}
/// \brief Destructor.
@@ -107,6 +126,24 @@ public:
///
/// \todo Disable in non-debug build
bool locked() const;
+
+private:
+ /// \brief Lock the mutex
+ ///
+ /// This method blocks until the mutex can be locked.
+ void lock();
+
+ /// \brief Try to lock the mutex
+ ///
+ /// This method doesn't block and returns immediately with a status
+ /// on whether the lock operation was successful.
+ ///
+ /// \return true if the lock was successful, false otherwise.
+ bool tryLock();
+
+ /// \brief Unlock the mutex
+ void unlock();
+
private:
friend class CondVar;
@@ -131,8 +168,6 @@ private:
class Impl;
Impl* impl_;
- void lock();
- void unlock();
};
/// \brief Encapsulation for a condition variable.
diff --git a/src/lib/util/threads/tests/lock_unittest.cc b/src/lib/util/threads/tests/lock_unittest.cc
index 4c4f831..c17999e 100644
--- a/src/lib/util/threads/tests/lock_unittest.cc
+++ b/src/lib/util/threads/tests/lock_unittest.cc
@@ -44,6 +44,44 @@ TEST(MutexTest, lockMultiple) {
Mutex::Locker l2(mutex); // Attempt to lock again.
}, isc::InvalidOperation);
EXPECT_TRUE(mutex.locked()); // Debug-only build
+
+ // block=true explicitly.
+ Mutex mutex2;
+ EXPECT_FALSE(mutex2.locked()); // Debug-only build
+ Mutex::Locker l12(mutex2, true);
+ EXPECT_TRUE(mutex2.locked()); // Debug-only build
+}
+
+void
+testThread(Mutex* mutex)
+{
+ // block=false (tryLock). This should not block indefinitely, but
+ // throw AlreadyLocked. If block were true, this would block
+ // indefinitely here.
+ EXPECT_THROW({
+ Mutex::Locker l3(*mutex, false);
+ }, Mutex::Locker::AlreadyLocked);
+
+ EXPECT_TRUE(mutex->locked()); // Debug-only build
+}
+
+// Test the non-blocking variant using a second thread.
+TEST(MutexTest, lockNonBlocking) {
+ // block=false (tryLock).
+ Mutex mutex;
+ Mutex::Locker l1(mutex, false);
+ EXPECT_TRUE(mutex.locked()); // Debug-only build
+
+ // First, try another locker from the same thread.
+ EXPECT_THROW({
+ Mutex::Locker l2(mutex, false);
+ }, Mutex::Locker::AlreadyLocked);
+
+ EXPECT_TRUE(mutex.locked()); // Debug-only build
+
+ // Now try another locker from a different thread.
+ Thread thread(boost::bind(&testThread, &mutex));
+ thread.wait();
}
#endif // ENABLE_DEBUG
diff --git a/src/lib/util/unittests/resolver.h b/src/lib/util/unittests/resolver.h
index 560a892..127fd54 100644
--- a/src/lib/util/unittests/resolver.h
+++ b/src/lib/util/unittests/resolver.h
@@ -60,7 +60,7 @@ class TestResolver : public isc::resolve::ResolverInterface {
PresetAnswers answers_;
public:
typedef std::pair<isc::dns::QuestionPtr, CallbackPtr> Request;
- /// \brief List of requests the tested class sent trough resolve
+ /// \brief List of requests the tested class sent through resolve
std::vector<Request> requests;
/// \brief Destructor
diff --git a/src/lib/xfr/tests/client_test.cc b/src/lib/xfr/tests/client_test.cc
index 6c9f4ad..ce783b0 100644
--- a/src/lib/xfr/tests/client_test.cc
+++ b/src/lib/xfr/tests/client_test.cc
@@ -24,7 +24,7 @@ using namespace isc::xfr;
namespace {
-TEST(ClientTest, connetFile) {
+TEST(ClientTest, connectFile) {
// File path is too long
struct sockaddr_un s; // can't be const; some compiler complains
EXPECT_THROW(XfroutClient(string(sizeof(s.sun_path), 'x')).connect(),
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 570c0e2..9f1025b 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -1 +1 @@
-SUBDIRS = system tools
+SUBDIRS = tools
diff --git a/tests/lettuce/README b/tests/lettuce/README
index 94bf82b..a53c6e7 100644
--- a/tests/lettuce/README
+++ b/tests/lettuce/README
@@ -70,6 +70,11 @@ These files *will* be overwritten or deleted if the same scenarios are run
again, so if you want to inspect them after a failed test, either do so
immediately or move the files.
+If you want to keep these output files even for successful runs, you can
+specify the environment variable LETTUCE_KEEP_OUTPUT=1. The files will
+still be overwritten by subsequent runs, but they will not automatically be
+deleted.
+
Adding and extending tests
--------------------------
diff --git a/tests/lettuce/configurations/glue.config b/tests/lettuce/configurations/glue.config
new file mode 100644
index 0000000..fc301d3
--- /dev/null
+++ b/tests/lettuce/configurations/glue.config
@@ -0,0 +1,34 @@
+{
+ "version": 3,
+ "Logging": {
+ "loggers": [{
+ "severity": "DEBUG",
+ "name": "*",
+ "debuglevel": 99
+ }]
+ },
+ "Auth": {
+ "listen_on": [{
+ "port": 47806,
+ "address": "127.0.0.1"
+ }]
+ },
+ "data_sources": {
+ "classes": {
+ "IN": [
+ {
+ "type": "sqlite3",
+ "params": {
+ "database_file": "data/glue.sqlite3"
+ }
+ }
+ ]
+ }
+ },
+ "Init": {
+ "components": {
+ "b10-auth": { "kind": "needed", "special": "auth" },
+ "b10-cmdctl": { "special": "cmdctl", "kind": "needed" }
+ }
+ }
+}
diff --git a/tests/lettuce/configurations/xfrin/.gitignore b/tests/lettuce/configurations/xfrin/.gitignore
index 5d56912..0b03cd2 100644
--- a/tests/lettuce/configurations/xfrin/.gitignore
+++ b/tests/lettuce/configurations/xfrin/.gitignore
@@ -1,2 +1,4 @@
/retransfer_master.conf
+/retransfer_master_nons.conf
/retransfer_slave.conf
+/retransfer_slave_notify.conf
diff --git a/tests/lettuce/configurations/xfrin/retransfer_master.conf.orig b/tests/lettuce/configurations/xfrin/retransfer_master.conf.orig
index 1b2953d..d7ea9a5 100644
--- a/tests/lettuce/configurations/xfrin/retransfer_master.conf.orig
+++ b/tests/lettuce/configurations/xfrin/retransfer_master.conf.orig
@@ -33,9 +33,6 @@
"port": 47806
} ]
},
- "Stats": {
- "poll-interval": 1
- },
"Init": {
"components": {
"b10-auth": { "kind": "needed", "special": "auth" },
diff --git a/tests/lettuce/configurations/xfrin/retransfer_master_diffs.conf b/tests/lettuce/configurations/xfrin/retransfer_master_diffs.conf
new file mode 100644
index 0000000..34d2d03
--- /dev/null
+++ b/tests/lettuce/configurations/xfrin/retransfer_master_diffs.conf
@@ -0,0 +1,47 @@
+{
+ "version": 3,
+ "Logging": {
+ "loggers": [ {
+ "debuglevel": 99,
+ "severity": "DEBUG",
+ "name": "*"
+ } ]
+ },
+ "Auth": {
+ "database_file": "data/xfrin-diffs.sqlite3",
+ "listen_on": [ {
+ "address": "::1",
+ "port": 47807
+ } ]
+ },
+ "data_sources": {
+ "classes": {
+ "IN": [{
+ "type": "sqlite3",
+ "params": {
+ "database_file": "data/xfrin-diffs.sqlite3"
+ }
+ }]
+ }
+ },
+ "Xfrout": {
+ "zone_config": [ {
+ "origin": "example."
+ } ],
+ "also_notify": [ {
+ "address": "::1",
+ "port": 47806
+ } ]
+ },
+ "Stats": {
+ "poll-interval": 1
+ },
+ "Init": {
+ "components": {
+ "b10-auth": { "kind": "needed", "special": "auth" },
+ "b10-xfrout": { "address": "Xfrout", "kind": "dispensable" },
+ "b10-zonemgr": { "address": "Zonemgr", "kind": "dispensable" },
+ "b10-cmdctl": { "special": "cmdctl", "kind": "needed" }
+ }
+ }
+}
diff --git a/tests/lettuce/configurations/xfrin/retransfer_master_v4.conf.orig b/tests/lettuce/configurations/xfrin/retransfer_master_v4.conf.orig
new file mode 100644
index 0000000..755c91b
--- /dev/null
+++ b/tests/lettuce/configurations/xfrin/retransfer_master_v4.conf.orig
@@ -0,0 +1,45 @@
+{
+ "version": 3,
+ "Logging": {
+ "loggers": [ {
+ "debuglevel": 99,
+ "severity": "DEBUG",
+ "name": "*"
+ } ]
+ },
+ "Auth": {
+ "database_file": "data/example.org.sqlite3",
+ "listen_on": [ {
+ "address": "127.0.0.1",
+ "port": 47809
+ } ]
+ },
+ "data_sources": {
+ "classes": {
+ "IN": [{
+ "type": "sqlite3",
+ "params": {
+ "database_file": "data/example.org.sqlite3"
+ }
+ }]
+ }
+ },
+ "Xfrout": {
+ "zone_config": [ {
+ "origin": "example.org"
+ } ],
+ "also_notify": [ {
+ "address": "127.0.0.1",
+ "port": 47806
+ } ]
+ },
+ "Init": {
+ "components": {
+ "b10-auth": { "kind": "needed", "special": "auth" },
+ "b10-xfrout": { "address": "Xfrout", "kind": "dispensable" },
+ "b10-zonemgr": { "address": "Zonemgr", "kind": "dispensable" },
+ "b10-stats": { "address": "Stats", "kind": "dispensable" },
+ "b10-cmdctl": { "special": "cmdctl", "kind": "needed" }
+ }
+ }
+}
diff --git a/tests/lettuce/configurations/xfrin/retransfer_slave_diffs.conf b/tests/lettuce/configurations/xfrin/retransfer_slave_diffs.conf
new file mode 100644
index 0000000..1bc6324
--- /dev/null
+++ b/tests/lettuce/configurations/xfrin/retransfer_slave_diffs.conf
@@ -0,0 +1,43 @@
+{
+ "version": 3,
+ "Logging": {
+ "loggers": [ {
+ "debuglevel": 99,
+ "severity": "DEBUG",
+ "name": "*"
+ } ]
+ },
+ "Auth": {
+ "database_file": "data/xfrin-before-diffs.sqlite3",
+ "listen_on": [ {
+ "address": "::1",
+ "port": 47806
+ } ]
+ },
+ "Xfrin": {
+ "zones": [ {
+ "name": "example",
+ "master_addr": "::1",
+ "master_port": 47807,
+ "use_ixfr": true
+ } ]
+ },
+ "data_sources": {
+ "classes": {
+ "IN": [{
+ "type": "sqlite3",
+ "params": {
+ "database_file": "data/xfrin-before-diffs.sqlite3"
+ }
+ }]
+ }
+ },
+ "Init": {
+ "components": {
+ "b10-auth": { "kind": "needed", "special": "auth" },
+ "b10-xfrin": { "address": "Xfrin", "kind": "dispensable" },
+ "b10-zonemgr": { "address": "Zonemgr", "kind": "dispensable" },
+ "b10-cmdctl": { "special": "cmdctl", "kind": "needed" }
+ }
+ }
+}
diff --git a/tests/lettuce/configurations/xfrin/retransfer_slave_notify.conf b/tests/lettuce/configurations/xfrin/retransfer_slave_notify.conf
deleted file mode 100644
index a5c22b1..0000000
--- a/tests/lettuce/configurations/xfrin/retransfer_slave_notify.conf
+++ /dev/null
@@ -1,48 +0,0 @@
-{
- "version": 3,
- "Logging": {
- "loggers": [ {
- "debuglevel": 99,
- "severity": "DEBUG",
- "name": "*"
- } ]
- },
- "Auth": {
- "database_file": "data/xfrin-notify.sqlite3",
- "listen_on": [ {
- "address": "::1",
- "port": 47806
- } ]
- },
- "data_sources": {
- "classes": {
- "IN": [{
- "type": "sqlite3",
- "params": {
- "database_file": "data/xfrin-notify.sqlite3"
- }
- }]
- }
- },
- "Xfrin": {
- "zones": [ {
- "name": "example.org",
- "master_addr": "::1",
- "master_port": 47807
- } ]
- },
- "Zonemgr": {
- "secondary_zones": [ {
- "name": "example.org",
- "class": "IN"
- } ]
- },
- "Init": {
- "components": {
- "b10-auth": { "kind": "needed", "special": "auth" },
- "b10-xfrin": { "address": "Xfrin", "kind": "dispensable" },
- "b10-zonemgr": { "address": "Zonemgr", "kind": "dispensable" },
- "b10-cmdctl": { "special": "cmdctl", "kind": "needed" }
- }
- }
-}
diff --git a/tests/lettuce/configurations/xfrin/retransfer_slave_notify.conf.orig b/tests/lettuce/configurations/xfrin/retransfer_slave_notify.conf.orig
new file mode 100644
index 0000000..3040b6c
--- /dev/null
+++ b/tests/lettuce/configurations/xfrin/retransfer_slave_notify.conf.orig
@@ -0,0 +1,49 @@
+{
+ "version": 3,
+ "Logging": {
+ "loggers": [ {
+ "debuglevel": 99,
+ "severity": "DEBUG",
+ "name": "*"
+ } ]
+ },
+ "Auth": {
+ "database_file": "data/xfrin-notify.sqlite3",
+ "listen_on": [ {
+ "address": "::1",
+ "port": 47806
+ } ]
+ },
+ "data_sources": {
+ "classes": {
+ "IN": [{
+ "type": "sqlite3",
+ "params": {
+ "database_file": "data/xfrin-notify.sqlite3"
+ }
+ }]
+ }
+ },
+ "Xfrin": {
+ "zones": [ {
+ "name": "example.org",
+ "master_addr": "::1",
+ "master_port": 47807
+ } ]
+ },
+ "Zonemgr": {
+ "secondary_zones": [ {
+ "name": "example.org",
+ "class": "IN"
+ } ]
+ },
+ "Init": {
+ "components": {
+ "b10-auth": { "kind": "needed", "special": "auth" },
+ "b10-xfrin": { "address": "Xfrin", "kind": "dispensable" },
+ "b10-zonemgr": { "address": "Zonemgr", "kind": "dispensable" },
+ "b10-stats": { "address": "Stats", "kind": "dispensable" },
+ "b10-cmdctl": { "special": "cmdctl", "kind": "needed" }
+ }
+ }
+}
diff --git a/tests/lettuce/configurations/xfrin/retransfer_slave_notify_v4.conf b/tests/lettuce/configurations/xfrin/retransfer_slave_notify_v4.conf
new file mode 100644
index 0000000..67ebfd3
--- /dev/null
+++ b/tests/lettuce/configurations/xfrin/retransfer_slave_notify_v4.conf
@@ -0,0 +1,49 @@
+{
+ "version": 3,
+ "Logging": {
+ "loggers": [ {
+ "debuglevel": 99,
+ "severity": "DEBUG",
+ "name": "*"
+ } ]
+ },
+ "Auth": {
+ "database_file": "data/xfrin-notify.sqlite3",
+ "listen_on": [ {
+ "address": "127.0.0.1",
+ "port": 47806
+ } ]
+ },
+ "data_sources": {
+ "classes": {
+ "IN": [{
+ "type": "sqlite3",
+ "params": {
+ "database_file": "data/xfrin-notify.sqlite3"
+ }
+ }]
+ }
+ },
+ "Xfrin": {
+ "zones": [ {
+ "name": "example.org",
+ "master_addr": "127.0.0.1",
+ "master_port": 47809
+ } ]
+ },
+ "Zonemgr": {
+ "secondary_zones": [ {
+ "name": "example.org",
+ "class": "IN"
+ } ]
+ },
+ "Init": {
+ "components": {
+ "b10-auth": { "kind": "needed", "special": "auth" },
+ "b10-xfrin": { "address": "Xfrin", "kind": "dispensable" },
+ "b10-zonemgr": { "address": "Zonemgr", "kind": "dispensable" },
+ "b10-stats": { "address": "Stats", "kind": "dispensable" },
+ "b10-cmdctl": { "special": "cmdctl", "kind": "needed" }
+ }
+ }
+}
diff --git a/tests/lettuce/data/.gitignore b/tests/lettuce/data/.gitignore
index 888175e..946228c 100644
--- a/tests/lettuce/data/.gitignore
+++ b/tests/lettuce/data/.gitignore
@@ -1,3 +1,4 @@
/inmem-xfrin.sqlite3
/test_nonexistent_db.sqlite3
+/xfrin-before-diffs.sqlite3
/xfrin-notify.sqlite3
diff --git a/tests/lettuce/data/glue.sqlite3 b/tests/lettuce/data/glue.sqlite3
new file mode 100644
index 0000000..a527de8
Binary files /dev/null and b/tests/lettuce/data/glue.sqlite3 differ
diff --git a/tests/lettuce/data/xfrin-before-diffs.sqlite3.orig b/tests/lettuce/data/xfrin-before-diffs.sqlite3.orig
new file mode 100644
index 0000000..45bf77d
Binary files /dev/null and b/tests/lettuce/data/xfrin-before-diffs.sqlite3.orig differ
diff --git a/tests/lettuce/data/xfrin-diffs.sqlite3 b/tests/lettuce/data/xfrin-diffs.sqlite3
new file mode 100644
index 0000000..55b233d
Binary files /dev/null and b/tests/lettuce/data/xfrin-diffs.sqlite3 differ
diff --git a/tests/lettuce/features/auth_badzone.feature b/tests/lettuce/features/auth_badzone.feature
index edc1a64..5448b6e 100644
--- a/tests/lettuce/features/auth_badzone.feature
+++ b/tests/lettuce/features/auth_badzone.feature
@@ -12,9 +12,9 @@ Feature: Authoritative DNS server with a bad zone
# will be logged and we cannot use the 'new' keyword to wait for
# 3 different log messages. *There could still be a race here if
# auth starts very quickly.*
- And wait for new bind10 stderr message DATASRC_LOAD_FROM_FILE_ERROR
- And wait for new bind10 stderr message DATASRC_LOAD_FROM_FILE_ERROR
- And wait for new bind10 stderr message DATASRC_LOAD_FROM_FILE_ERROR
+ And wait for new bind10 stderr message DATASRC_LOAD_ZONE_ERROR
+ And wait for new bind10 stderr message DATASRC_LOAD_ZONE_ERROR
+ And wait for new bind10 stderr message DATASRC_LOAD_ZONE_ERROR
And wait for bind10 stderr message BIND10_STARTED_CC
And wait for bind10 stderr message CMDCTL_STARTED
diff --git a/tests/lettuce/features/bindctl_commands.feature b/tests/lettuce/features/bindctl_commands.feature
index b9fef82..a7a455a 100644
--- a/tests/lettuce/features/bindctl_commands.feature
+++ b/tests/lettuce/features/bindctl_commands.feature
@@ -154,3 +154,18 @@ Feature: control with bindctl
bind10 module Xfrout should be running
bind10 module Xfrin should be running
bind10 module Zonemgr should be running
+
+ Scenario: Shutting down a certain module
+ # We could test with several modules, but for now we are particularly
+ # interested in shutting down cmdctl. It previously caused hangup,
+ # so this scenario confirms it's certainly fixed. Note: since cmdctl
+ # is a "needed" component, shutting it down will result in system
+ # shutdown. So "send bind10 command" will fail (it cannot complete
+ # "quit").
+ Given I have bind10 running with configuration bindctl/bindctl.config
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+
+ When I send bind10 ignoring failure the command Cmdctl shutdown
+ And wait for bind10 stderr message CMDCTL_EXITING
+ And wait for bind10 stderr message BIND10_SHUTDOWN_COMPLETE
diff --git a/tests/lettuce/features/example.feature b/tests/lettuce/features/example.feature
index ecfdcc3..86d20d3 100644
--- a/tests/lettuce/features/example.feature
+++ b/tests/lettuce/features/example.feature
@@ -26,7 +26,7 @@ Feature: Example feature
Scenario: New database
# This test checks whether a database file is automatically created
- # Underwater, we take advantage of our intialization routines so
+ # Underwater, we take advantage of our initialization routines so
# that we are sure this file does not exist, see
# features/terrain/terrain.py
@@ -161,11 +161,13 @@ Feature: Example feature
A query for www.example.org should have rcode NOERROR
Wait for new bind10 stderr message AUTH_SEND_NORMAL_RESPONSE
Then set bind10 configuration data_sources/classes/IN[0]/params to {"database_file": "data/empty_db.sqlite3"}
- And wait for new bind10 stderr message DATASRC_SQLITE_CONNOPEN
+ # The 'not missing placeholder' check is for #2743
+ And wait for new bind10 stderr message DATASRC_SQLITE_CONNOPEN not Missing placeholder
A query for www.example.org should have rcode REFUSED
Wait for new bind10 stderr message AUTH_SEND_NORMAL_RESPONSE
Then set bind10 configuration data_sources/classes/IN[0]/params to {"database_file": "data/example.org.sqlite3"}
- And wait for new bind10 stderr message DATASRC_SQLITE_CONNOPEN
+ # The 'not missing placeholder' check is for #2743
+ And wait for new bind10 stderr message DATASRC_SQLITE_CONNOPEN not Missing placeholder
A query for www.example.org should have rcode NOERROR
Scenario: two bind10 instances
diff --git a/tests/lettuce/features/nsec3_auth.feature b/tests/lettuce/features/nsec3_auth.feature
index 4e5ed5b..6d3a556 100644
--- a/tests/lettuce/features/nsec3_auth.feature
+++ b/tests/lettuce/features/nsec3_auth.feature
@@ -241,7 +241,7 @@ Feature: NSEC3 Authoritative service
"""
#
- # Below are additional tests, not explicitely stated in RFC5155
+ # Below are additional tests, not explicitly stated in RFC5155
#
Scenario: 7.2.2 other; Name Error where one NSEC3 covers multiple parts of proof (closest encloser)
diff --git a/tests/lettuce/features/queries.feature b/tests/lettuce/features/queries.feature
index 8a66d1c..b0a6fac 100644
--- a/tests/lettuce/features/queries.feature
+++ b/tests/lettuce/features/queries.feature
@@ -3,6 +3,51 @@ Feature: Querying feature
for instance whether multiple queries in a row return consistent
answers.
+ Scenario: Glue
+ # Check the auth server returns the correct glue when asked for it.
+ Given I have bind10 running with configuration glue.config
+ 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
+
+ # This query should result in a delegation with two NS; one in the
+ # delegated zone and one in a so called out-of-bailiwick zone for which
+ # the auth server has authority, too. For the former, the server
+ # should return glue in the parent zone. For the latter, BIND 9 and
+ # BIND 10 behave differently; BIND 9 uses "glue" in the parent zone
+ # (since this is the root zone everything can be considered a valid
+ # glue). BIND 10 (using sqlite3 data source) searches the other zone
+ # and uses the authoritative data in that zone (which is intentionally
+ # different from the glue in the root zone).
+ A query for foo.bar.example type A should have rcode NOERROR
+ The answer section of the last query response should be
+ """
+ """
+ The authority section of the last query response should be
+ """
+ example. 172800 IN NS NS1.example.COM.
+ example. 172800 IN NS NS.example.
+ """
+ The additional section of the last query response should be
+ """
+ NS.example. 172800 IN A 192.0.2.1
+ NS.example. 172800 IN A 192.0.2.2
+ NS1.example.COM. 172800 IN A 192.0.2.3
+ """
+ # Test we don't get out-of-zone glue
+ A query for example.net type A should have rcode NOERROR
+ The answer section of the last query response should be
+ """
+ """
+ The authority section of the last query response should be
+ """
+ example.net. 300 IN NS ns2.example.info.
+ example.net. 300 IN NS ns1.example.info.
+ """
+ The additional section of the last query response should be
+ """
+ """
+
Scenario: Repeated queries
Given I have bind10 running with configuration example.org.inmem.config
And wait for bind10 stderr message BIND10_STARTED_CC
@@ -50,6 +95,10 @@ Feature: Querying feature
ns2.example.org. 3600 IN A 192.0.2.4
"""
+ # Make sure handling statistics command handling checked below is
+ # after this query
+ And wait for bind10 stderr message AUTH_SEND_NORMAL_RESPONSE
+
When I wait for new bind10 stderr message STATS_SEND_STATISTICS_REQUEST
# make sure Auth module receives a command
And wait for new bind10 stderr message AUTH_RECEIVED_COMMAND
@@ -92,6 +141,10 @@ Feature: Querying feature
ns2.example.org. 3600 IN A 192.0.2.4
"""
+ # Make sure handling statistics command handling checked below is
+ # after this query
+ And wait for new bind10 stderr message AUTH_SEND_NORMAL_RESPONSE
+
When I wait for new bind10 stderr message STATS_SEND_STATISTICS_REQUEST
# make sure Auth module receives a command
And wait for new bind10 stderr message AUTH_RECEIVED_COMMAND
@@ -122,6 +175,10 @@ Feature: Querying feature
example.org. 3600 IN SOA ns1.example.org. admin.example.org. 1234 3600 1800 2419200 7200
"""
+ # Make sure handling statistics command handling checked below is
+ # after this query
+ And wait for new bind10 stderr message AUTH_SEND_NORMAL_RESPONSE
+
When I wait for new bind10 stderr message STATS_SEND_STATISTICS_REQUEST
# make sure Auth module receives a command
And wait for new bind10 stderr message AUTH_RECEIVED_COMMAND
@@ -186,6 +243,10 @@ Feature: Querying feature
mail.example.org. 3600 IN A 192.0.2.10
"""
+ # Make sure handling statistics command handling checked below is
+ # after this query
+ And wait for bind10 stderr message AUTH_SEND_NORMAL_RESPONSE
+
When I wait for new bind10 stderr message STATS_SEND_STATISTICS_REQUEST
# make sure Auth module receives a command
And wait for new bind10 stderr message AUTH_RECEIVED_COMMAND
@@ -237,6 +298,10 @@ Feature: Querying feature
ns.sub.example.org. 3600 IN A 192.0.2.101
"""
+ # Make sure handling statistics command handling checked below is
+ # after this query
+ And wait for bind10 stderr message AUTH_SEND_NORMAL_RESPONSE
+
When I wait for new bind10 stderr message STATS_SEND_STATISTICS_REQUEST
# make sure Auth module receives a command
And wait for new bind10 stderr message AUTH_RECEIVED_COMMAND
@@ -288,6 +353,10 @@ Feature: Querying feature
A query for example.org type SSHFP should have rcode NOERROR
The last query response should have ancount 0
+ # Make sure handling statistics command handling checked below is
+ # after this query
+ And wait for bind10 stderr message AUTH_SEND_NORMAL_RESPONSE
+
When I wait for new bind10 stderr message STATS_SEND_STATISTICS_REQUEST
# make sure Auth module receives a command
And wait for new bind10 stderr message AUTH_RECEIVED_COMMAND
@@ -313,6 +382,9 @@ Feature: Querying feature
"""
shell.example.org. 3600 IN SSHFP 2 1 123456789abcdef67890123456789abcdef67890
"""
+ # Make sure handling statistics command handling checked below is
+ # after this query
+ And wait for bind10 stderr message AUTH_SEND_NORMAL_RESPONSE
When I wait for new bind10 stderr message STATS_SEND_STATISTICS_REQUEST
# make sure Auth module receives a command
diff --git a/tests/lettuce/features/terrain/bind10_control.py b/tests/lettuce/features/terrain/bind10_control.py
index 5aab431..e288aa9 100644
--- a/tests/lettuce/features/terrain/bind10_control.py
+++ b/tests/lettuce/features/terrain/bind10_control.py
@@ -120,13 +120,15 @@ def have_bind10_running(step, config_file, cmdctl_port, process_name):
step.given(start_step)
# function to send lines to bindctl, and store the result
-def run_bindctl(commands, cmdctl_port=None):
+def run_bindctl(commands, cmdctl_port=None, ignore_failure=False):
"""Run bindctl.
Parameters:
commands: a sequence of strings which will be sent.
cmdctl_port: a port number on which cmdctl is listening, is converted
to string if necessary. If not provided, or None, defaults
to 47805
+ ignore_failure(bool): if set to True, don't examin the result code
+ of bindctl and assert it succeeds.
bindctl's stdout and stderr streams are stored (as one multiline string
in world.last_bindctl_stdout/stderr.
@@ -140,6 +142,8 @@ def run_bindctl(commands, cmdctl_port=None):
for line in commands:
bindctl.stdin.write(line + "\n")
(stdout, stderr) = bindctl.communicate()
+ if ignore_failure:
+ return
result = bindctl.returncode
world.last_bindctl_stdout = stdout
world.last_bindctl_stderr = stderr
@@ -306,19 +310,25 @@ def config_remove_command(step, name, value, cmdctl_port):
"quit"]
run_bindctl(commands, cmdctl_port)
- at step('send bind10(?: with cmdctl port (\d+))? the command (.+)')
-def send_command(step, cmdctl_port, command):
+ at step('send bind10(?: with cmdctl port (\d+))?( ignoring failure)? the command (.+)')
+def send_command(step, cmdctl_port, ignore_failure, command):
"""
Run bindctl, send the given command, and exit bindctl.
Parameters:
command ('the command <command>'): The command to send.
cmdctl_port ('with cmdctl port <portnr>', optional): cmdctl port to send
the command to. Defaults to 47805.
- Fails if cmdctl does not exit with status code 0.
+ ignore_failure ('ignoring failure', optional): set to None if bindctl
+ is expected to succeed (normal case, which is the default); if it is
+ not None, it means bindctl is expected to fail (and it's acceptable).
+
+ Fails if bindctl does not exit with status code 0 and ignore_failure
+ is not None.
+
"""
commands = [command,
"quit"]
- run_bindctl(commands, cmdctl_port)
+ run_bindctl(commands, cmdctl_port, ignore_failure is not None)
@step('bind10 module (\S+) should( not)? be running')
def module_is_running(step, name, not_str):
@@ -379,67 +389,6 @@ def query_statistics(step, statistics, name, cmdctl_port):
% (port_str, name,\
' name=%s' % statistics if statistics else ''))
-def find_value(dictionary, key):
- """A helper method. Recursively find a value corresponding to the
- key of the dictionary and returns it. Returns None if the
- dictionary is not dict type."""
- if type(dictionary) is not dict:
- return
- if key in dictionary:
- return dictionary[key]
- else:
- for v in dictionary.values():
- return find_value(v, key)
-
- at step('the statistics counter (\S+)(?: in the category (\S+))?'+ \
- '(?: for the zone (\S+))? should be' + \
- '(?:( greater than| less than| between))? (\-?\d+)(?: and (\-?\d+))?')
-def check_statistics(step, counter, category, zone, gtltbt, number, upper):
- """
- check the output of bindctl for statistics of specified counter
- and zone.
- Parameters:
- counter ('counter <counter>'): The counter name of statistics.
- category ('category <category>', optional): The category of counter.
- zone ('zone <zone>', optional): The zone name.
- gtltbt (' greater than'|' less than'|' between', optional): greater than
- <number> or less than <number> or between <number> and <upper>.
- number ('<number>): The expect counter number. <number> is assumed
- to be an unsigned integer.
- upper ('<upper>, optional): The expect upper counter number when
- using 'between'.
- """
- output = parse_bindctl_output_as_data_structure()
- found = None
- category_str = ""
- zone_str = ""
- depth = []
- if category:
- depth.insert(0, category)
- category_str = " for category %s" % category
- if zone:
- depth.insert(0, zone)
- zone_str = " for zone %s" % zone
- for level in depth:
- output = find_value(output, level)
- found = find_value(output, counter)
- assert found is not None, \
- 'Not found statistics counter %s%s%s' % \
- (counter, category_str, zone_str)
- msg = "Got %s, expected%s %s as counter %s%s" % \
- (found, gtltbt, number, counter, zone_str)
- if gtltbt and 'between' in gtltbt and upper:
- msg = "Got %s, expected%s %s and %s as counter %s%s" % \
- (found, gtltbt, number, upper, counter, zone_str)
- assert int(number) <= int(found) \
- and int(found) <= int(upper), msg
- elif gtltbt and 'greater' in gtltbt:
- assert int(found) > int(number), msg
- elif gtltbt and 'less' in gtltbt:
- assert int(found) < int(number), msg
- else:
- assert int(found) == int(number), msg
-
@step('statistics counters are 0 in category (\S+)( except for the' + \
' following items)?')
def check_statistics_items(step, category, has_except_for):
@@ -451,9 +400,14 @@ def check_statistics_items(step, category, has_except_for):
with the multiline part.
Expected values of items are taken from the multiline part of the step in
- the scenario. The multiline part has two columns: item_name and item_value.
- item_name is a relative name to category. item_value is an expected value
- for item_name.
+ the scenario. The multiline part has at most four columns: item_name,
+ item_value, min_value, and max_value. item_name is a relative name
+ to category. item_value is an expected value for
+ item_name. min_value and max_value are expected to be used when
+ item_value cannot be specified to be item_value. min_value is the
+ minimum value in the expected range, and max_value is the maximum
+ value in the expected range. Values would be examined if they are
+ in columns corresponding to these.
"""
def flatten(dictionary, prefix=''):
@@ -470,15 +424,55 @@ def check_statistics_items(step, category, has_except_for):
# fetch step tables in the scnario as hashes
for item in step.hashes:
name = category+'.'+item['item_name']
- value = item['item_value']
assert stats.has_key(name), \
'Statistics item %s was not found' % (name)
found = stats[name]
- assert int(found) == int(value), \
- 'Statistics item %s has unexpected value %s (expect %s)' % \
+ if 'item_value' in item and item['item_value']:
+ value = item['item_value']
+ assert int(found) == int(value), \
+ 'Statistics item %s has unexpected value %s (expect %s)' % \
+ (name, found, value)
+ if 'min_value' in item and item['min_value']:
+ value = item['min_value']
+ assert float(value) <= float(found), \
+ 'Statistics item %s has unexpected value %s (expect %s or greater than)' % \
+ (name, found, value)
+ if 'max_value' in item and item['max_value']:
+ value = item['max_value']
+ assert float(found) <= float(value), \
+ 'Statistics item %s has unexpected value %s (expect %s or less than)' % \
(name, found, value)
del(stats[name])
for name, found in stats.items():
assert int(found) == 0, \
'Statistics item %s has unexpected value %s (expect %s)' % \
(name, found, 0)
+
+ at step('check initial statistics(?:( not)? containing (\S+))? for (\S+)'
+ '( with cmdctl port \d+)?( except for the following items)?')
+def check_init_statistics(step, notv, string, name, cmdctl_port, has_except_for):
+ """Checks the initial statistics for the module. Also checks a
+ string is contained or not contained in them. Statistics counters
+ other than zero can follow below.
+ Parameters:
+ notv ('not'): reverse the check (fail if string is found)
+ string ('containing <string>') string to look for
+ name ('module <name>'): The name of the module (case sensitive!)
+ cmdctl_port ('with cmdctl port <portnr>', optional): cmdctl port to send
+ the command to.
+ has_except_for ('except for the following items'): checks values of items
+ with the multiline part.
+ """
+ query_str = 'query statistics of bind10 module ' + name
+ if cmdctl_port:
+ query_str = query_str + cmdctl_port
+ notcontain_str = 'last bindctl output should%s contain "%s"'
+ check_str = 'statistics counters are 0 in category .' + name
+ if has_except_for:
+ check_str = check_str + has_except_for + "\n" \
+ + step.represent_hashes()
+ step.given(query_str)
+ step.given(notcontain_str % (' not', 'error'))
+ if string is not None:
+ step.given(notcontain_str % (notv, string))
+ step.given(check_str)
diff --git a/tests/lettuce/features/terrain/steps.py b/tests/lettuce/features/terrain/steps.py
index e470acf..e41bb6d 100644
--- a/tests/lettuce/features/terrain/steps.py
+++ b/tests/lettuce/features/terrain/steps.py
@@ -37,8 +37,10 @@ def wait_for_stderr_message(step, times, new, process_name, message, not_message
output.
Parameter:
times: Check for the string this many times.
- new: (' new', optional): Only check the output printed since last time
- this step was used for this process.
+ new: (' new', optional): Only check the output from the process that has
+ not been covered in previous calls to this
+ function. See RunningProcess._wait_for_output_str
+ for details.
process_name ('<name> stderr'): Name of the process to check the output of.
message ('message <message>'): Output (part) to wait for.
not_message ('not <message>'): Output (part) to wait for, and fail
@@ -60,8 +62,10 @@ def wait_for_stdout_message(step, times, new, process_name, message, not_message
output.
Parameter:
times: Check for the string this many times.
- new: (' new', optional): Only check the output printed since last time
- this step was used for this process.
+ new: (' new', optional): Only check the output from the process that has
+ not been covered in previous calls to this
+ function. See RunningProcess._wait_for_output_str
+ for details.
process_name ('<name> stderr'): Name of the process to check the output of.
message ('message <message>'): Output (part) to wait for, and succeed.
not_message ('not <message>'): Output (part) to wait for, and fail
diff --git a/tests/lettuce/features/terrain/terrain.py b/tests/lettuce/features/terrain/terrain.py
index ce7426b..b861442 100644
--- a/tests/lettuce/features/terrain/terrain.py
+++ b/tests/lettuce/features/terrain/terrain.py
@@ -26,12 +26,16 @@
from lettuce import *
import subprocess
-import os.path
+import os
import shutil
import re
import sys
import time
+# lettuce cannot directly pass commands to the terrain, so we need to
+# use environment variables to influence behaviour
+KEEP_OUTPUT = 'LETTUCE_KEEP_OUTPUT'
+
# In order to make sure we start all tests with a 'clean' environment,
# We perform a number of initialization steps, like restoring configuration
# files, and removing generated data files.
@@ -64,12 +68,18 @@ copylist = [
"configurations/ddns/noddns.config"],
["configurations/xfrin/retransfer_master.conf.orig",
"configurations/xfrin/retransfer_master.conf"],
+ ["configurations/xfrin/retransfer_master_v4.conf.orig",
+ "configurations/xfrin/retransfer_master_v4.conf"],
["configurations/xfrin/retransfer_master_nons.conf.orig",
"configurations/xfrin/retransfer_master_nons.conf"],
["configurations/xfrin/retransfer_slave.conf.orig",
"configurations/xfrin/retransfer_slave.conf"],
+ ["configurations/xfrin/retransfer_slave_notify.conf.orig",
+ "configurations/xfrin/retransfer_slave_notify.conf"],
["data/inmem-xfrin.sqlite3.orig",
"data/inmem-xfrin.sqlite3"],
+ ["data/xfrin-before-diffs.sqlite3.orig",
+ "data/xfrin-before-diffs.sqlite3"],
["data/xfrin-notify.sqlite3.orig",
"data/xfrin-notify.sqlite3"],
["data/ddns/example.org.sqlite3.orig",
@@ -105,11 +115,15 @@ class RunningProcess:
self.process = None
self.step = step
self.process_name = process_name
- self.remove_files_on_exit = True
+ self.remove_files_on_exit = (os.environ.get(KEEP_OUTPUT) != '1')
self._check_output_dir()
self._create_filenames()
self._start_process(args)
+ # used in _wait_for_output_str, map from (filename, (strings))
+ # to a file offset.
+ self.__file_offsets = {}
+
def _start_process(self, args):
"""
Start the process.
@@ -190,11 +204,29 @@ class RunningProcess:
os.remove(self.stderr_filename)
os.remove(self.stdout_filename)
- def _wait_for_output_str(self, filename, running_file, strings, only_new, matches = 1):
- """
- Wait for a line of output in this process. This will (if only_new is
- False) first check all previous output from the process, and if not
- found, check all output since the last time this method was called.
+ def _wait_for_output_str(self, filename, running_file, strings, only_new,
+ matches=1):
+ """
+ Wait for a line of output in this process. This will (if
+ only_new is False) check all output from the process including
+ that may have been checked before. If only_new is True, it
+ only checks output that has not been covered in previous calls
+ to this method for the file (if there was no such previous call to
+ this method, it works same as the case of only_new=False).
+
+ Care should be taken if only_new is to be set to True, as it may cause
+ counter-intuitive results. For example, assume the file is expected
+ to contain a line that has XXX and another line has YYY, but the
+ ordering is not predictable. If this method is called with XXX as
+ the search string, but the line containing YYY appears before the
+ target line, this method remembers the point in the file beyond
+ the line that has XXX. If a next call to this method specifies
+ YYY as the search string with only_new being True, the search will
+ fail. If the same string is expected to appear multiple times
+ and you want to catch the latest one, a more reliable way is to
+ specify the match number and set only_new to False, if the number
+ of matches is predictable.
+
For each line in the output, the given strings array is checked. If
any output lines checked contains one of the strings in the strings
array, that string (not the line!) is returned.
@@ -202,33 +234,34 @@ class RunningProcess:
filename: The filename to read previous output from, if applicable.
running_file: The open file to read new output from.
strings: Array of strings to look for.
- only_new: If true, only check output since last time this method was
- called. If false, first check earlier output.
+ only_new: See above.
matches: Check for the string this many times.
Returns a tuple containing the matched string, and the complete line
it was found in.
Fails if none of the strings was read after 10 seconds
(OUTPUT_WAIT_INTERVAL * OUTPUT_WAIT_MAX_INTERVALS).
"""
+ # Identify the start offset of search. if only_new=True, start from
+ # the farthest point we've reached in the file; otherwise start from
+ # the beginning.
+ if not filename in self.__file_offsets:
+ self.__file_offsets[filename] = 0
+ offset = self.__file_offsets[filename] if only_new else 0
+ running_file.seek(offset)
+
match_count = 0
- if not only_new:
- full_file = open(filename, "r")
- for line in full_file:
- for string in strings:
- if line.find(string) != -1:
- match_count += 1
- if match_count >= matches:
- full_file.close()
- return (string, line)
wait_count = 0
while wait_count < OUTPUT_WAIT_MAX_INTERVALS:
- where = running_file.tell()
line = running_file.readline()
+ where = running_file.tell()
if line:
for string in strings:
if line.find(string) != -1:
match_count += 1
if match_count >= matches:
+ # If we've gone further, update the recorded offset
+ if where > self.__file_offsets[filename]:
+ self.__file_offsets[filename] = where
return (string, line)
else:
wait_count += 1
@@ -241,8 +274,7 @@ class RunningProcess:
Wait for one of the given strings in this process's stderr output.
Parameters:
strings: Array of strings to look for.
- only_new: If true, only check output since last time this method was
- called. If false, first check earlier output.
+ only_new: See _wait_for_output_str.
matches: Check for the string this many times.
Returns a tuple containing the matched string, and the complete line
it was found in.
@@ -257,8 +289,7 @@ class RunningProcess:
Wait for one of the given strings in this process's stdout output.
Parameters:
strings: Array of strings to look for.
- only_new: If true, only check output since last time this method was
- called. If false, first check earlier output.
+ only_new: See _wait_for_output_str.
matches: Check for the string this many times.
Returns a tuple containing the matched string, and the complete line
it was found in.
@@ -338,8 +369,7 @@ class RunningProcesses:
Parameters:
process_name: The name of the process to check the stderr output of.
strings: Array of strings to look for.
- only_new: If true, only check output since last time this method was
- called. If false, first check earlier output.
+ only_new: See _wait_for_output_str.
matches: Check for the string this many times.
Returns the matched string.
Fails if none of the strings was read after 10 seconds
@@ -358,8 +388,7 @@ class RunningProcesses:
Parameters:
process_name: The name of the process to check the stdout output of.
strings: Array of strings to look for.
- only_new: If true, only check output since last time this method was
- called. If false, first check earlier output.
+ only_new: See _wait_for_output_str.
matches: Check for the string this many times.
Returns the matched string.
Fails if none of the strings was read after 10 seconds
diff --git a/tests/lettuce/features/xfrin_bind10.feature b/tests/lettuce/features/xfrin_bind10.feature
index 7ba1ca0..90a1144 100644
--- a/tests/lettuce/features/xfrin_bind10.feature
+++ b/tests/lettuce/features/xfrin_bind10.feature
@@ -33,7 +33,10 @@ Feature: Xfrin
And wait for new bind10 stderr message XFRIN_ZONE_WARN
# But after complaining, the zone data should be accepted.
Then wait for new bind10 stderr message XFRIN_TRANSFER_SUCCESS not XFRIN_XFR_PROCESS_FAILURE
- Then wait for new bind10 stderr message ZONEMGR_RECEIVE_XFRIN_SUCCESS
+ # there's no guarantee this is logged before XFRIN_TRANSFER_SUCCESS, so
+ # we can't reliably use 'wait for new'. In this case this should be the
+ # only occurrence of this message, so this should be okay.
+ Then wait for bind10 stderr message ZONEMGR_RECEIVE_XFRIN_SUCCESS
A query for www.example.org to [::1]:47806 should have rcode NOERROR
# The transferred zone should have 11 non-NSEC3 RRs and 1 NSEC3 RR.
@@ -56,7 +59,8 @@ Feature: Xfrin
Then I send bind10 the command Xfrin retransfer example.org IN ::1 47807
And wait for new bind10 stderr message XFRIN_ZONE_INVALID
And wait for new bind10 stderr message XFRIN_INVALID_ZONE_DATA
- Then wait for new bind10 stderr message ZONEMGR_RECEIVE_XFRIN_FAILED
+ # We can't use 'wait for new' here; see above.
+ Then wait for bind10 stderr message ZONEMGR_RECEIVE_XFRIN_FAILED
A query for example.org type NS to [::1]:47806 should have rcode NOERROR
And transfer result should have 13 rrs
@@ -82,7 +86,8 @@ Feature: Xfrin
# Make sure it is fully open
When I send bind10 the command Xfrin retransfer example.org
Then wait for new bind10 stderr message XFRIN_TRANSFER_SUCCESS not XFRIN_XFR_PROCESS_FAILURE
- And wait for new bind10 stderr message ZONEMGR_RECEIVE_XFRIN_SUCCESS
+ # this can't be 'wait for new'; see above.
+ And wait for bind10 stderr message ZONEMGR_RECEIVE_XFRIN_SUCCESS
# First to master, a transfer should then fail
When I send bind10 the following commands with cmdctl port 47804:
@@ -139,8 +144,52 @@ Feature: Xfrin
# zone is invalid and then reject it.
And wait for new bind10 stderr message XFRIN_ZONE_INVALID
And wait for new bind10 stderr message XFRIN_INVALID_ZONE_DATA
- Then wait for new bind10 stderr message ZONEMGR_RECEIVE_XFRIN_FAILED
+ # This can't be 'wait for new'
+ Then wait for bind10 stderr message ZONEMGR_RECEIVE_XFRIN_FAILED
# The zone still doesn't exist as it is rejected.
# FIXME: This step fails. Probably an empty zone is created in the data
# source :-|. This should be REFUSED, not SERVFAIL.
A query for www.example.org to [::1]:47806 should have rcode SERVFAIL
+
+ # TODO:
+ # * IXFR - generate an sqlite db that contains the journal. Check it was
+ # IXFR by logs.
+ # * IXFR->AXFR fallback if IXFR is not available (even rejected or
+ # something, not just the differences missing).
+ # * Retransfer with short refresh time (without notify).
+ Scenario: With differences
+ # We transfer from one bind10 to other, just like in the Retransfer command
+ # scenario. Just this time, the master contains the differences table
+ # and the slave has a previous version of the zone, so we use the IXFR.
+
+ Given I have bind10 running with configuration xfrin/retransfer_master_diffs.conf with cmdctl port 47804 as master
+ And wait for master stderr message BIND10_STARTED_CC
+ And wait for master stderr message CMDCTL_STARTED
+ And wait for master stderr message AUTH_SERVER_STARTED
+ And wait for master stderr message XFROUT_STARTED
+ And wait for master stderr message ZONEMGR_STARTED
+
+ And I have bind10 running with configuration xfrin/retransfer_slave_diffs.conf
+ 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
+ And wait for bind10 stderr message XFRIN_STARTED
+ And wait for bind10 stderr message ZONEMGR_STARTED
+
+ A query for example. type SOA to [::1]:47806 should have rcode NOERROR
+ The answer section of the last query response should be
+ """
+ example. 3600 IN SOA ns1.example. hostmaster.example. 94 3600 900 7200 300
+ """
+
+ When I send bind10 the command Xfrin retransfer example. IN ::1 47807
+ Then wait for new bind10 stderr message XFRIN_GOT_INCREMENTAL_RESP
+ Then wait for new bind10 stderr message XFRIN_IXFR_TRANSFER_SUCCESS not XFRIN_XFR_PROCESS_FAILURE
+ # This can't be 'wait for new'
+ Then wait for bind10 stderr message ZONEMGR_RECEIVE_XFRIN_SUCCESS
+
+ A query for example. type SOA to [::1]:47806 should have rcode NOERROR
+ The answer section of the last query response should be
+ """
+ example. 3600 IN SOA ns1.example. hostmaster.example. 100 3600 900 7200 300
+ """
diff --git a/tests/lettuce/features/xfrin_notify_handling.feature b/tests/lettuce/features/xfrin_notify_handling.feature
index e3f2642..375a8a9 100644
--- a/tests/lettuce/features/xfrin_notify_handling.feature
+++ b/tests/lettuce/features/xfrin_notify_handling.feature
@@ -1,5 +1,10 @@
Feature: Xfrin incoming notify handling
- Tests for Xfrin incoming notify handling.
+ Tests for Xfrin incoming notify handling. They also test
+ statistics counters incremented, which are related to notifying
+ and transferring by Xfrout and receiveing by Xfrin. Some cases are
+ considered: Transferring is done via IPv4 or IPv6 transport. A
+ transfer request from Xfrin is rejected by Xfrout. The master
+ server or slave server is unreachable.
Scenario: Handle incoming notify
Given I have bind10 running with configuration xfrin/retransfer_master.conf with cmdctl port 47804 as master
@@ -20,76 +25,193 @@ Feature: Xfrin incoming notify handling
A query for www.example.org to [::1]:47806 should have rcode NXDOMAIN
#
- # Test for statistics
+ # Test1 for Xfrout statistics
+ #
+ check initial statistics not containing example.org for Xfrout with cmdctl port 47804 except for the following items
+ | item_name | item_max | item_min |
+ | socket.unixdomain.open | 1 | 0 |
+ # Note: .Xfrout.socket.unixdomain.open can be either expected to
+ # be 0 or 1 here. The reason is: if b10-xfrout has started up and is
+ # ready for a request from b10-stats, then b10-stats does request
+ # to b10-xfrout and the value results in 1. Otherwise if
+ # b10-xfrout is starting and isn't yet ready, then b10-stats
+ # doesn't request to b10-xfrout and the value still remains to be the
+ # default value(0).
+
+ #
+ # Test2 for Xfrin statistics
+ #
+ check initial statistics not containing example.org for Xfrin
+
+ When I send bind10 with cmdctl port 47804 the command Xfrout notify example.org IN
+ Then wait for new master stderr message XFROUT_NOTIFY_COMMAND
+ Then wait for new bind10 stderr message AUTH_RECEIVED_NOTIFY
+ # From this point we can't reliably 'wait for new' because the ordering
+ # of logs from different processes is unpredictable. But these
+ # should be okay in this case.
+ Then wait for bind10 stderr message ZONEMGR_RECEIVE_NOTIFY
+ Then wait for bind10 stderr message XFRIN_XFR_TRANSFER_STARTED
+ Then wait for bind10 stderr message XFRIN_TRANSFER_SUCCESS not XFRIN_XFR_PROCESS_FAILURE
+ Then wait for bind10 stderr message ZONEMGR_RECEIVE_XFRIN_SUCCESS
+ Then wait for master stderr message NOTIFY_OUT_REPLY_RECEIVED
+
+ A query for www.example.org to [::1]:47806 should have rcode NOERROR
+ # Make sure handling statistics command handling checked below is
+ # after this query
+ And wait for bind10 stderr message AUTH_SEND_NORMAL_RESPONSE
+
#
- # check for initial statistics
+ # Test3 for Xfrout statistics
#
+ # check statistics change
+ #
+
+ # wait until the last stats requesting is finished
When I query statistics zones of bind10 module Xfrout with cmdctl port 47804
+ # note that this does not 100% guarantee the stats updated Xfrout
+ # statistics. But there doesn't seem to be a better log message that
+ # suggests this event.
+ wait for new master stderr message XFROUT_RECEIVED_GETSTATS_COMMAND
last bindctl output should not contain "error"
- last bindctl output should not contain "example.org."
- Then the statistics counter notifyoutv4 for the zone _SERVER_ should be 0
- Then the statistics counter notifyoutv6 for the zone _SERVER_ should be 0
- Then the statistics counter xfrrej for the zone _SERVER_ should be 0
- Then the statistics counter xfrreqdone for the zone _SERVER_ should be 0
-
- When I query statistics ixfr_running of bind10 module Xfrout with cmdctl port 47804
- Then the statistics counter ixfr_running should be 0
- When I query statistics axfr_running of bind10 module Xfrout with cmdctl port 47804
- Then the statistics counter axfr_running should be 0
+ When I query statistics zones of bind10 module Xfrout with cmdctl port 47804
+ The statistics counters are 0 in category .Xfrout.zones except for the following items
+ | item_name | item_value |
+ | _SERVER_.notifyoutv6 | 1 |
+ | _SERVER_.xfrreqdone | 1 |
+ | example.org..notifyoutv6 | 1 |
+ | example.org..xfrreqdone | 1 |
When I query statistics socket of bind10 module Xfrout with cmdctl port 47804
- Then the statistics counter open should be between 0 and 1
- Then the statistics counter openfail should be 0
- Then the statistics counter close should be 0
- Then the statistics counter bindfail should be 0
- Then the statistics counter acceptfail should be 0
- Then the statistics counter accept should be 0
- Then the statistics counter senderr should be 0
- Then the statistics counter recverr should be 0
+ The statistics counters are 0 in category .Xfrout.socket.unixdomain except for the following items
+ | item_name | item_value |
+ | open | 1 |
+ | accept | 1 |
+
+ #
+ # Test4 for Xfrin statistics
+ #
+ # check statistics change
+ #
+
+ # wait until the last stats requesting is finished
+ When I query statistics zones of bind10 module Xfrin with cmdctl
+ wait for new bind10 stderr message XFRIN_RECEIVED_COMMAND
+ last bindctl output should not contain "error"
+
+ When I query statistics zones of bind10 module Xfrin with cmdctl
+ The statistics counters are 0 in category .Xfrin.zones except for the following items
+ | item_name | item_value | min_value |
+ | _SERVER_.soaoutv6 | 1 | |
+ | _SERVER_.axfrreqv6 | 1 | |
+ | _SERVER_.xfrsuccess | 1 | |
+ | _SERVER_.last_axfr_duration | | 0.0 |
+ | example.org..soaoutv6 | 1 | |
+ | example.org..axfrreqv6 | 1 | |
+ | example.org..xfrsuccess | 1 | |
+ | example.org..last_axfr_duration | | 0.0 |
+
+ #
+ # Test for handling incoming notify only in IPv4
+ #
+ Scenario: Handle incoming notify (IPv4)
+ Given I have bind10 running with configuration xfrin/retransfer_master_v4.conf with cmdctl port 47804 as master
+ And wait for master stderr message BIND10_STARTED_CC
+ And wait for master stderr message CMDCTL_STARTED
+ And wait for master stderr message AUTH_SERVER_STARTED
+ And wait for master stderr message XFROUT_STARTED
+ And wait for master stderr message ZONEMGR_STARTED
+ And wait for master stderr message STATS_STARTING
+
+ And I have bind10 running with configuration xfrin/retransfer_slave_notify_v4.conf
+ 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
+ And wait for bind10 stderr message XFRIN_STARTED
+ And wait for bind10 stderr message ZONEMGR_STARTED
+
+ A query for www.example.org to 127.0.0.1:47806 should have rcode NXDOMAIN
+
+ #
+ # Test1 for Xfrout statistics
+ #
+ check initial statistics not containing example.org for Xfrout with cmdctl port 47804 except for the following items
+ | item_name | item_max | item_min |
+ | socket.unixdomain.open | 1 | 0 |
+ # Note: See above about .Xfrout.socket.unixdomain.open.
+
+ #
+ # Test2 for Xfrin statistics
+ #
+ check initial statistics not containing example.org for Xfrin
When I send bind10 with cmdctl port 47804 the command Xfrout notify example.org IN
Then wait for new master stderr message XFROUT_NOTIFY_COMMAND
Then wait for new bind10 stderr message AUTH_RECEIVED_NOTIFY
- Then wait for new bind10 stderr message ZONEMGR_RECEIVE_NOTIFY
- Then wait for new bind10 stderr message XFRIN_XFR_TRANSFER_STARTED
- Then wait for new bind10 stderr message XFRIN_TRANSFER_SUCCESS not XFRIN_XFR_PROCESS_FAILURE
- Then wait for new bind10 stderr message ZONEMGR_RECEIVE_XFRIN_SUCCESS
- Then wait 5 times for new master stderr message NOTIFY_OUT_SENDING_NOTIFY
- Then wait for new master stderr message NOTIFY_OUT_RETRY_EXCEEDED
-
- A query for www.example.org to [::1]:47806 should have rcode NOERROR
+ # From this point we can't reliably 'wait for new' because the ordering
+ # of logs from different processes is unpredictable. But these
+ # should be okay in this case.
+ Then wait for bind10 stderr message ZONEMGR_RECEIVE_NOTIFY
+ Then wait for bind10 stderr message XFRIN_XFR_TRANSFER_STARTED
+ Then wait for bind10 stderr message XFRIN_TRANSFER_SUCCESS not XFRIN_XFR_PROCESS_FAILURE
+ Then wait for bind10 stderr message ZONEMGR_RECEIVE_XFRIN_SUCCESS
+ Then wait for master stderr message NOTIFY_OUT_REPLY_RECEIVED
+
+ A query for www.example.org to 127.0.0.1:47806 should have rcode NOERROR
+ # Make sure handling statistics command handling checked below is
+ # after this query
+ And wait for bind10 stderr message AUTH_SEND_NORMAL_RESPONSE
#
- # Test for statistics
+ # Test3 for Xfrout statistics
#
- # check for statistics change
+ # check statistics change
#
# wait until the last stats requesting is finished
- wait for new master stderr message STATS_SEND_STATISTICS_REQUEST
+ When I query statistics zones of bind10 module Xfrout with cmdctl port 47804
+ # note that this does not 100% guarantee the stats updated Xfrout
+ # statistics. But there doesn't seem to be a better log message that
+ # suggests this event.
wait for new master stderr message XFROUT_RECEIVED_GETSTATS_COMMAND
+ last bindctl output should not contain "error"
When I query statistics zones of bind10 module Xfrout with cmdctl port 47804
- last bindctl output should not contain "error"
- Then the statistics counter notifyoutv4 for the zone _SERVER_ should be 0
- Then the statistics counter notifyoutv4 for the zone example.org. should be 0
- Then the statistics counter notifyoutv6 for the zone _SERVER_ should be 5
- Then the statistics counter notifyoutv6 for the zone example.org. should be 5
- Then the statistics counter xfrrej for the zone _SERVER_ should be 0
- Then the statistics counter xfrrej for the zone example.org. should be 0
- Then the statistics counter xfrreqdone for the zone _SERVER_ should be 1
- Then the statistics counter xfrreqdone for the zone example.org. should be 1
+ The statistics counters are 0 in category .Xfrout.zones except for the following items
+ | item_name | item_value |
+ | _SERVER_.notifyoutv4 | 1 |
+ | _SERVER_.xfrreqdone | 1 |
+ | example.org..notifyoutv4 | 1 |
+ | example.org..xfrreqdone | 1 |
When I query statistics socket of bind10 module Xfrout with cmdctl port 47804
- Then the statistics counter open should be 1
- Then the statistics counter openfail should be 0
- Then the statistics counter close should be 0
- Then the statistics counter bindfail should be 0
- Then the statistics counter acceptfail should be 0
- Then the statistics counter accept should be 1
- Then the statistics counter senderr should be 0
- Then the statistics counter recverr should be 0
+ The statistics counters are 0 in category .Xfrout.socket.unixdomain except for the following items
+ | item_name | item_value |
+ | open | 1 |
+ | accept | 1 |
+
+ #
+ # Test4 for Xfrin statistics
+ #
+ # check statistics change
+ #
+
+ # wait until the last stats requesting is finished
+ When I query statistics zones of bind10 module Xfrin with cmdctl
+ wait for new bind10 stderr message XFRIN_RECEIVED_COMMAND
+ last bindctl output should not contain "error"
+
+ When I query statistics zones of bind10 module Xfrin with cmdctl
+ The statistics counters are 0 in category .Xfrin.zones except for the following items
+ | item_name | item_value | min_value |
+ | _SERVER_.soaoutv4 | 1 | |
+ | _SERVER_.axfrreqv4 | 1 | |
+ | _SERVER_.xfrsuccess | 1 | |
+ | _SERVER_.last_axfr_duration | | 0.0 |
+ | example.org..soaoutv4 | 1 | |
+ | example.org..axfrreqv4 | 1 | |
+ | example.org..xfrsuccess | 1 | |
+ | example.org..last_axfr_duration | | 0.0 |
#
# Test for Xfr request rejected
@@ -113,33 +235,123 @@ Feature: Xfrin incoming notify handling
A query for www.example.org to [::1]:47806 should have rcode NXDOMAIN
#
- # Test1 for statistics
+ # Test1 for Xfrout statistics
+ #
+ check initial statistics not containing example.org for Xfrout with cmdctl port 47804 except for the following items
+ | item_name | item_max | item_min |
+ | socket.unixdomain.open | 1 | 0 |
+ # Note: See above about .Xfrout.socket.unixdomain.open.
+
+ #
+ # Test2 for Xfrin statistics
+ #
+ check initial statistics not containing example.org for Xfrin
+
+ #
+ # set transfer_acl rejection
+ # Local xfr requests from Xfrin module would be rejected here.
+ #
+ When I send bind10 the following commands with cmdctl port 47804
+ """
+ config set Xfrout/zone_config[0]/transfer_acl [{"action": "REJECT", "from": "::1"}]
+ config commit
+ """
+ last bindctl output should not contain Error
+
+ When I send bind10 with cmdctl port 47804 the command Xfrout notify example.org IN
+ Then wait for new master stderr message XFROUT_NOTIFY_COMMAND
+ Then wait for new bind10 stderr message AUTH_RECEIVED_NOTIFY
+ # can't use 'wait for new' below.
+ Then wait for bind10 stderr message ZONEMGR_RECEIVE_NOTIFY
+ Then wait for bind10 stderr message XFRIN_XFR_TRANSFER_STARTED
+ Then wait for bind10 stderr message XFRIN_XFR_TRANSFER_PROTOCOL_VIOLATION not XFRIN_TRANSFER_SUCCESS
+ Then wait for bind10 stderr message ZONEMGR_RECEIVE_XFRIN_FAILED not ZONEMGR_RECEIVE_XFRIN_SUCCESS
+ Then wait for master stderr message NOTIFY_OUT_REPLY_RECEIVED
+
+ A query for www.example.org to [::1]:47806 should have rcode NXDOMAIN
+
+ #
+ # Test3 for Xfrout statistics
#
- # check for initial statistics
+ # check statistics change
#
+
+ # wait until the last stats requesting is finished
When I query statistics zones of bind10 module Xfrout with cmdctl port 47804
+ wait for new master stderr message XFROUT_RECEIVED_GETSTATS_COMMAND
last bindctl output should not contain "error"
- last bindctl output should not contain "example.org."
- Then the statistics counter notifyoutv4 for the zone _SERVER_ should be 0
- Then the statistics counter notifyoutv6 for the zone _SERVER_ should be 0
- Then the statistics counter xfrrej for the zone _SERVER_ should be 0
- Then the statistics counter xfrreqdone for the zone _SERVER_ should be 0
- When I query statistics ixfr_running of bind10 module Xfrout with cmdctl port 47804
- Then the statistics counter ixfr_running should be 0
-
- When I query statistics axfr_running of bind10 module Xfrout with cmdctl port 47804
- Then the statistics counter axfr_running should be 0
+ When I query statistics zones of bind10 module Xfrout with cmdctl port 47804
+ The statistics counters are 0 in category .Xfrout.zones except for the following items
+ | item_name | item_value | min_value | max_value |
+ | _SERVER_.notifyoutv6 | 1 | | |
+ | _SERVER_.xfrrej | | 1 | 3 |
+ | example.org..notifyoutv6 | 1 | | |
+ | example.org..xfrrej | | 1 | 3 |
+ # Note: The above rejection counters might sometimes be increased
+ # up to 3. See this for details
+ # http://git.bind10.isc.org/~tester/builder/BIND10-lettuce/20120918210000-MacOS/logs/lettuce.out
When I query statistics socket of bind10 module Xfrout with cmdctl port 47804
- Then the statistics counter open should be between 0 and 1
- Then the statistics counter openfail should be 0
- Then the statistics counter close should be 0
- Then the statistics counter bindfail should be 0
- Then the statistics counter acceptfail should be 0
- Then the statistics counter accept should be 0
- Then the statistics counter senderr should be 0
- Then the statistics counter recverr should be 0
+ The statistics counters are 0 in category .Xfrout.socket.unixdomain except for the following items
+ | item_name | item_value |
+ | open | 1 |
+ | accept | 1 |
+
+ #
+ # Test4 for Xfrin statistics
+ #
+ # check statistics change
+ #
+
+ # wait until the last stats requesting is finished
+ When I query statistics zones of bind10 module Xfrin with cmdctl
+ wait for new bind10 stderr message XFRIN_RECEIVED_COMMAND
+ last bindctl output should not contain "error"
+
+ When I query statistics zones of bind10 module Xfrin with cmdctl
+ The statistics counters are 0 in category .Xfrin.zones except for the following items
+ | item_name | item_value |
+ | _SERVER_.soaoutv6 | 1 |
+ | _SERVER_.axfrreqv6 | 1 |
+ | _SERVER_.xfrfail | 1 |
+ | example.org..soaoutv6 | 1 |
+ | example.org..axfrreqv6 | 1 |
+ | example.org..xfrfail | 1 |
+
+ #
+ # Test for Xfr request rejected in IPv4
+ #
+ Scenario: Handle incoming notify (XFR request rejected in IPv4)
+ Given I have bind10 running with configuration xfrin/retransfer_master_v4.conf with cmdctl port 47804 as master
+ And wait for master stderr message BIND10_STARTED_CC
+ And wait for master stderr message CMDCTL_STARTED
+ And wait for master stderr message AUTH_SERVER_STARTED
+ And wait for master stderr message XFROUT_STARTED
+ And wait for master stderr message ZONEMGR_STARTED
+ And wait for master stderr message STATS_STARTING
+
+ And I have bind10 running with configuration xfrin/retransfer_slave_notify_v4.conf
+ 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
+ And wait for bind10 stderr message XFRIN_STARTED
+ And wait for bind10 stderr message ZONEMGR_STARTED
+
+ A query for www.example.org to 127.0.0.1:47806 should have rcode NXDOMAIN
+
+ #
+ # Test1 for Xfrout statistics
+ #
+ check initial statistics not containing example.org for Xfrout with cmdctl port 47804 except for the following items
+ | item_name | item_max | item_min |
+ | socket.unixdomain.open | 1 | 0 |
+ # Note: See above about .Xfrout.socket.unixdomain.open.
+
+ #
+ # Test2 for Xfrin statistics
+ #
+ check initial statistics not containing example.org for Xfrin
#
# set transfer_acl rejection
@@ -147,7 +359,7 @@ Feature: Xfrin incoming notify handling
#
When I send bind10 the following commands with cmdctl port 47804
"""
- config set Xfrout/zone_config[0]/transfer_acl [{"action": "REJECT", "from": "::1"}]
+ config set Xfrout/zone_config[0]/transfer_acl [{"action": "REJECT", "from": "127.0.0.1"}]
config commit
"""
last bindctl output should not contain Error
@@ -155,47 +367,63 @@ Feature: Xfrin incoming notify handling
When I send bind10 with cmdctl port 47804 the command Xfrout notify example.org IN
Then wait for new master stderr message XFROUT_NOTIFY_COMMAND
Then wait for new bind10 stderr message AUTH_RECEIVED_NOTIFY
- Then wait for new bind10 stderr message ZONEMGR_RECEIVE_NOTIFY
- Then wait for new bind10 stderr message XFRIN_XFR_TRANSFER_STARTED
- Then wait for new bind10 stderr message XFRIN_XFR_TRANSFER_PROTOCOL_VIOLATION not XFRIN_XFR_TRANSFER_STARTED
- Then wait for new bind10 stderr message ZONEMGR_RECEIVE_XFRIN_FAILED not ZONEMGR_RECEIVE_XFRIN_SUCCESS
- Then wait 5 times for new master stderr message NOTIFY_OUT_SENDING_NOTIFY
- Then wait for new master stderr message NOTIFY_OUT_RETRY_EXCEEDED
+ # can't use 'wait for new' below.
+ Then wait for bind10 stderr message ZONEMGR_RECEIVE_NOTIFY
+ Then wait for bind10 stderr message XFRIN_XFR_TRANSFER_STARTED
+ Then wait for bind10 stderr message XFRIN_XFR_TRANSFER_PROTOCOL_VIOLATION not XFRIN_TRANSFER_SUCCESS
+ Then wait for bind10 stderr message ZONEMGR_RECEIVE_XFRIN_FAILED not ZONEMGR_RECEIVE_XFRIN_SUCCESS
+ Then wait for master stderr message NOTIFY_OUT_REPLY_RECEIVED
- A query for www.example.org to [::1]:47806 should have rcode NXDOMAIN
+ A query for www.example.org to 127.0.0.1:47806 should have rcode NXDOMAIN
#
- # Test2 for statistics
+ # Test3 for Xfrout statistics
#
- # check for statistics change
+ # check statistics change
#
- # wait until the last stats requesting is finished
- wait for new master stderr message STATS_SEND_STATISTICS_REQUEST
+ When I query statistics zones of bind10 module Xfrout with cmdctl port 47804
+ # wait until stats request at least after NOTIFY_OUT_REPLY_RECEIVED
wait for new master stderr message XFROUT_RECEIVED_GETSTATS_COMMAND
+ last bindctl output should not contain "error"
When I query statistics zones of bind10 module Xfrout with cmdctl port 47804
- last bindctl output should not contain "error"
- Then the statistics counter notifyoutv4 for the zone _SERVER_ should be 0
- Then the statistics counter notifyoutv4 for the zone example.org. should be 0
- Then the statistics counter notifyoutv6 for the zone _SERVER_ should be 5
- Then the statistics counter notifyoutv6 for the zone example.org. should be 5
- # The counts of rejection would be between 1 and 2. They are not
- # fixed. It would depend on timing or the platform.
- Then the statistics counter xfrrej for the zone _SERVER_ should be greater than 0
- Then the statistics counter xfrrej for the zone example.org. should be greater than 0
- Then the statistics counter xfrreqdone for the zone _SERVER_ should be 0
- Then the statistics counter xfrreqdone for the zone example.org. should be 0
+ The statistics counters are 0 in category .Xfrout.zones except for the following items
+ | item_name | item_value | min_value | max_value |
+ | _SERVER_.notifyoutv4 | 1 | | |
+ | _SERVER_.xfrrej | | 1 | 3 |
+ | example.org..notifyoutv4 | 1 | | |
+ | example.org..xfrrej | | 1 | 3 |
+ # Note: The above rejection counters might sometimes be increased
+ # up to 3. See this for details
+ # http://git.bind10.isc.org/~tester/builder/BIND10-lettuce/20120918210000-MacOS/logs/lettuce.out
When I query statistics socket of bind10 module Xfrout with cmdctl port 47804
- Then the statistics counter open should be 1
- Then the statistics counter openfail should be 0
- Then the statistics counter close should be 0
- Then the statistics counter bindfail should be 0
- Then the statistics counter acceptfail should be 0
- Then the statistics counter accept should be 1
- Then the statistics counter senderr should be 0
- Then the statistics counter recverr should be 0
+ The statistics counters are 0 in category .Xfrout.socket.unixdomain except for the following items
+ | item_name | item_value |
+ | open | 1 |
+ | accept | 1 |
+
+ #
+ # Test4 for Xfrin statistics
+ #
+ # check statistics change
+ #
+
+ # wait until the last stats requesting is finished
+ When I query statistics zones of bind10 module Xfrin with cmdctl
+ wait for new bind10 stderr message XFRIN_RECEIVED_COMMAND
+ last bindctl output should not contain "error"
+
+ When I query statistics zones of bind10 module Xfrin with cmdctl
+ The statistics counters are 0 in category .Xfrin.zones except for the following items
+ | item_name | item_value |
+ | _SERVER_.soaoutv4 | 1 |
+ | _SERVER_.axfrreqv4 | 1 |
+ | _SERVER_.xfrfail | 1 |
+ | example.org..soaoutv4 | 1 |
+ | example.org..axfrreqv4 | 1 |
+ | example.org..xfrfail | 1 |
#
# Test for unreachable slave
@@ -220,27 +448,142 @@ Feature: Xfrin incoming notify handling
# check statistics change
#
- # wait until the last stats requesting is finished
- wait for new master stderr message STATS_SEND_STATISTICS_REQUEST
+ When I query statistics zones of bind10 module Xfrout with cmdctl port 47804
+ # wait until stats request at least after NOTIFY_OUT_TIMEOUT
wait for new master stderr message XFROUT_RECEIVED_GETSTATS_COMMAND
+ last bindctl output should not contain "error"
When I query statistics zones of bind10 module Xfrout with cmdctl port 47804
- last bindctl output should not contain "error"
- Then the statistics counter notifyoutv4 for the zone _SERVER_ should be 0
- Then the statistics counter notifyoutv4 for the zone example.org. should be 0
- Then the statistics counter notifyoutv6 for the zone _SERVER_ should be greater than 0
- Then the statistics counter notifyoutv6 for the zone example.org. should be greater than 0
- Then the statistics counter xfrrej for the zone _SERVER_ should be 0
- Then the statistics counter xfrrej for the zone example.org. should be 0
- Then the statistics counter xfrreqdone for the zone _SERVER_ should be 0
- Then the statistics counter xfrreqdone for the zone example.org. should be 0
+ The statistics counters are 0 in category .Xfrout.zones except for the following items
+ | item_name | min_value | max_value |
+ | _SERVER_.notifyoutv6 | 1 | 5 |
+ | example.org..notifyoutv6 | 1 | 5 |
When I query statistics socket of bind10 module Xfrout with cmdctl port 47804
- Then the statistics counter open should be 1
- Then the statistics counter openfail should be 0
- Then the statistics counter close should be 0
- Then the statistics counter bindfail should be 0
- Then the statistics counter acceptfail should be 0
- Then the statistics counter accept should be 0
- Then the statistics counter senderr should be 0
- Then the statistics counter recverr should be 0
+ The statistics counters are 0 in category .Xfrout.socket.unixdomain except for the following items
+ | item_name | item_value |
+ | open | 1 |
+
+ #
+ # Test for NOTIFY that would result in NOTAUTH
+ #
+ Scenario: Handle incoming notify that does match authoritative zones
+ Given I have bind10 running with configuration xfrin/retransfer_master.conf with cmdctl port 47804 as master
+ And wait for master stderr message BIND10_STARTED_CC
+ And wait for master stderr message CMDCTL_STARTED
+ And wait for master stderr message AUTH_SERVER_STARTED
+ And wait for master stderr message XFROUT_STARTED
+ And wait for master stderr message ZONEMGR_STARTED
+ And wait for master stderr message STATS_STARTING
+
+ And I have bind10 running with configuration xfrin/retransfer_slave_notify.conf
+ 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
+ And wait for bind10 stderr message XFRIN_STARTED
+ And wait for bind10 stderr message ZONEMGR_STARTED
+
+ #
+ # replace master's data source with unmatched zone for slave's zone.
+ # we restart Xfrout to make it sure.
+ #
+ When I send bind10 the following commands with cmdctl port 47804
+ """
+ config set data_sources/classes/IN[0]/params/database_file data/ixfr-out/zones.sqlite3
+ config set Auth/database_file data/ixfr-out/zones.sqlite3
+ config set Xfrout/zone_config[0]/origin example.com
+ config commit
+ Xfrout shutdown
+ """
+ last bindctl output should not contain "error"
+ And wait for new master stderr message XFROUT_STARTED
+
+ A query for www.example.com to [::1]:47806 should have rcode REFUSED
+
+ When I send bind10 with cmdctl port 47804 the command Xfrout notify example.com IN
+ Then wait for new master stderr message XFROUT_NOTIFY_COMMAND
+ Then wait for new bind10 stderr message AUTH_RECEIVED_NOTIFY_NOTAUTH
+ Then wait for new master stderr message NOTIFY_OUT_REPLY_RECEIVED
+
+ A query for www.example.com to [::1]:47806 should have rcode REFUSED
+
+ #
+ # Test for NOTIFY that's not in the secondaries list
+ #
+ Scenario: Handle incoming notify that is not in the secondaries list
+ Given I have bind10 running with configuration xfrin/retransfer_master.conf with cmdctl port 47804 as master
+ And wait for master stderr message BIND10_STARTED_CC
+ And wait for master stderr message CMDCTL_STARTED
+ And wait for master stderr message AUTH_SERVER_STARTED
+ And wait for master stderr message XFROUT_STARTED
+ And wait for master stderr message ZONEMGR_STARTED
+ And wait for master stderr message STATS_STARTING
+
+ And I have bind10 running with configuration xfrin/retransfer_slave_notify.conf
+ 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
+ And wait for bind10 stderr message XFRIN_STARTED
+ And wait for bind10 stderr message ZONEMGR_STARTED
+
+ #
+ # Empty slave's secondaries list, and restart zonemgr to make it sure
+ #
+ When I send bind10 the following commands with cmdctl
+ """
+ config remove Zonemgr/secondary_zones[0]
+ config commit
+ Zonemgr shutdown
+ """
+ last bindctl output should not contain "error"
+ And wait for new bind10 stderr message ZONEMGR_STARTED
+
+ A query for www.example.org to [::1]:47806 should have rcode NXDOMAIN
+
+ When I send bind10 with cmdctl port 47804 the command Xfrout notify example.org IN
+ Then wait for new master stderr message XFROUT_NOTIFY_COMMAND
+ Then wait for new bind10 stderr message AUTH_RECEIVED_NOTIFY
+ Then wait for new bind10 stderr message ZONEMGR_RECEIVE_NOTIFY
+ Then wait for new bind10 stderr message ZONEMGR_ZONE_NOTIFY_NOT_SECONDARY
+ Then wait for new master stderr message NOTIFY_OUT_REPLY_RECEIVED
+
+ A query for www.example.org to [::1]:47806 should have rcode NXDOMAIN
+
+ #
+ # Test for NOTIFY when zonemgr is not running
+ #
+ Scenario: Handle incoming notify while zonemgr is not running
+ Given I have bind10 running with configuration xfrin/retransfer_master.conf with cmdctl port 47804 as master
+ And wait for master stderr message BIND10_STARTED_CC
+ And wait for master stderr message CMDCTL_STARTED
+ And wait for master stderr message AUTH_SERVER_STARTED
+ And wait for master stderr message XFROUT_STARTED
+ And wait for master stderr message ZONEMGR_STARTED
+ And wait for master stderr message STATS_STARTING
+
+ And I have bind10 running with configuration xfrin/retransfer_slave_notify.conf
+ 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
+ And wait for bind10 stderr message XFRIN_STARTED
+ And wait for bind10 stderr message ZONEMGR_STARTED
+
+ # remove zonemgr from the system. a subsequent notify is ignored, but
+ # an error message shouldn't be logged at auth.
+ When I send bind10 the following commands with cmdctl
+ """
+ config remove Init/components b10-zonemgr
+ config commit
+ """
+ last bindctl output should not contain "error"
+ And wait for new bind10 stderr message BIND10_PROCESS_ENDED
+
+ A query for www.example.org to [::1]:47806 should have rcode NXDOMAIN
+
+ When I send bind10 with cmdctl port 47804 the command Xfrout notify example.org IN
+ Then wait for master stderr message XFROUT_NOTIFY_COMMAND
+ Then wait for new bind10 stderr message AUTH_RECEIVED_NOTIFY
+ Then wait for new bind10 stderr message AUTH_ZONEMGR_NOTEXIST not AUTH_ZONEMGR_ERROR
+ Then wait for master stderr message NOTIFY_OUT_TIMEOUT not NOTIFY_OUT_REPLY_RECEIVED
+
+ A query for www.example.org to [::1]:47806 should have rcode NXDOMAIN
diff --git a/tests/lettuce/setup_intree_bind10.sh.in b/tests/lettuce/setup_intree_bind10.sh.in
index 63b90ff..b8e85d4 100644
--- a/tests/lettuce/setup_intree_bind10.sh.in
+++ b/tests/lettuce/setup_intree_bind10.sh.in
@@ -30,7 +30,7 @@ export PYTHONPATH
# required by loadable python modules.
SET_ENV_LIBRARY_PATH=@SET_ENV_LIBRARY_PATH@
if test $SET_ENV_LIBRARY_PATH = yes; then
- @ENV_LIBRARY_PATH@=@abs_top_builddir@/src/lib/dns/.libs:@abs_top_builddir@/src/lib/dns/python/.libs:@abs_top_builddir@/src/lib/cryptolink/.libs:@abs_top_builddir@/src/lib/cc/.libs:@abs_top_builddir@/src/lib/config/.libs:@abs_top_builddir@/src/lib/log/.libs:@abs_top_builddir@/src/lib/acl/.libs:@abs_top_builddir@/src/lib/util/.libs:@abs_top_builddir@/src/lib/util/io/.libs:@abs_top_builddir@/src/lib/exceptions/.libs:@abs_top_builddir@/src/lib/datasrc/.libs:$@ENV_LIBRARY_PATH@
+ @ENV_LIBRARY_PATH@=@abs_top_builddir@/src/lib/dns/.libs:@abs_top_builddir@/src/lib/dns/python/.libs:@abs_top_builddir@/src/lib/cryptolink/.libs:@abs_top_builddir@/src/lib/cc/.libs:@abs_top_builddir@/src/lib/config/.libs:@abs_top_builddir@/src/lib/log/.libs:@abs_top_builddir@/src/lib/acl/.libs:@abs_top_builddir@/src/lib/util/.libs:@abs_top_builddir@/src/lib/util/threads/.libs:@abs_top_builddir@/src/lib/util/io/.libs:@abs_top_builddir@/src/lib/exceptions/.libs:@abs_top_builddir@/src/lib/datasrc/.libs:$@ENV_LIBRARY_PATH@
export @ENV_LIBRARY_PATH@
fi
diff --git a/tests/system/.gitignore b/tests/system/.gitignore
deleted file mode 100644
index 76f87fe..0000000
--- a/tests/system/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-/conf.sh
-/run.sh
diff --git a/tests/system/Makefile.am b/tests/system/Makefile.am
deleted file mode 100644
index aed1d79..0000000
--- a/tests/system/Makefile.am
+++ /dev/null
@@ -1,16 +0,0 @@
-systest:
- sh $(srcdir)/runall.sh
-
-distclean-local:
- sh $(srcdir)/cleanall.sh
-
-# Most of the files under this directory (including test subdirectories)
-# must be listed in EXTRA_DIST.
-EXTRA_DIST = README cleanall.sh ifconfig.sh start.pl stop.pl runall.sh
-EXTRA_DIST += common/default_user.csv
-EXTRA_DIST += glue/auth.good glue/example.good glue/noglue.good glue/test.good
-EXTRA_DIST += glue/tests.sh glue/clean.sh
-EXTRA_DIST += glue/nsx1/com.db glue/nsx1/net.db glue/nsx1/root-servers.nil.db
-EXTRA_DIST += glue/nsx1/root.db
-EXTRA_DIST += bindctl/tests.sh bindctl/clean.sh bindctl/setup.sh
-EXTRA_DIST += bindctl/nsx1/root.db bindctl/nsx1/example-normalized.db
diff --git a/tests/system/README b/tests/system/README
deleted file mode 100644
index a1c0a97..0000000
--- a/tests/system/README
+++ /dev/null
@@ -1,64 +0,0 @@
-Copyright (C) 2004, 2010, 2011 Internet Systems Consortium, Inc. ("ISC")
-Copyright (C) 2000, 2001 Internet Software Consortium.
-See COPYRIGHT in the source root or http://isc.org/copyright.html for terms.
-
-This is a simple test environment for running BIND 10 system tests
-involving multiple name servers. It was originally developed for BIND
-9, and has been ported to test BIND 10 implementations. Ideally we
-should share the same framework for both versions, so some part of the
-original setup are kept, even though they are BIND 9 specific and not
-currently used.
-
-Also, these tests generally rely on BIND 9 programs, most commonly
-its dig, and will sometimes be its name server (named). So, the test
-environment assumes that there's a source tree of BIND 9 where its
-programs are built, and that an environment variable "BIND9_TOP" is set
-to point to the top directory of the source tree.
-
-There are multiple test suites, each in a separate subdirectory and
-involving a different DNS setup. They are:
-
- bindctl/ Some basic management operations using the bindctl tool
- glue/ Glue handling tests
- ixfr/ Incremental transfer tests
-
-(the following tests are planned to be added soon)
- dnssec/ DNSSEC tests
- masterfile/ Master file parser
- axfr/ Full-transfer tests
-
-Typically each test suite sets up 2-5 instances of BIND 10 (or BIND 9
-named) and then performs one or more tests against them. Within the test
-suite subdirectory, each instance has a separate subdirectory containing
-its configuration data. By convention, these subdirectories are named
-"nsx1", "nsx2", etc for BIND 10 ("x" means BIND 10), and "ns1", "ns2",
-etc. for BIND 9.
-
-The tests are completely self-contained and do not require access to
-the real DNS. Generally, one of the test servers (ns[x]1) is set up as
-a root name server and is listed in the hints file of the others.
-
-To enable all servers to run on the same machine, they bind to separate
-virtual IP address on the loopback interface. ns[x]1 runs on 10.53.0.1,
-ns[x]2 on 10.53.0.2, etc. Before running any tests, you must set up
-these addresses by running "ifconfig.sh up" as root.
-
-Mac OS X:
-If you wish to make the interfaces survive across reboots copy
-org.isc.bind.system and org.isc.bind.system to /Library/LaunchDaemons
-then run "launchctl load /Library/LaunchDaemons/org.isc.bind.system.plist"
-as root.
-
-The servers use port 53210 instead of the usual port 53, so they can be
-run without root privileges once the interfaces have been set up.
-
-The tests can be run individually like this:
-
- sh run.sh xfer
- sh run.sh glue
- etc.
-
-To run all the tests, just type "make systest" either on this directory
-or on the top source directory. Note: currently these tests cannot be
-run when built under a separate build directory. Everything must be
-run within the original source tree.
diff --git a/tests/system/bindctl/clean.sh b/tests/system/bindctl/clean.sh
deleted file mode 100755
index f691512..0000000
--- a/tests/system/bindctl/clean.sh
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/sh
-#
-# Copyright (C) 2004, 2007 Internet Systems Consortium, Inc. ("ISC")
-# Copyright (C) 2000, 2001 Internet Software Consortium.
-#
-# 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.
-
-rm -f */b10-config.db
-rm -f dig.out.* bindctl.out.*
-rm -f */msgq_socket */zone.sqlite3
diff --git a/tests/system/bindctl/nsx1/.gitignore b/tests/system/bindctl/nsx1/.gitignore
deleted file mode 100644
index 4a8ce05..0000000
--- a/tests/system/bindctl/nsx1/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-/b10-config.db.template
-/bind10.run
-/bindctl.out
diff --git a/tests/system/bindctl/nsx1/b10-config.db.template.in b/tests/system/bindctl/nsx1/b10-config.db.template.in
deleted file mode 100644
index 4d6f287..0000000
--- a/tests/system/bindctl/nsx1/b10-config.db.template.in
+++ /dev/null
@@ -1,29 +0,0 @@
-{"version": 2,
- "Auth": {
- "listen_on": [{"address": "10.53.0.1", "port": 53210}],
- "database_file": "@abs_builddir@/zone.sqlite3"
- },
- "data_sources": {
- "classes": {
- "IN": [{
- "type": "sqlite3",
- "params": {
- "database_file": "@abs_builddir@/zone.sqlite3"
- }
- }]
- }
- },
- "Logging": {
- "loggers": [
- {
- "name": "*",
- "severity": "DEBUG",
- "output_options": [],
- "debuglevel": 99
- }
- ]
- },
- "Stats": {
- "poll-interval": 1
- }
-}
diff --git a/tests/system/bindctl/nsx1/example-normalized.db b/tests/system/bindctl/nsx1/example-normalized.db
deleted file mode 100644
index 7129522..0000000
--- a/tests/system/bindctl/nsx1/example-normalized.db
+++ /dev/null
@@ -1,3 +0,0 @@
-com. 300 IN SOA postmaster.example. ns.example.com. 2000042100 600 600 1200 600
-com. 300 IN NS ns.example.com.
-ns.example.com. 300 IN A 192.0.2.2
diff --git a/tests/system/bindctl/nsx1/root.db b/tests/system/bindctl/nsx1/root.db
deleted file mode 100644
index 31293de..0000000
--- a/tests/system/bindctl/nsx1/root.db
+++ /dev/null
@@ -1,25 +0,0 @@
-; Copyright (C) 2004, 2007 Internet Systems Consortium, Inc. ("ISC")
-; Copyright (C) 2000, 2001 Internet Software Consortium.
-;
-; 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.
-
-$TTL 300
-. IN SOA postmaster.example. a.root.servers.nil. (
- 2000042100 ; serial
- 600 ; refresh
- 600 ; retry
- 1200 ; expire
- 600 ; minimum
- )
-. NS ns.example.com.
-ns.example.com. A 192.0.2.1
diff --git a/tests/system/bindctl/setup.sh b/tests/system/bindctl/setup.sh
deleted file mode 100755
index 7a9a323..0000000
--- a/tests/system/bindctl/setup.sh
+++ /dev/null
@@ -1,26 +0,0 @@
-#!/bin/sh
-#
-# Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-# PERFORMANCE OF THIS SOFTWARE.
-
-SYSTEMTESTTOP=..
-. $SYSTEMTESTTOP/conf.sh
-
-SUBTEST_TOP=${TEST_TOP}/bindctl
-
-cp ${SUBTEST_TOP}/nsx1/b10-config.db.template ${SUBTEST_TOP}/nsx1/b10-config.db
-
-rm -f ${SUBTEST_TOP}/*/zone.sqlite3
-${B10_LOADZONE} -i 1 -c "{\"database_file\": \"${SUBTEST_TOP}/nsx1/zone.sqlite3\"}" \
- . ${SUBTEST_TOP}//nsx1/root.db
diff --git a/tests/system/bindctl/tests.sh b/tests/system/bindctl/tests.sh
deleted file mode 100755
index e1e533a..0000000
--- a/tests/system/bindctl/tests.sh
+++ /dev/null
@@ -1,238 +0,0 @@
-#!/bin/sh
-#
-# Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-# PERFORMANCE OF THIS SOFTWARE.
-
-SYSTEMTESTTOP=..
-. $SYSTEMTESTTOP/conf.sh
-
-#
-# Do bindctl tests.
-#
-
-status=0
-n=0
-
-# TODO: consider consistency with statistics definition in auth.spec
-
-# flatten JSON
-awk_flatten_json='
-function join(ary, len) {
- ret = "";
- for (i = 1; i <= len; ++i) {
- ret = ret""ary[i];
- }
- return ret;
-}
-BEGIN {
- depth = 0;
-}
-/.+{$/ {
- label[++depth] = $1;
- next;
-}
-/},?/ {
- --depth;
- next;
-}
-/:/ {
- print join(label,depth)""$1" "$2;
-}
-'
-# Check the counters have expected values given with 1st argument.
-# This function tests only these counters will be incremented in every checks
-# since the content of datasource and requests are not changed in this test.
-test_counters () {
- status=0
- $AWK "$awk_flatten_json" bindctl.out.$n | \
- grep '"Auth":"zones":"_SERVER_":"request":"v4": '$1 > \
- /dev/null || status=1
- $AWK "$awk_flatten_json" bindctl.out.$n | \
- grep '"Auth":"zones":"_SERVER_":"request":"v6": '0 > \
- /dev/null || status=1
- $AWK "$awk_flatten_json" bindctl.out.$n | \
- grep '"Auth":"zones":"_SERVER_":"request":"udp": '$1 > \
- /dev/null || status=1
- $AWK "$awk_flatten_json" bindctl.out.$n | \
- grep '"Auth":"zones":"_SERVER_":"request":"tcp": '0 > \
- /dev/null || status=1
- $AWK "$awk_flatten_json" bindctl.out.$n | \
- grep '"Auth":"zones":"_SERVER_":"opcode":"query": '$1 > \
- /dev/null || status=1
- $AWK "$awk_flatten_json" bindctl.out.$n | \
- grep '"Auth":"zones":"_SERVER_":"responses": '$1 > \
- /dev/null || status=1
- $AWK "$awk_flatten_json" bindctl.out.$n | \
- grep '"Auth":"zones":"_SERVER_":"rcode":"noerror": '$1 > \
- /dev/null || status=1
- $AWK "$awk_flatten_json" bindctl.out.$n | \
- grep '"Auth":"zones":"_SERVER_":"qrysuccess": '$1 > \
- /dev/null || status=1
- $AWK "$awk_flatten_json" bindctl.out.$n | \
- grep '"Auth":"zones":"_SERVER_":"qryauthans": '$1 > \
- /dev/null || status=1
- return $status
-}
-expected_count=0
-
-echo "I:Checking b10-auth is disabled by default ($n)"
-$DIG +norec @10.53.0.1 -p 53210 ns.example.com. A > /dev/null && status=1
-if [ $status != 0 ]; then echo "I:failed"; fi
-n=`expr $n + 1`
-
-echo "I:Starting b10-auth and checking that it works ($n)"
-echo 'config add Init/components b10-auth
-config set Init/components/b10-auth { "special": "auth", "kind": "needed" }
-config commit
-quit
-' | $RUN_BINDCTL \
- --csv-file-dir=$BINDCTL_CSV_DIR 2>&1 > /dev/null || status=1
-$DIG +norec @10.53.0.1 -p 53210 ns.example.com. A >dig.out.$n || status=1
-# perform a simple check on the output (digcomp would be too much for this)
-grep 192.0.2.1 dig.out.$n > /dev/null || status=1
-if [ $status != 0 ]; then echo "I:failed"; fi
-n=`expr $n + 1`
-
-echo "I:Checking BIND 10 statistics after a pause ($n)"
-# wait for 2sec to make sure b10-stats gets the latest statistics.
-sleep 2
-echo 'Stats show
-' | $RUN_BINDCTL \
- --csv-file-dir=$BINDCTL_CSV_DIR > bindctl.out.$n || status=1
-# the server should have received 1 request
-expected_count=`expr $expected_count + 1`
-test_counters $expected_count
-if [ $? != 0 ]; then echo "I:failed"; fi
-n=`expr $n + 1`
-
-echo "I:Stopping b10-auth and checking that ($n)"
-echo 'config remove Init/components b10-auth
-config commit
-quit
-' | $RUN_BINDCTL \
- --csv-file-dir=$BINDCTL_CSV_DIR 2>&1 > /dev/null || status=1
-# dig should exit with a failure code.
-$DIG +tcp +norec @10.53.0.1 -p 53210 ns.example.com. A > /dev/null && status=1
-if [ $status != 0 ]; then echo "I:failed"; fi
-n=`expr $n + 1`
-
-echo "I:Restarting b10-auth and checking that ($n)"
-echo 'config add Init/components b10-auth
-config set Init/components/b10-auth { "special": "auth", "kind": "needed" }
-config commit
-quit
-' | $RUN_BINDCTL \
- --csv-file-dir=$BINDCTL_CSV_DIR 2>&1 > /dev/null || status=1
-sleep 2
-$DIG +norec @10.53.0.1 -p 53210 ns.example.com. A >dig.out.$n || status=1
-# perform a simple check on the output (digcomp would be too much for this)
-grep 192.0.2.1 dig.out.$n > /dev/null || status=1
-if [ $status != 0 ]; then echo "I:failed"; fi
-n=`expr $n + 1`
-
-echo "I:Rechecking BIND 10 statistics after a pause ($n)"
-sleep 2
-echo 'Stats show
-' | $RUN_BINDCTL \
- --csv-file-dir=$BINDCTL_CSV_DIR > bindctl.out.$n || status=1
-# The statistics counters can not be reset even after auth
-# restarts. Because stats preserves the query counts which the dying
-# auth sent. Then it cumulates them and new counts which the living
-# auth sends. This note assumes that the issue would have been
-# resolved : "#1941 stats lossage (multiple auth servers)".
-expected_count=`expr $expected_count + 1`
-test_counters $expected_count
-if [ $? != 0 ]; then echo "I:failed"; fi
-n=`expr $n + 1`
-
-echo "I:Changing the data source from sqlite3 to in-memory ($n)"
-DATASRC_SPEC='{"type": "MasterFiles", "cache-enable": true, "params": {"com":'
-DATASRC_SPEC="${DATASRC_SPEC} \"${TEST_TOP}/bindctl/nsx1/example-normalized.db\"}}"
-echo "config set data_sources/classes/IN[0] ${DATASRC_SPEC}
-config commit
-quit
-" | $RUN_BINDCTL \
- --csv-file-dir=$BINDCTL_CSV_DIR > bindctl.out.$n || status=1
-sleep 2
-$DIG +norec @10.53.0.1 -p 53210 ns.example.com. A >dig.out.$n || status=1
-grep 192.0.2.2 dig.out.$n > /dev/null || status=1
-if [ $status != 0 ]; then echo "I:failed"; fi
-n=`expr $n + 1`
-
-echo "I:Rechecking BIND 10 statistics after changing the datasource ($n)"
-sleep 2
-echo 'Stats show
-' | $RUN_BINDCTL \
- --csv-file-dir=$BINDCTL_CSV_DIR > bindctl.out.$n || status=1
-# The statistics counters shouldn't be reset due to hot-swapping datasource.
-expected_count=`expr $expected_count + 1`
-test_counters $expected_count
-if [ $? != 0 ]; then echo "I:failed"; fi
-n=`expr $n + 1`
-
-echo "I:Starting more b10-auths and checking that ($n)"
-for i in 2 3
-do
- echo 'config add Init/components b10-auth-'$i'
-config set Init/components/b10-auth-'$i' { "special": "auth", "kind": "needed" }
-config commit
-quit
-' | $RUN_BINDCTL \
- --csv-file-dir=$BINDCTL_CSV_DIR 2>&1 > /dev/null || status=1
-done
-sleep 2
-$DIG +norec @10.53.0.1 -p 53210 ns.example.com. A >dig.out.$n || status=1
-grep 192.0.2.2 dig.out.$n > /dev/null || status=1
-if [ $status != 0 ]; then echo "I:failed"; fi
-n=`expr $n + 1`
-
-echo "I:Rechecking BIND 10 statistics consistency after a pause ($n)"
-sleep 2
-expected_count=`expr $expected_count + 1`
-# Rechecking some times
-for i in 1 2 3 4
-do
- echo 'Stats show
-' | $RUN_BINDCTL \
- --csv-file-dir=$BINDCTL_CSV_DIR > bindctl.out.$n || status=1
- # The statistics counters should keep being consistent even while
- # multiple b10-auths are running.
-
- test_counters $expected_count
- if [ $? != 0 ]; then echo "I:failed "; break ; fi
-done
-n=`expr $n + 1`
-
-echo "I:Stopping extra b10-auths and checking that ($n)"
-for i in 3 2
-do
- echo 'config remove Init/components b10-auth-'$i'
-config commit
-quit
-' | $RUN_BINDCTL \
- --csv-file-dir=$BINDCTL_CSV_DIR 2>&1 > /dev/null || status=1
-done
-sleep 2
-$DIG +norec @10.53.0.1 -p 53210 ns.example.com. A >dig.out.$n || status=1
-grep 192.0.2.2 dig.out.$n > /dev/null || status=1
-if [ $status != 0 ]; then echo "I:failed"; fi
-n=`expr $n + 1`
-
-# The statistics counters can not be rechecked here because the auth
-# instance seems to hang up after one of the multiple auth instances
-# was removed via bindctl. This reason seems to be the same reason as
-# #1703.
-
-echo "I:exit status: $status"
-exit $status
diff --git a/tests/system/cleanall.sh b/tests/system/cleanall.sh
deleted file mode 100755
index 434c6b1..0000000
--- a/tests/system/cleanall.sh
+++ /dev/null
@@ -1,36 +0,0 @@
-#!/bin/sh
-#
-# Copyright (C) 2004, 2007 Internet Systems Consortium, Inc. ("ISC")
-# Copyright (C) 2000, 2001 Internet Software Consortium.
-#
-# 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.
-
-#
-# Clean up after system tests.
-#
-
-find . -type f \( \
- -name 'K*' -o -name '*~' -o -name '*.core' -o -name '*.log' \
- -o -name '*.pid' -o -name '*.keyset' -o -name named.run \
- -o -name bind10.run -o -name lwresd.run -o -name ans.run \) -print | \
- xargs rm -f
-
-status=0
-
-for d in ./.* ./* ./*/*
-do
- case $d in ./.|./..) continue ;; esac
- test -d $d || continue
-
- test ! -f $d/clean.sh || ( cd $d && sh clean.sh )
-done
diff --git a/tests/system/common/default_user.csv b/tests/system/common/default_user.csv
deleted file mode 100644
index e13e194..0000000
--- a/tests/system/common/default_user.csv
+++ /dev/null
@@ -1 +0,0 @@
-root,bind10
diff --git a/tests/system/common/rndc.conf b/tests/system/common/rndc.conf
deleted file mode 100644
index a897548..0000000
--- a/tests/system/common/rndc.conf
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2004, 2007 Internet Systems Consortium, Inc. ("ISC")
- * Copyright (C) 2000, 2001 Internet Software Consortium.
- *
- * 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.
- */
-
-options {
- default-key "rndc_key";
-};
-
-key rndc_key {
- algorithm hmac-md5;
- secret "1234abcd8765";
-};
diff --git a/tests/system/common/rndc.key b/tests/system/common/rndc.key
deleted file mode 100644
index c2c3457..0000000
--- a/tests/system/common/rndc.key
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
- * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
- * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
- * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
- * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
- * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
- * PERFORMANCE OF THIS SOFTWARE.
- */
-
-/* $Id: rndc.key,v 1.3 2011-03-12 04:59:47 tbox Exp $ */
-
-key rndc_key {
- secret "1234abcd8765";
- algorithm hmac-md5;
-};
diff --git a/tests/system/conf.sh.in b/tests/system/conf.sh.in
deleted file mode 100755
index 4948d63..0000000
--- a/tests/system/conf.sh.in
+++ /dev/null
@@ -1,73 +0,0 @@
-#!/bin/sh
-#
-# Copyright (C) 2004-2011 Internet Systems Consortium, Inc. ("ISC")
-# Copyright (C) 2000-2003 Internet Software Consortium.
-#
-# 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.
-
-#
-# Common configuration data for system tests, to be sourced into
-# other shell scripts.
-#
-
-# Prerequisite check
-if [ @srcdir@ != @builddir@ ]; then
- echo "Currently systest doesn't work for a separate build tree."
- echo "Rebuild BIND 10 on the source tree and run the tests."
- exit 1
-fi
-
-if [ -z $BIND9_TOP ]; then
- echo "systest assumes there's a compiled tree of BIND 9 which can be"
- echo "accessed via the BIND9_TOP environment variable."
- echo "Please make sure this assumption is met."
- exit 1
-fi
-
-# Find the top of the source and test trees.
-export TOP=@abs_top_srcdir@
-export TEST_TOP=@abs_builddir@
-
-# Programs
-export RUN_BIND10=$TOP/src/bin/bind10/run_bind10.sh
-export RUN_BINDCTL=$TOP/src/bin/bindctl/run_bindctl.sh
-export BINDCTL_CSV_DIR=@abs_srcdir@/common/
-export B10_LOADZONE=$TOP/src/bin/loadzone/run_loadzone.sh
-export BIND9_NAMED=$BIND9_TOP/bin/named/named
-export DIG=$BIND9_TOP/bin/dig/dig
-export RNDC=$BIND9_TOP/bin/rndc/rndc
-
-# Test tools borrowed from BIND 9's system test (without change).
-export TESTSOCK=$BIND9_TOP/bin/tests/system/testsock.pl
-export DIGCOMP=$BIND9_TOP/bin/tests/system/digcomp.pl
-
-# bindctl test doesn't work right now and is disabled (see #2568)
-#export SUBDIRS="bindctl glue ixfr/in-2"
-export SUBDIRS="glue ixfr/in-2"
-
-# Add appropriate subdirectories to the above statement as the tests become
-# available.
-#SUBDIRS="dnssec masterfile ixfr/in-1 ixfr/in-2 ixfr/in-4"
-
-# PERL will be an empty string if no perl interpreter was found. A similar
-# comment applies to AWK.
-export PERL=@PERL@
-export AWK=@AWK@
-
-# Other constants
-export RNDC_PORT=9953
-export DNS_PORT=53210
-
-export TESTS_TOP=$TOP/tests
-export SYSTEM_TOP=$TESTS_TOP/system
-export IXFR_TOP=$SYSTEM_TOP/ixfr
diff --git a/tests/system/glue/.gitignore b/tests/system/glue/.gitignore
deleted file mode 100644
index 87e08bf..0000000
--- a/tests/system/glue/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/setup.sh
diff --git a/tests/system/glue/auth.good b/tests/system/glue/auth.good
deleted file mode 100644
index 2c619f6..0000000
--- a/tests/system/glue/auth.good
+++ /dev/null
@@ -1,15 +0,0 @@
-
-; <<>> DiG 9.0 <<>> +norec @10.53.0.1 -p 5300 foo.bar.example.org. a
-;; global options: printcmd
-;; Got answer:
-;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 41239
-;; flags: qr ad; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 1
-
-;; QUESTION SECTION:
-;foo.bar.example.org. IN A
-
-;; AUTHORITY SECTION:
-example.org. 172800 IN NS b.root-servers.nil.
-
-;; ADDITIONAL SECTION:
-b.root-servers.nil. 300 IN A 10.53.0.2
diff --git a/tests/system/glue/clean.sh b/tests/system/glue/clean.sh
deleted file mode 100755
index b2c1e02..0000000
--- a/tests/system/glue/clean.sh
+++ /dev/null
@@ -1,23 +0,0 @@
-#!/bin/sh
-#
-# Copyright (C) 2004, 2007 Internet Systems Consortium, Inc. ("ISC")
-# Copyright (C) 2000, 2001 Internet Software Consortium.
-#
-# 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.
-
-#
-# Clean up after glue tests.
-#
-
-rm -f dig.out.*
-rm -f */msgq_socket */zone.sqlite3
diff --git a/tests/system/glue/example.good b/tests/system/glue/example.good
deleted file mode 100644
index b6d5708..0000000
--- a/tests/system/glue/example.good
+++ /dev/null
@@ -1,22 +0,0 @@
-
-; <<>> DiG 9.0 <<>> +norec @10.53.0.1 -p 5300 foo.bar.example. A
-;; global options: printcmd
-;; Got answer:
-;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 58772
-;; flags: qr ad; QUERY: 1, ANSWER: 0, AUTHORITY: 6, ADDITIONAL: 7
-
-;; QUESTION SECTION:
-;foo.bar.example. IN A
-
-;; AUTHORITY SECTION:
-example. 172800 IN NS NS1.example.COM.
-example. 172800 IN NS NS.example.
-
-;; ADDITIONAL SECTION:
-NS.example. 172800 IN A 192.0.2.1
-NS.example. 172800 IN A 192.0.2.2
-NS1.example.COM. 172800 IN A 192.0.2.3
-;; These are not used now - they are in a different master file
-;; than the answer.
-; NS1.example.COM. 172800 IN A 192.0.2.101
-; NS1.example.COM. 172800 IN AAAA 2001:db8::1
diff --git a/tests/system/glue/noglue.good b/tests/system/glue/noglue.good
deleted file mode 100644
index 57a2211..0000000
--- a/tests/system/glue/noglue.good
+++ /dev/null
@@ -1,14 +0,0 @@
-
-; <<>> DiG 9.0 <<>> @10.53.0.1 -p 5300 example.net a
-;; global options: printcmd
-;; Got answer:
-;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 29409
-;; flags: qr rd ad; QUERY: 1, ANSWER: 0, AUTHORITY: 2, ADDITIONAL: 0
-
-;; QUESTION SECTION:
-;example.net. IN A
-
-;; AUTHORITY SECTION:
-example.net. 300 IN NS ns2.example.info.
-example.net. 300 IN NS ns1.example.info.
-
diff --git a/tests/system/glue/nsx1/.gitignore b/tests/system/glue/nsx1/.gitignore
deleted file mode 100644
index c0750b3..0000000
--- a/tests/system/glue/nsx1/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-/b10-config.db
-/bind10.run
-/bindctl.out
diff --git a/tests/system/glue/nsx1/b10-config.db.in b/tests/system/glue/nsx1/b10-config.db.in
deleted file mode 100644
index 6802c53..0000000
--- a/tests/system/glue/nsx1/b10-config.db.in
+++ /dev/null
@@ -1,36 +0,0 @@
-{"version": 2,
- "Auth": {
- "listen_on": [{"address": "10.53.0.1", "port": 53210}],
- "database_file": "@abs_builddir@/zone.sqlite3"
- },
- "data_sources": {
- "classes": {
- "IN": [{
- "type": "sqlite3",
- "params": {
- "database_file": "@abs_builddir@/zone.sqlite3"
- }
- }]
- }
- },
- "Logging": {
- "loggers": [
- {
- "name": "*",
- "severity": "DEBUG",
- "output_options": [],
- "debuglevel": 99
- }
- ]
- },
- "Init": {
- "components": {
- "b10-auth": {"kind": "needed", "special": "auth" },
- "b10-xfrin": { "address": "Xfrin", "kind": "dispensable" },
- "b10-xfrout": { "address": "Xfrout", "kind": "dispensable" },
- "b10-zonemgr": { "address": "Zonemgr", "kind": "dispensable" },
- "b10-stats": { "address": "Stats", "kind": "dispensable" },
- "b10-cmdctl": { "special": "cmdctl", "kind": "needed" }
- }
- }
-}
diff --git a/tests/system/glue/nsx1/com.db b/tests/system/glue/nsx1/com.db
deleted file mode 100644
index c4b94e1..0000000
--- a/tests/system/glue/nsx1/com.db
+++ /dev/null
@@ -1,31 +0,0 @@
-; Copyright (C) 2004, 2007 Internet Systems Consortium, Inc. ("ISC")
-; Copyright (C) 2000, 2001 Internet Software Consortium.
-;
-; 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.
-
-$ORIGIN com.
-$TTL 300
-@ IN SOA root.example.com. a.root.servers.nil. (
- 2000042100 ; serial
- 600 ; refresh
- 600 ; retry
- 1200 ; expire
- 600 ; minimum
- )
-@ NS a.root-servers.nil.
-
-example.com. NS ns1.example.com.
-example.com. NS ns2.example.com.
-ns1.example.com. 172800 IN A 192.0.2.101
-ns1.example.com. 172800 IN AAAA 2001:db8::1
-ns2.example.com. 172800 IN A 192.0.2.102
diff --git a/tests/system/glue/nsx1/net.db b/tests/system/glue/nsx1/net.db
deleted file mode 100644
index 8b66521..0000000
--- a/tests/system/glue/nsx1/net.db
+++ /dev/null
@@ -1,32 +0,0 @@
-; Copyright (C) 2004, 2007 Internet Systems Consortium, Inc. ("ISC")
-; Copyright (C) 2000, 2001 Internet Software Consortium.
-;
-; 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.
-
-$ORIGIN net.
-$TTL 300
-@ IN SOA root.example.net. a.root.servers.nil. (
- 2000042100 ; serial
- 600 ; refresh
- 600 ; retry
- 1200 ; expire
- 600 ; minimum
- )
-@ NS a.root-servers.nil.
-
-; Referral outside of server authority, but with glue records present.
-; Don't hand out the glue.
-example.net. NS ns1.example.info.
-example.net. NS ns2.example.info.
-ns1.example.info. 172800 IN A 192.0.2.101
-ns2.example.info. 172800 IN A 192.0.2.102
diff --git a/tests/system/glue/nsx1/root-servers.nil.db b/tests/system/glue/nsx1/root-servers.nil.db
deleted file mode 100644
index 45050a9..0000000
--- a/tests/system/glue/nsx1/root-servers.nil.db
+++ /dev/null
@@ -1,26 +0,0 @@
-; Copyright (C) 2004, 2007 Internet Systems Consortium, Inc. ("ISC")
-; Copyright (C) 2000, 2001 Internet Software Consortium.
-;
-; 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.
-
-$TTL 300
-@ IN SOA ns hostmaster (
- 1
- 3600
- 1800
- 1814400
- 3600
- )
- NS a
-a A 10.53.0.1
-b A 10.53.0.2
diff --git a/tests/system/glue/nsx1/root.db b/tests/system/glue/nsx1/root.db
deleted file mode 100644
index 271a4d8..0000000
--- a/tests/system/glue/nsx1/root.db
+++ /dev/null
@@ -1,53 +0,0 @@
-; Copyright (C) 2004, 2007 Internet Systems Consortium, Inc. ("ISC")
-; Copyright (C) 2000, 2001 Internet Software Consortium.
-;
-; 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.
-
-$TTL 300
-. IN SOA postmaster.example. a.root.servers.nil. (
- 2000042100 ; serial
- 600 ; refresh
- 600 ; retry
- 1200 ; expire
- 600 ; minimum
- )
-. NS a.root-servers.nil.
-
-root-servers.nil. NS a.root-servers.nil.
-a.root-servers.nil. A 10.53.0.1
-
-; Delegate some domains that contain name servers for the sample
-; ccTLDs below.
-com. 172800 IN NS a.root-servers.nil.
-
-;
-; A sample TLD
-;
-example. 172800 IN NS NS.example.
-example. 172800 IN NS NS1.example.COM.
-NS.example. 172800 IN A 192.0.2.1
-NS.example. 172800 IN A 192.0.2.2
-NS1.example.COM. 172800 IN A 192.0.2.3
-
-;
-;
-;
-test. 172800 IN NS ns.test.
-test. 172800 IN NS ns1.example.net.
-ns.test. 172800 IN A 192.0.2.200
-ns1.example.net. 172800 IN A 192.0.2.201
-
-;
-; A hypothetical ccTLD where we are authoritative for the NS glue.
-;
-example.org 172800 IN NS b.root-servers.nil.
diff --git a/tests/system/glue/setup.sh.in b/tests/system/glue/setup.sh.in
deleted file mode 100755
index 2f9f886..0000000
--- a/tests/system/glue/setup.sh.in
+++ /dev/null
@@ -1,25 +0,0 @@
-#!/bin/sh
-#
-# Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-# PERFORMANCE OF THIS SOFTWARE.
-
-SYSTEMTESTTOP=..
-. $SYSTEMTESTTOP/conf.sh
-
-rm -f */zone.sqlite3
-${B10_LOADZONE} -i 1 -c '{"database_file": "@builddir@/nsx1/zone.sqlite3"}' . @builddir@/nsx1/root.db
-${B10_LOADZONE} -i 1 -c '{"database_file": "@builddir@/nsx1/zone.sqlite3"}' root-servers.nil \
- @builddir@/nsx1/root-servers.nil.db
-${B10_LOADZONE} -i 1 -c '{"database_file": "@builddir@/nsx1/zone.sqlite3"}' com @builddir@/nsx1/com.db
-${B10_LOADZONE} -i 1 -c '{"database_file": "@builddir@/nsx1/zone.sqlite3"}' net @builddir@/nsx1/net.db
diff --git a/tests/system/glue/test.good b/tests/system/glue/test.good
deleted file mode 100644
index b9b4719..0000000
--- a/tests/system/glue/test.good
+++ /dev/null
@@ -1,19 +0,0 @@
-
-; <<>> DiG 9.8.0 <<>> @127.0.0.1 -p 5300 foo.bar.test
-; (1 server found)
-;; global options: +cmd
-;; Got answer:
-;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 55069
-;; flags: qr rd; QUERY: 1, ANSWER: 0, AUTHORITY: 2, ADDITIONAL: 2
-;; WARNING: recursion requested but not available
-
-;; QUESTION SECTION:
-;foo.bar.test. IN A
-
-;; AUTHORITY SECTION:
-test. 172800 IN NS ns.test.
-test. 172800 IN NS ns1.example.net.
-
-;; ADDITIONAL SECTION:
-ns.test. 172800 IN A 192.0.2.200
-ns1.example.net. 172800 IN A 192.0.2.201
diff --git a/tests/system/glue/tests.sh b/tests/system/glue/tests.sh
deleted file mode 100755
index dafb1ad..0000000
--- a/tests/system/glue/tests.sh
+++ /dev/null
@@ -1,68 +0,0 @@
-#!/bin/sh
-#
-# Copyright (C) 2004, 2007 Internet Systems Consortium, Inc. ("ISC")
-# Copyright (C) 2000, 2001, 2003 Internet Software Consortium.
-#
-# 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.
-
-SYSTEMTESTTOP=..
-. $SYSTEMTESTTOP/conf.sh
-
-#
-# Do glue tests.
-#
-
-status=0
-n=0
-
-# This query should result in a delegation with two NS; one in the delegated
-# zone and one in a so called out-of-bailiwick zone for which the auth server
-# has authority, too. For the former, the server should return glue in the
-# parent zone. For the latter, BIND 9 and BIND 10 behave differently; BIND 9
-# uses "glue" in the parent zone (since this is the root zone everything can
-# be considered a valid glue). BIND 10 (using sqlite3 data source) searches
-# the other zone and uses the authoritative data in that zone (which is
-# intentionally different from the glue in the root zone).
-echo "I:testing that a TLD referral gets a full glue set from the root zone ($n)"
-$DIG +norec @10.53.0.1 -p 53210 foo.bar.example. A >dig.out.$n || status=1
-$PERL $DIGCOMP example.good dig.out.$n || status=1
-n=`expr $n + 1`
-
-# Disabling this test, as it checks for looking up glue in a different zone
-# finder than the answer is from. This is not supported now.
-#echo "I:testing that we find glue A RRs we are authoritative for ($n)"
-#$DIG +norec @10.53.0.1 -p 53210 foo.bar.example.org. a >dig.out.$n || status=1
-#$PERL $DIGCOMP auth.good dig.out.$n || status=1
-#n=`expr $n + 1`
-
-# We cannot do this test for BIND 10 because b10-auth doesn't act as a
-# recursive (caching) server (by design)
-# echo "I:testing that we find glue A/AAAA RRs in the cache ($n)"
-# $DIG +norec @10.53.0.1 -p 53210 foo.bar.yy. a >dig.out.$n || status=1
-# $PERL $DIGCOMP yy.good dig.out.$n || status=1
-# n=`expr $n + 1`
-
-echo "I:testing that we don't find out-of-zone glue ($n)"
-$DIG +norec @10.53.0.1 -p 53210 example.net. a > dig.out.$n || status=1
-$PERL $DIGCOMP noglue.good dig.out.$n || status=1
-n=`expr $n + 1`
-
-# This test currently fails (additional section will be empty, which is
-# incorrect). See Trac ticket #646.
-#echo "I:testing that we are finding partial out-of-zone glue ($n)"
-#$DIG +norec @10.53.0.1 -p 53210 foo.bar.test. a >dig.out.$n || status=1
-#$PERL $DIGCOMP test.good dig.out.$n || status=1
-#n=`expr $n + 1`
-
-echo "I:exit status: $status"
-exit $status
diff --git a/tests/system/ifconfig.sh b/tests/system/ifconfig.sh
deleted file mode 100755
index 7c695e2..0000000
--- a/tests/system/ifconfig.sh
+++ /dev/null
@@ -1,226 +0,0 @@
-#!/bin/sh
-#
-# Copyright (C) 2004, 2007-2010 Internet Systems Consortium, Inc. ("ISC")
-# Copyright (C) 2000-2003 Internet Software Consortium.
-#
-# 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.
-
-#
-# Set up interface aliases for bind9 system tests.
-#
-# IPv4: 10.53.0.{1..7} RFC 1918
-# IPv6: fd92:7065:b8e:ffff::{1..7} ULA
-#
-
-config_guess=""
-for f in ./config.guess ../../config.guess
-do
- if test -f $f
- then
- config_guess=$f
- fi
-done
-
-if test "X$config_guess" = "X"
-then
- cat <<EOF >&2
-$0: must be run from the top level source directory or the
-bin/tests/system directory
-EOF
- exit 1
-fi
-
-# If running on hp-ux, don't even try to run config.guess.
-# It will try to create a temporary file in the current directory,
-# which fails when running as root with the current directory
-# on a NFS mounted disk.
-
-case `uname -a` in
- *HP-UX*) sys=hpux ;;
- *) sys=`sh $config_guess` ;;
-esac
-
-case "$2" in
-[0-9]|[1-9][0-9]|[1-9][0-9][0-9]) base=$2;;
-*) base=""
-esac
-
-case "$3" in
-[0-9]|[1-9][0-9]|[1-9][0-9][0-9]) base6=$2;;
-*) base6=""
-esac
-
-case "$1" in
-
- start|up)
- for ns in 1 2 3 4 5 6 7 8
- do
- if test -n "$base"
- then
- int=`expr $ns + $base - 1`
- else
- int=$ns
- fi
- if test -n "$base6"
- then
- int6=`expr $ns + $base6 - 1`
- else
- int6=$ns
- fi
- case "$sys" in
- *-pc-solaris2.5.1)
- ifconfig lo0:$int 10.53.0.$ns netmask 0xffffffff up
- ;;
- *-sun-solaris2.[6-7])
- ifconfig lo0:$int 10.53.0.$ns netmask 0xffffffff up
- ;;
- *-*-solaris2.[8-9]|*-*-solaris2.1[0-9])
- /sbin/ifconfig lo0:$int plumb
- /sbin/ifconfig lo0:$int 10.53.0.$ns up
- if test -n "$int6"
- then
- /sbin/ifconfig lo0:$int6 inet6 plumb
- /sbin/ifconfig lo0:$int6 \
- inet6 fd92:7065:b8e:ffff::$ns up
- fi
- ;;
- *-*-linux*)
- ifconfig lo:$int 10.53.0.$ns up netmask 255.255.255.0
- ifconfig lo inet6 add fd92:7065:b8e:ffff::$ns/64
- ;;
- *-unknown-freebsd*)
- ifconfig lo0 10.53.0.$ns alias netmask 0xffffffff
- ifconfig lo0 inet6 fd92:7065:b8e:ffff::$ns alias
- ;;
- *-unknown-netbsd*)
- ifconfig lo0 10.53.0.$ns alias netmask 255.255.255.0
- ifconfig lo0 inet6 fd92:7065:b8e:ffff::$ns alias
- ;;
- *-unknown-openbsd*)
- ifconfig lo0 10.53.0.$ns alias netmask 255.255.255.0
- ifconfig lo0 inet6 fd92:7065:b8e:ffff::$ns alias
- ;;
- *-*-bsdi[3-5].*)
- ifconfig lo0 add 10.53.0.$ns netmask 255.255.255.0
- ;;
- *-dec-osf[4-5].*)
- ifconfig lo0 alias 10.53.0.$ns
- ;;
- *-sgi-irix6.*)
- ifconfig lo0 alias 10.53.0.$ns
- ;;
- *-*-sysv5uw7*|*-*-sysv*UnixWare*|*-*-sysv*OpenUNIX*)
- ifconfig lo0 10.53.0.$ns alias netmask 0xffffffff
- ;;
- *-ibm-aix4.*|*-ibm-aix5.*)
- ifconfig lo0 alias 10.53.0.$ns
- ifconfig lo0 inet6 alias -dad fd92:7065:b8e:ffff::$ns/64
- ;;
- hpux)
- ifconfig lo0:$int 10.53.0.$ns netmask 255.255.255.0 up
- ifconfig lo0:$int inet6 fd92:7065:b8e:ffff::$ns up
- ;;
- *-sco3.2v*)
- ifconfig lo0 alias 10.53.0.$ns
- ;;
- *-darwin*)
- ifconfig lo0 alias 10.53.0.$ns
- ifconfig lo0 inet6 fd92:7065:b8e:ffff::$ns alias
- ;;
- *)
- echo "Don't know how to set up interface. Giving up."
- exit 1
- esac
- done
- ;;
-
- stop|down)
- for ns in 8 7 6 5 4 3 2 1
- do
- if test -n "$base"
- then
- int=`expr $ns + $base - 1`
- else
- int=$ns
- fi
- case "$sys" in
- *-pc-solaris2.5.1)
- ifconfig lo0:$int 0.0.0.0 down
- ;;
- *-sun-solaris2.[6-7])
- ifconfig lo0:$int 10.53.0.$ns down
- ;;
- *-*-solaris2.[8-9]|*-*-solaris2.1[0-9])
- ifconfig lo0:$int 10.53.0.$ns down
- ifconfig lo0:$int 10.53.0.$ns unplumb
- if test -n "$int6"
- then
- ifconfig lo0:$int6 inet6 down
- ifconfig lo0:$int6 inet6 unplumb
- fi
- ;;
- *-*-linux*)
- ifconfig lo:$int 10.53.0.$ns down
- ifconfig lo inet6 del fd92:7065:b8e:ffff::$ns/64
- ;;
- *-unknown-freebsd*)
- ifconfig lo0 10.53.0.$ns delete
- ifconfig lo0 inet6 fd92:7065:b8e:ffff::$ns delete
- ;;
- *-unknown-netbsd*)
- ifconfig lo0 10.53.0.$ns delete
- ifconfig lo0 inet6 fd92:7065:b8e:ffff::$ns delete
- ;;
- *-unknown-openbsd*)
- ifconfig lo0 10.53.0.$ns delete
- ifconfig lo0 inet6 fd92:7065:b8e:ffff::$ns delete
- ;;
- *-*-bsdi[3-5].*)
- ifconfig lo0 remove 10.53.0.$ns
- ;;
- *-dec-osf[4-5].*)
- ifconfig lo0 -alias 10.53.0.$ns
- ;;
- *-sgi-irix6.*)
- ifconfig lo0 -alias 10.53.0.$ns
- ;;
- *-*-sysv5uw7*|*-*-sysv*UnixWare*|*-*-sysv*OpenUNIX*)
- ifconfig lo0 -alias 10.53.0.$ns
- ;;
- *-ibm-aix4.*|*-ibm-aix5.*)
- ifconfig lo0 delete 10.53.0.$ns
- ifconfig lo0 delete inet6 fd92:7065:b8e:ffff::$ns/64
- ;;
- hpux)
- ifconfig lo0:$int 0.0.0.0
- ifconfig lo0:$int inet6 ::
- ;;
- *-sco3.2v*)
- ifconfig lo0 -alias 10.53.0.$ns
- ;;
- *darwin*)
- ifconfig lo0 -alias 10.53.0.$ns
- ifconfig lo0 inet6 fd92:7065:b8e:ffff::$ns delete
- ;;
- *)
- echo "Don't know how to destroy interface. Giving up."
- exit 1
- esac
- done
-
- ;;
-
- *)
- echo "Usage: $0 { up | down } [base]"
- exit 1
-esac
diff --git a/tests/system/ixfr/.gitignore b/tests/system/ixfr/.gitignore
deleted file mode 100644
index 027d45e..0000000
--- a/tests/system/ixfr/.gitignore
+++ /dev/null
@@ -1,8 +0,0 @@
-/b10-config.db
-/common_tests.sh
-/db.example.n0
-/db.example.n2
-/db.example.n2.refresh
-/db.example.n4
-/db.example.n6
-/ixfr_init.sh
diff --git a/tests/system/ixfr/README b/tests/system/ixfr/README
deleted file mode 100644
index 51cba8a..0000000
--- a/tests/system/ixfr/README
+++ /dev/null
@@ -1,86 +0,0 @@
-Introduction
-============
-The directories in-1 to in-4 implement the following tests of the IXFR-in
-capability of BIND 10.
-
-in-1: Check that BIND 10 can receive IXFR in a single UDP packet.
-in-2: Check that BIND 10 can receive IXFR via TCP.
-in-3: Check that BIND 10 will request AXFR if the server does not support IXFR.
-in-4: Check that BIND 10 will request IXFR when its SOA refresh times out
-
-The tests are described more fully in the document:
-
-http://bind10.isc.org/wiki/IxfrSystemTests
-
-Overview
-========
-All the tests use two nameservers:
-
-* A BIND 9 nameserver acting as the IXFR server (using the nomenclature
-of RFC 1995).
-* A BIND 10 nameserver acting at the IXFR client.
-
-In general, the tests attempt to set up the server and client independently.
-Communication is established between the systems by updating their
-configurations and a notification sent to the client. This should cause the
-client to request an IXFR from the server. (The exception is test 4, where the
-request is a result of the expiration of the SOA refresh time.)
-
-A check of zone files - or in these tests, of SOA serial number - can only
-reveal that a transfer has taken place. To check what has happened,
-e.g. whether the transfer was via UDP or whether a TCP request took place,
-the BIND 10 log file is searched for known message IDs.
-
-The searching of the log files for message IDs is one of the reasons that,
-unlike other system tests, the IXFR set of tests is broken up into separate
-tests that require the stopping and starting of nameservers (and tidying up of
-log files) between each test. Doing this means that only the existence of a
-particular message ID needs to be checked - there is no risk that another test
-produced it. The other reason is that the each IXFR test requires the
-nameservers to be in a specific state at the start of the test; this is easier
-to assure if they are not updating one another as the result of configuration
-settings established in the previous test.
-
-Test Files
-==========
-
-Data Files
-----------
-(All within tests/system/ixfr. Some .in files are processed to substitute
-for build variables in the build process to give the files listed here.)
-
-db.example.nX. These files hold the RRs for a zone for which should not
-fit within a single UDP packet. The files are different versions of the zone
-- the N-0 version (i.e. the latest version - "N" - the "-0" is present so
-that the files have a consistent name), N-2 etc. (See the full description
-of the tests for the meaning of N-2 etc.)
-
-db.example.common: A set of RRs to bulk out the zone to be larger than can
-be contained in a single UDP packet.
-
-db.example.n2.refresh: The N-2 version of the zone, but with a small SOA
-refresh time (for test 4).
-
-named_xxxx.conf: Various BIND 9 configuration files with NOTIFYs and/or
-IXFR enabled or disabled.
-
-Directories
------------
-The tests/system/ixfr directory holds the IXFR tests. Within that
-directory are subdirectories in-1 through in-4 for each test. And within
-each test directory are the directories ns1 (for the BIND 9 nameserver)
-and nsx2 (for the BIND 10 nameserver).
-
-Shell Scripts
--------------
-The IXFR tests use the same framework as the rest of the system tests,
-being based around shell scripts. Many have a ".in" form as they require
-substitution of build variables before they can be used, and so are
-listed in configure.ac. The files specific to the IXFR tests are:
-
-tests/system/ixfr/ixfr_init.sh.in: defines environment variables and shell
-subroutines used in the tests. (This references system/conf.sh.in which
-defines most of them.)
-
-tests/system/ixfr/common_tests.sh.in: tests in-1 and in-2 are virtually
-identical - this holds the common code.
diff --git a/tests/system/ixfr/b10-config.db.in b/tests/system/ixfr/b10-config.db.in
deleted file mode 100644
index a36117d..0000000
--- a/tests/system/ixfr/b10-config.db.in
+++ /dev/null
@@ -1,51 +0,0 @@
-{"version": 2,
- "Xfrin": {
- "zones": [{
- "master_addr": "10.53.0.1",
- "master_port": 53210,
- "name": "example.",
- "use_ixfr": true
- }]
- },
- "Auth": {
- "listen_on": [{
- "address": "10.53.0.2",
- "port": 53210
- }],
- "database_file": "@abs_builddir@/zone.sqlite3"
- },
- "data_sources": {
- "classes": {
- "IN": [{
- "type": "sqlite3",
- "params": {
- "database_file": "@abs_builddir@/zone.sqlite3"
- }
- }]
- }
- },
- "Logging": {
- "loggers": [{
- "name": "*",
- "severity": "DEBUG",
- "output_options": [],
- "debuglevel": 99
- }]
- },
- "Zonemgr": {
- "secondary_zones": [{
- "name": "example.",
- "class": "IN"
- }]
- },
- "Init": {
- "components": {
- "b10-auth": {"kind": "needed", "special": "auth" },
- "b10-xfrin": { "address": "Xfrin", "kind": "dispensable" },
- "b10-xfrout": { "address": "Xfrout", "kind": "dispensable" },
- "b10-zonemgr": { "address": "Zonemgr", "kind": "dispensable" },
- "b10-stats": { "address": "Stats", "kind": "dispensable" },
- "b10-cmdctl": { "special": "cmdctl", "kind": "needed" }
- }
- }
-}
diff --git a/tests/system/ixfr/clean_ns.sh b/tests/system/ixfr/clean_ns.sh
deleted file mode 100644
index 88f4ff1..0000000
--- a/tests/system/ixfr/clean_ns.sh
+++ /dev/null
@@ -1,28 +0,0 @@
-#!/bin/sh
-#
-# Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-# PERFORMANCE OF THIS SOFTWARE.
-
-# Clean up nameserver directories after zone transfer tests.
-
-rm -f ns1/named.conf
-rm -f ns1/db.example*
-rm -f ns1/named.memstats
-
-rm -f nsx2/bind10.run
-rm -f nsx2/b10-config.db
-rm -f ../zone.sqlite3
-
-rm -f client.dig
-rm -f server.dig
diff --git a/tests/system/ixfr/common_tests.sh.in b/tests/system/ixfr/common_tests.sh.in
deleted file mode 100644
index 90d0284..0000000
--- a/tests/system/ixfr/common_tests.sh.in
+++ /dev/null
@@ -1,78 +0,0 @@
-#!/bin/sh
-#
-# Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-# PERFORMANCE OF THIS SOFTWARE.
-
-# \file
-# This script is used in a couple of IXFR tests.
-#
-# Preconditions:\n
-# The BIND 9 nameserver (ns1, 10.53.0.1, acting as the IXFR server) is loaded
-# with the N-4 version of the zone. (It may hold prior versions as well.)
-# Notifications are disabled.
-#
-# The BIND 10 nameserver (nsx2, 10.53.0.2, acting as the IXFR client) is loaded
-# with an earlier (unspecified) version of the zone.
-#
-# Actions:\n
-# This script updates the IXFR server with the N-2 and N-0 versions of the zone.
-# It then updates the BIND 10 configuration so that it looks for IXFRs from
-# the IXFR server and causes the server to send the client a NOTIFY. After
-# waiting for the client to update from the server, it compares ther zones of
-# the two system, reporting an error if they are different.
-#
-# Caller Actions:\n
-# The caller can pre-load the BIND 10 IXFR client with whatever version of the
-# zone it requires. It can also load the BIND 9 IXFR server with zones earlier
-# than N-4.
-#
-# After this test has finished, it is up to the caller to check the logs
-# to see if they report the expected behavior.
-#
-# \return 0 if the script executed successfully, non-zero otherwise
-
-# Set up variables etc.
-. @abs_top_builddir@/tests/system/conf.sh
-. $IXFR_TOP/ixfr_init.sh
-
-set -e
-
-# Store the SOA serial number of the BIND 10 client for later use.
-old_client_serial=`$DIG_SOA @$CLIENT_IP | $AWK '{print $3}'`
-echo "I:$CLIENT_NAME SOA serial of IXFR client is $old_client_serial"
-
-# Load the BIND 9 system (the IXFR server) with the "n - 2" and "n" version of
-# the zones. With ixfr-from-differences set to "yes", the nameserver should
-# generate the differences between them.
-echo "I:$SERVER_NAME updating IXFR-server for ixfr-in tests"
-update_server_zone $SERVER_NAME $SERVER_IP $IXFR_TOP/db.example.n2
-
-# Wait a bit - it seems that if two updates are loaded in quick succession,
-# the second sometimes gets lost.
-sleep 5
-update_server_zone $SERVER_NAME $SERVER_IP $IXFR_TOP/db.example.n0
-
-echo "I:$CLIENT_NAME forcing IXFR client to retrieve new version of the zone"
-$RUN_BINDCTL << .
-Xfrin retransfer zone_name="example"
-.
-
-# Wait for the client to update itself.
-wait_for_update $CLIENT_NAME $CLIENT_IP $old_client_serial
-
-# Has updated, compare the client and server's versions of the zone s- they
-# should be the same.
-compare_zones $SERVER_NAME $SERVER_IP $CLIENT_NAME $CLIENT_IP
-
-set +e
diff --git a/tests/system/ixfr/db.example.common b/tests/system/ixfr/db.example.common
deleted file mode 100644
index 90435ce..0000000
--- a/tests/system/ixfr/db.example.common
+++ /dev/null
@@ -1,1556 +0,0 @@
-; Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
-;
-; Permission to use, copy, modify, and/or distribute this software for any
-; purpose with or without fee is hereby granted, provided that the above
-; copyright notice and this permission notice appear in all copies.
-;
-; THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-; REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-; AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-; INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-; LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-; PERFORMANCE OF THIS SOFTWARE.
-
-; This files holds a number of AAAA records to bulk out a zone file beyond
-; 16kB. It is used in tests where it is required that the contents of a zone
-; do not fit into a single UDP packet.
-
-aaaa-000 IN AAAA 2001:db8::0000
-aaaa-001 IN AAAA 2001:db8::0001
-aaaa-002 IN AAAA 2001:db8::0002
-aaaa-003 IN AAAA 2001:db8::0003
-aaaa-004 IN AAAA 2001:db8::0004
-aaaa-005 IN AAAA 2001:db8::0005
-aaaa-006 IN AAAA 2001:db8::0006
-aaaa-007 IN AAAA 2001:db8::0007
-aaaa-008 IN AAAA 2001:db8::0008
-aaaa-009 IN AAAA 2001:db8::0009
-aaaa-010 IN AAAA 2001:db8::000a
-aaaa-011 IN AAAA 2001:db8::000b
-aaaa-012 IN AAAA 2001:db8::000c
-aaaa-013 IN AAAA 2001:db8::000d
-aaaa-014 IN AAAA 2001:db8::000e
-aaaa-015 IN AAAA 2001:db8::000f
-aaaa-016 IN AAAA 2001:db8::0010
-aaaa-017 IN AAAA 2001:db8::0011
-aaaa-018 IN AAAA 2001:db8::0012
-aaaa-019 IN AAAA 2001:db8::0013
-aaaa-020 IN AAAA 2001:db8::0014
-aaaa-021 IN AAAA 2001:db8::0015
-aaaa-022 IN AAAA 2001:db8::0016
-aaaa-023 IN AAAA 2001:db8::0017
-aaaa-024 IN AAAA 2001:db8::0018
-aaaa-025 IN AAAA 2001:db8::0019
-aaaa-026 IN AAAA 2001:db8::001a
-aaaa-027 IN AAAA 2001:db8::001b
-aaaa-028 IN AAAA 2001:db8::001c
-aaaa-029 IN AAAA 2001:db8::001d
-aaaa-030 IN AAAA 2001:db8::001e
-aaaa-031 IN AAAA 2001:db8::001f
-aaaa-032 IN AAAA 2001:db8::0020
-aaaa-033 IN AAAA 2001:db8::0021
-aaaa-034 IN AAAA 2001:db8::0022
-aaaa-035 IN AAAA 2001:db8::0023
-aaaa-036 IN AAAA 2001:db8::0024
-aaaa-037 IN AAAA 2001:db8::0025
-aaaa-038 IN AAAA 2001:db8::0026
-aaaa-039 IN AAAA 2001:db8::0027
-aaaa-040 IN AAAA 2001:db8::0028
-aaaa-041 IN AAAA 2001:db8::0029
-aaaa-042 IN AAAA 2001:db8::002a
-aaaa-043 IN AAAA 2001:db8::002b
-aaaa-044 IN AAAA 2001:db8::002c
-aaaa-045 IN AAAA 2001:db8::002d
-aaaa-046 IN AAAA 2001:db8::002e
-aaaa-047 IN AAAA 2001:db8::002f
-aaaa-048 IN AAAA 2001:db8::0030
-aaaa-049 IN AAAA 2001:db8::0031
-aaaa-050 IN AAAA 2001:db8::0032
-aaaa-051 IN AAAA 2001:db8::0033
-aaaa-052 IN AAAA 2001:db8::0034
-aaaa-053 IN AAAA 2001:db8::0035
-aaaa-054 IN AAAA 2001:db8::0036
-aaaa-055 IN AAAA 2001:db8::0037
-aaaa-056 IN AAAA 2001:db8::0038
-aaaa-057 IN AAAA 2001:db8::0039
-aaaa-058 IN AAAA 2001:db8::003a
-aaaa-059 IN AAAA 2001:db8::003b
-aaaa-060 IN AAAA 2001:db8::003c
-aaaa-061 IN AAAA 2001:db8::003d
-aaaa-062 IN AAAA 2001:db8::003e
-aaaa-063 IN AAAA 2001:db8::003f
-aaaa-064 IN AAAA 2001:db8::0040
-aaaa-065 IN AAAA 2001:db8::0041
-aaaa-066 IN AAAA 2001:db8::0042
-aaaa-067 IN AAAA 2001:db8::0043
-aaaa-068 IN AAAA 2001:db8::0044
-aaaa-069 IN AAAA 2001:db8::0045
-aaaa-070 IN AAAA 2001:db8::0046
-aaaa-071 IN AAAA 2001:db8::0047
-aaaa-072 IN AAAA 2001:db8::0048
-aaaa-073 IN AAAA 2001:db8::0049
-aaaa-074 IN AAAA 2001:db8::004a
-aaaa-075 IN AAAA 2001:db8::004b
-aaaa-076 IN AAAA 2001:db8::004c
-aaaa-077 IN AAAA 2001:db8::004d
-aaaa-078 IN AAAA 2001:db8::004e
-aaaa-079 IN AAAA 2001:db8::004f
-aaaa-080 IN AAAA 2001:db8::0050
-aaaa-081 IN AAAA 2001:db8::0051
-aaaa-082 IN AAAA 2001:db8::0052
-aaaa-083 IN AAAA 2001:db8::0053
-aaaa-084 IN AAAA 2001:db8::0054
-aaaa-085 IN AAAA 2001:db8::0055
-aaaa-086 IN AAAA 2001:db8::0056
-aaaa-087 IN AAAA 2001:db8::0057
-aaaa-088 IN AAAA 2001:db8::0058
-aaaa-089 IN AAAA 2001:db8::0059
-aaaa-090 IN AAAA 2001:db8::005a
-aaaa-091 IN AAAA 2001:db8::005b
-aaaa-092 IN AAAA 2001:db8::005c
-aaaa-093 IN AAAA 2001:db8::005d
-aaaa-094 IN AAAA 2001:db8::005e
-aaaa-095 IN AAAA 2001:db8::005f
-aaaa-096 IN AAAA 2001:db8::0060
-aaaa-097 IN AAAA 2001:db8::0061
-aaaa-098 IN AAAA 2001:db8::0062
-aaaa-099 IN AAAA 2001:db8::0063
-aaaa-100 IN AAAA 2001:db8::0064
-aaaa-101 IN AAAA 2001:db8::0065
-aaaa-102 IN AAAA 2001:db8::0066
-aaaa-103 IN AAAA 2001:db8::0067
-aaaa-104 IN AAAA 2001:db8::0068
-aaaa-105 IN AAAA 2001:db8::0069
-aaaa-106 IN AAAA 2001:db8::006a
-aaaa-107 IN AAAA 2001:db8::006b
-aaaa-108 IN AAAA 2001:db8::006c
-aaaa-109 IN AAAA 2001:db8::006d
-aaaa-110 IN AAAA 2001:db8::006e
-aaaa-111 IN AAAA 2001:db8::006f
-aaaa-112 IN AAAA 2001:db8::0070
-aaaa-113 IN AAAA 2001:db8::0071
-aaaa-114 IN AAAA 2001:db8::0072
-aaaa-115 IN AAAA 2001:db8::0073
-aaaa-116 IN AAAA 2001:db8::0074
-aaaa-117 IN AAAA 2001:db8::0075
-aaaa-118 IN AAAA 2001:db8::0076
-aaaa-119 IN AAAA 2001:db8::0077
-aaaa-120 IN AAAA 2001:db8::0078
-aaaa-121 IN AAAA 2001:db8::0079
-aaaa-122 IN AAAA 2001:db8::007a
-aaaa-123 IN AAAA 2001:db8::007b
-aaaa-124 IN AAAA 2001:db8::007c
-aaaa-125 IN AAAA 2001:db8::007d
-aaaa-126 IN AAAA 2001:db8::007e
-aaaa-127 IN AAAA 2001:db8::007f
-aaaa-128 IN AAAA 2001:db8::0080
-aaaa-129 IN AAAA 2001:db8::0081
-aaaa-130 IN AAAA 2001:db8::0082
-aaaa-131 IN AAAA 2001:db8::0083
-aaaa-132 IN AAAA 2001:db8::0084
-aaaa-133 IN AAAA 2001:db8::0085
-aaaa-134 IN AAAA 2001:db8::0086
-aaaa-135 IN AAAA 2001:db8::0087
-aaaa-136 IN AAAA 2001:db8::0088
-aaaa-137 IN AAAA 2001:db8::0089
-aaaa-138 IN AAAA 2001:db8::008a
-aaaa-139 IN AAAA 2001:db8::008b
-aaaa-140 IN AAAA 2001:db8::008c
-aaaa-141 IN AAAA 2001:db8::008d
-aaaa-142 IN AAAA 2001:db8::008e
-aaaa-143 IN AAAA 2001:db8::008f
-aaaa-144 IN AAAA 2001:db8::0090
-aaaa-145 IN AAAA 2001:db8::0091
-aaaa-146 IN AAAA 2001:db8::0092
-aaaa-147 IN AAAA 2001:db8::0093
-aaaa-148 IN AAAA 2001:db8::0094
-aaaa-149 IN AAAA 2001:db8::0095
-aaaa-150 IN AAAA 2001:db8::0096
-aaaa-151 IN AAAA 2001:db8::0097
-aaaa-152 IN AAAA 2001:db8::0098
-aaaa-153 IN AAAA 2001:db8::0099
-aaaa-154 IN AAAA 2001:db8::009a
-aaaa-155 IN AAAA 2001:db8::009b
-aaaa-156 IN AAAA 2001:db8::009c
-aaaa-157 IN AAAA 2001:db8::009d
-aaaa-158 IN AAAA 2001:db8::009e
-aaaa-159 IN AAAA 2001:db8::009f
-aaaa-160 IN AAAA 2001:db8::00a0
-aaaa-161 IN AAAA 2001:db8::00a1
-aaaa-162 IN AAAA 2001:db8::00a2
-aaaa-163 IN AAAA 2001:db8::00a3
-aaaa-164 IN AAAA 2001:db8::00a4
-aaaa-165 IN AAAA 2001:db8::00a5
-aaaa-166 IN AAAA 2001:db8::00a6
-aaaa-167 IN AAAA 2001:db8::00a7
-aaaa-168 IN AAAA 2001:db8::00a8
-aaaa-169 IN AAAA 2001:db8::00a9
-aaaa-170 IN AAAA 2001:db8::00aa
-aaaa-171 IN AAAA 2001:db8::00ab
-aaaa-172 IN AAAA 2001:db8::00ac
-aaaa-173 IN AAAA 2001:db8::00ad
-aaaa-174 IN AAAA 2001:db8::00ae
-aaaa-175 IN AAAA 2001:db8::00af
-aaaa-176 IN AAAA 2001:db8::00b0
-aaaa-177 IN AAAA 2001:db8::00b1
-aaaa-178 IN AAAA 2001:db8::00b2
-aaaa-179 IN AAAA 2001:db8::00b3
-aaaa-180 IN AAAA 2001:db8::00b4
-aaaa-181 IN AAAA 2001:db8::00b5
-aaaa-182 IN AAAA 2001:db8::00b6
-aaaa-183 IN AAAA 2001:db8::00b7
-aaaa-184 IN AAAA 2001:db8::00b8
-aaaa-185 IN AAAA 2001:db8::00b9
-aaaa-186 IN AAAA 2001:db8::00ba
-aaaa-187 IN AAAA 2001:db8::00bb
-aaaa-188 IN AAAA 2001:db8::00bc
-aaaa-189 IN AAAA 2001:db8::00bd
-aaaa-190 IN AAAA 2001:db8::00be
-aaaa-191 IN AAAA 2001:db8::00bf
-aaaa-192 IN AAAA 2001:db8::00c0
-aaaa-193 IN AAAA 2001:db8::00c1
-aaaa-194 IN AAAA 2001:db8::00c2
-aaaa-195 IN AAAA 2001:db8::00c3
-aaaa-196 IN AAAA 2001:db8::00c4
-aaaa-197 IN AAAA 2001:db8::00c5
-aaaa-198 IN AAAA 2001:db8::00c6
-aaaa-199 IN AAAA 2001:db8::00c7
-aaaa-200 IN AAAA 2001:db8::00c8
-aaaa-201 IN AAAA 2001:db8::00c9
-aaaa-202 IN AAAA 2001:db8::00ca
-aaaa-203 IN AAAA 2001:db8::00cb
-aaaa-204 IN AAAA 2001:db8::00cc
-aaaa-205 IN AAAA 2001:db8::00cd
-aaaa-206 IN AAAA 2001:db8::00ce
-aaaa-207 IN AAAA 2001:db8::00cf
-aaaa-208 IN AAAA 2001:db8::00d0
-aaaa-209 IN AAAA 2001:db8::00d1
-aaaa-210 IN AAAA 2001:db8::00d2
-aaaa-211 IN AAAA 2001:db8::00d3
-aaaa-212 IN AAAA 2001:db8::00d4
-aaaa-213 IN AAAA 2001:db8::00d5
-aaaa-214 IN AAAA 2001:db8::00d6
-aaaa-215 IN AAAA 2001:db8::00d7
-aaaa-216 IN AAAA 2001:db8::00d8
-aaaa-217 IN AAAA 2001:db8::00d9
-aaaa-218 IN AAAA 2001:db8::00da
-aaaa-219 IN AAAA 2001:db8::00db
-aaaa-220 IN AAAA 2001:db8::00dc
-aaaa-221 IN AAAA 2001:db8::00dd
-aaaa-222 IN AAAA 2001:db8::00de
-aaaa-223 IN AAAA 2001:db8::00df
-aaaa-224 IN AAAA 2001:db8::00e0
-aaaa-225 IN AAAA 2001:db8::00e1
-aaaa-226 IN AAAA 2001:db8::00e2
-aaaa-227 IN AAAA 2001:db8::00e3
-aaaa-228 IN AAAA 2001:db8::00e4
-aaaa-229 IN AAAA 2001:db8::00e5
-aaaa-230 IN AAAA 2001:db8::00e6
-aaaa-231 IN AAAA 2001:db8::00e7
-aaaa-232 IN AAAA 2001:db8::00e8
-aaaa-233 IN AAAA 2001:db8::00e9
-aaaa-234 IN AAAA 2001:db8::00ea
-aaaa-235 IN AAAA 2001:db8::00eb
-aaaa-236 IN AAAA 2001:db8::00ec
-aaaa-237 IN AAAA 2001:db8::00ed
-aaaa-238 IN AAAA 2001:db8::00ee
-aaaa-239 IN AAAA 2001:db8::00ef
-aaaa-240 IN AAAA 2001:db8::00f0
-aaaa-241 IN AAAA 2001:db8::00f1
-aaaa-242 IN AAAA 2001:db8::00f2
-aaaa-243 IN AAAA 2001:db8::00f3
-aaaa-244 IN AAAA 2001:db8::00f4
-aaaa-245 IN AAAA 2001:db8::00f5
-aaaa-246 IN AAAA 2001:db8::00f6
-aaaa-247 IN AAAA 2001:db8::00f7
-aaaa-248 IN AAAA 2001:db8::00f8
-aaaa-249 IN AAAA 2001:db8::00f9
-aaaa-250 IN AAAA 2001:db8::00fa
-aaaa-251 IN AAAA 2001:db8::00fb
-aaaa-252 IN AAAA 2001:db8::00fc
-aaaa-253 IN AAAA 2001:db8::00fd
-aaaa-254 IN AAAA 2001:db8::00fe
-aaaa-255 IN AAAA 2001:db8::00ff
-aaaa-256 IN AAAA 2001:db8::0100
-aaaa-257 IN AAAA 2001:db8::0101
-aaaa-258 IN AAAA 2001:db8::0102
-aaaa-259 IN AAAA 2001:db8::0103
-aaaa-260 IN AAAA 2001:db8::0104
-aaaa-261 IN AAAA 2001:db8::0105
-aaaa-262 IN AAAA 2001:db8::0106
-aaaa-263 IN AAAA 2001:db8::0107
-aaaa-264 IN AAAA 2001:db8::0108
-aaaa-265 IN AAAA 2001:db8::0109
-aaaa-266 IN AAAA 2001:db8::010a
-aaaa-267 IN AAAA 2001:db8::010b
-aaaa-268 IN AAAA 2001:db8::010c
-aaaa-269 IN AAAA 2001:db8::010d
-aaaa-270 IN AAAA 2001:db8::010e
-aaaa-271 IN AAAA 2001:db8::010f
-aaaa-272 IN AAAA 2001:db8::0110
-aaaa-273 IN AAAA 2001:db8::0111
-aaaa-274 IN AAAA 2001:db8::0112
-aaaa-275 IN AAAA 2001:db8::0113
-aaaa-276 IN AAAA 2001:db8::0114
-aaaa-277 IN AAAA 2001:db8::0115
-aaaa-278 IN AAAA 2001:db8::0116
-aaaa-279 IN AAAA 2001:db8::0117
-aaaa-280 IN AAAA 2001:db8::0118
-aaaa-281 IN AAAA 2001:db8::0119
-aaaa-282 IN AAAA 2001:db8::011a
-aaaa-283 IN AAAA 2001:db8::011b
-aaaa-284 IN AAAA 2001:db8::011c
-aaaa-285 IN AAAA 2001:db8::011d
-aaaa-286 IN AAAA 2001:db8::011e
-aaaa-287 IN AAAA 2001:db8::011f
-aaaa-288 IN AAAA 2001:db8::0120
-aaaa-289 IN AAAA 2001:db8::0121
-aaaa-290 IN AAAA 2001:db8::0122
-aaaa-291 IN AAAA 2001:db8::0123
-aaaa-292 IN AAAA 2001:db8::0124
-aaaa-293 IN AAAA 2001:db8::0125
-aaaa-294 IN AAAA 2001:db8::0126
-aaaa-295 IN AAAA 2001:db8::0127
-aaaa-296 IN AAAA 2001:db8::0128
-aaaa-297 IN AAAA 2001:db8::0129
-aaaa-298 IN AAAA 2001:db8::012a
-aaaa-299 IN AAAA 2001:db8::012b
-aaaa-300 IN AAAA 2001:db8::012c
-aaaa-301 IN AAAA 2001:db8::012d
-aaaa-302 IN AAAA 2001:db8::012e
-aaaa-303 IN AAAA 2001:db8::012f
-aaaa-304 IN AAAA 2001:db8::0130
-aaaa-305 IN AAAA 2001:db8::0131
-aaaa-306 IN AAAA 2001:db8::0132
-aaaa-307 IN AAAA 2001:db8::0133
-aaaa-308 IN AAAA 2001:db8::0134
-aaaa-309 IN AAAA 2001:db8::0135
-aaaa-310 IN AAAA 2001:db8::0136
-aaaa-311 IN AAAA 2001:db8::0137
-aaaa-312 IN AAAA 2001:db8::0138
-aaaa-313 IN AAAA 2001:db8::0139
-aaaa-314 IN AAAA 2001:db8::013a
-aaaa-315 IN AAAA 2001:db8::013b
-aaaa-316 IN AAAA 2001:db8::013c
-aaaa-317 IN AAAA 2001:db8::013d
-aaaa-318 IN AAAA 2001:db8::013e
-aaaa-319 IN AAAA 2001:db8::013f
-aaaa-320 IN AAAA 2001:db8::0140
-aaaa-321 IN AAAA 2001:db8::0141
-aaaa-322 IN AAAA 2001:db8::0142
-aaaa-323 IN AAAA 2001:db8::0143
-aaaa-324 IN AAAA 2001:db8::0144
-aaaa-325 IN AAAA 2001:db8::0145
-aaaa-326 IN AAAA 2001:db8::0146
-aaaa-327 IN AAAA 2001:db8::0147
-aaaa-328 IN AAAA 2001:db8::0148
-aaaa-329 IN AAAA 2001:db8::0149
-aaaa-330 IN AAAA 2001:db8::014a
-aaaa-331 IN AAAA 2001:db8::014b
-aaaa-332 IN AAAA 2001:db8::014c
-aaaa-333 IN AAAA 2001:db8::014d
-aaaa-334 IN AAAA 2001:db8::014e
-aaaa-335 IN AAAA 2001:db8::014f
-aaaa-336 IN AAAA 2001:db8::0150
-aaaa-337 IN AAAA 2001:db8::0151
-aaaa-338 IN AAAA 2001:db8::0152
-aaaa-339 IN AAAA 2001:db8::0153
-aaaa-340 IN AAAA 2001:db8::0154
-aaaa-341 IN AAAA 2001:db8::0155
-aaaa-342 IN AAAA 2001:db8::0156
-aaaa-343 IN AAAA 2001:db8::0157
-aaaa-344 IN AAAA 2001:db8::0158
-aaaa-345 IN AAAA 2001:db8::0159
-aaaa-346 IN AAAA 2001:db8::015a
-aaaa-347 IN AAAA 2001:db8::015b
-aaaa-348 IN AAAA 2001:db8::015c
-aaaa-349 IN AAAA 2001:db8::015d
-aaaa-350 IN AAAA 2001:db8::015e
-aaaa-351 IN AAAA 2001:db8::015f
-aaaa-352 IN AAAA 2001:db8::0160
-aaaa-353 IN AAAA 2001:db8::0161
-aaaa-354 IN AAAA 2001:db8::0162
-aaaa-355 IN AAAA 2001:db8::0163
-aaaa-356 IN AAAA 2001:db8::0164
-aaaa-357 IN AAAA 2001:db8::0165
-aaaa-358 IN AAAA 2001:db8::0166
-aaaa-359 IN AAAA 2001:db8::0167
-aaaa-360 IN AAAA 2001:db8::0168
-aaaa-361 IN AAAA 2001:db8::0169
-aaaa-362 IN AAAA 2001:db8::016a
-aaaa-363 IN AAAA 2001:db8::016b
-aaaa-364 IN AAAA 2001:db8::016c
-aaaa-365 IN AAAA 2001:db8::016d
-aaaa-366 IN AAAA 2001:db8::016e
-aaaa-367 IN AAAA 2001:db8::016f
-aaaa-368 IN AAAA 2001:db8::0170
-aaaa-369 IN AAAA 2001:db8::0171
-aaaa-370 IN AAAA 2001:db8::0172
-aaaa-371 IN AAAA 2001:db8::0173
-aaaa-372 IN AAAA 2001:db8::0174
-aaaa-373 IN AAAA 2001:db8::0175
-aaaa-374 IN AAAA 2001:db8::0176
-aaaa-375 IN AAAA 2001:db8::0177
-aaaa-376 IN AAAA 2001:db8::0178
-aaaa-377 IN AAAA 2001:db8::0179
-aaaa-378 IN AAAA 2001:db8::017a
-aaaa-379 IN AAAA 2001:db8::017b
-aaaa-380 IN AAAA 2001:db8::017c
-aaaa-381 IN AAAA 2001:db8::017d
-aaaa-382 IN AAAA 2001:db8::017e
-aaaa-383 IN AAAA 2001:db8::017f
-aaaa-384 IN AAAA 2001:db8::0180
-aaaa-385 IN AAAA 2001:db8::0181
-aaaa-386 IN AAAA 2001:db8::0182
-aaaa-387 IN AAAA 2001:db8::0183
-aaaa-388 IN AAAA 2001:db8::0184
-aaaa-389 IN AAAA 2001:db8::0185
-aaaa-390 IN AAAA 2001:db8::0186
-aaaa-391 IN AAAA 2001:db8::0187
-aaaa-392 IN AAAA 2001:db8::0188
-aaaa-393 IN AAAA 2001:db8::0189
-aaaa-394 IN AAAA 2001:db8::018a
-aaaa-395 IN AAAA 2001:db8::018b
-aaaa-396 IN AAAA 2001:db8::018c
-aaaa-397 IN AAAA 2001:db8::018d
-aaaa-398 IN AAAA 2001:db8::018e
-aaaa-399 IN AAAA 2001:db8::018f
-aaaa-400 IN AAAA 2001:db8::0190
-aaaa-401 IN AAAA 2001:db8::0191
-aaaa-402 IN AAAA 2001:db8::0192
-aaaa-403 IN AAAA 2001:db8::0193
-aaaa-404 IN AAAA 2001:db8::0194
-aaaa-405 IN AAAA 2001:db8::0195
-aaaa-406 IN AAAA 2001:db8::0196
-aaaa-407 IN AAAA 2001:db8::0197
-aaaa-408 IN AAAA 2001:db8::0198
-aaaa-409 IN AAAA 2001:db8::0199
-aaaa-410 IN AAAA 2001:db8::019a
-aaaa-411 IN AAAA 2001:db8::019b
-aaaa-412 IN AAAA 2001:db8::019c
-aaaa-413 IN AAAA 2001:db8::019d
-aaaa-414 IN AAAA 2001:db8::019e
-aaaa-415 IN AAAA 2001:db8::019f
-aaaa-416 IN AAAA 2001:db8::01a0
-aaaa-417 IN AAAA 2001:db8::01a1
-aaaa-418 IN AAAA 2001:db8::01a2
-aaaa-419 IN AAAA 2001:db8::01a3
-aaaa-420 IN AAAA 2001:db8::01a4
-aaaa-421 IN AAAA 2001:db8::01a5
-aaaa-422 IN AAAA 2001:db8::01a6
-aaaa-423 IN AAAA 2001:db8::01a7
-aaaa-424 IN AAAA 2001:db8::01a8
-aaaa-425 IN AAAA 2001:db8::01a9
-aaaa-426 IN AAAA 2001:db8::01aa
-aaaa-427 IN AAAA 2001:db8::01ab
-aaaa-428 IN AAAA 2001:db8::01ac
-aaaa-429 IN AAAA 2001:db8::01ad
-aaaa-430 IN AAAA 2001:db8::01ae
-aaaa-431 IN AAAA 2001:db8::01af
-aaaa-432 IN AAAA 2001:db8::01b0
-aaaa-433 IN AAAA 2001:db8::01b1
-aaaa-434 IN AAAA 2001:db8::01b2
-aaaa-435 IN AAAA 2001:db8::01b3
-aaaa-436 IN AAAA 2001:db8::01b4
-aaaa-437 IN AAAA 2001:db8::01b5
-aaaa-438 IN AAAA 2001:db8::01b6
-aaaa-439 IN AAAA 2001:db8::01b7
-aaaa-440 IN AAAA 2001:db8::01b8
-aaaa-441 IN AAAA 2001:db8::01b9
-aaaa-442 IN AAAA 2001:db8::01ba
-aaaa-443 IN AAAA 2001:db8::01bb
-aaaa-444 IN AAAA 2001:db8::01bc
-aaaa-445 IN AAAA 2001:db8::01bd
-aaaa-446 IN AAAA 2001:db8::01be
-aaaa-447 IN AAAA 2001:db8::01bf
-aaaa-448 IN AAAA 2001:db8::01c0
-aaaa-449 IN AAAA 2001:db8::01c1
-aaaa-450 IN AAAA 2001:db8::01c2
-aaaa-451 IN AAAA 2001:db8::01c3
-aaaa-452 IN AAAA 2001:db8::01c4
-aaaa-453 IN AAAA 2001:db8::01c5
-aaaa-454 IN AAAA 2001:db8::01c6
-aaaa-455 IN AAAA 2001:db8::01c7
-aaaa-456 IN AAAA 2001:db8::01c8
-aaaa-457 IN AAAA 2001:db8::01c9
-aaaa-458 IN AAAA 2001:db8::01ca
-aaaa-459 IN AAAA 2001:db8::01cb
-aaaa-460 IN AAAA 2001:db8::01cc
-aaaa-461 IN AAAA 2001:db8::01cd
-aaaa-462 IN AAAA 2001:db8::01ce
-aaaa-463 IN AAAA 2001:db8::01cf
-aaaa-464 IN AAAA 2001:db8::01d0
-aaaa-465 IN AAAA 2001:db8::01d1
-aaaa-466 IN AAAA 2001:db8::01d2
-aaaa-467 IN AAAA 2001:db8::01d3
-aaaa-468 IN AAAA 2001:db8::01d4
-aaaa-469 IN AAAA 2001:db8::01d5
-aaaa-470 IN AAAA 2001:db8::01d6
-aaaa-471 IN AAAA 2001:db8::01d7
-aaaa-472 IN AAAA 2001:db8::01d8
-aaaa-473 IN AAAA 2001:db8::01d9
-aaaa-474 IN AAAA 2001:db8::01da
-aaaa-475 IN AAAA 2001:db8::01db
-aaaa-476 IN AAAA 2001:db8::01dc
-aaaa-477 IN AAAA 2001:db8::01dd
-aaaa-478 IN AAAA 2001:db8::01de
-aaaa-479 IN AAAA 2001:db8::01df
-aaaa-480 IN AAAA 2001:db8::01e0
-aaaa-481 IN AAAA 2001:db8::01e1
-aaaa-482 IN AAAA 2001:db8::01e2
-aaaa-483 IN AAAA 2001:db8::01e3
-aaaa-484 IN AAAA 2001:db8::01e4
-aaaa-485 IN AAAA 2001:db8::01e5
-aaaa-486 IN AAAA 2001:db8::01e6
-aaaa-487 IN AAAA 2001:db8::01e7
-aaaa-488 IN AAAA 2001:db8::01e8
-aaaa-489 IN AAAA 2001:db8::01e9
-aaaa-490 IN AAAA 2001:db8::01ea
-aaaa-491 IN AAAA 2001:db8::01eb
-aaaa-492 IN AAAA 2001:db8::01ec
-aaaa-493 IN AAAA 2001:db8::01ed
-aaaa-494 IN AAAA 2001:db8::01ee
-aaaa-495 IN AAAA 2001:db8::01ef
-aaaa-496 IN AAAA 2001:db8::01f0
-aaaa-497 IN AAAA 2001:db8::01f1
-aaaa-498 IN AAAA 2001:db8::01f2
-aaaa-499 IN AAAA 2001:db8::01f3
-aaaa-500 IN AAAA 2001:db8::01f4
-aaaa-501 IN AAAA 2001:db8::01f5
-aaaa-502 IN AAAA 2001:db8::01f6
-aaaa-503 IN AAAA 2001:db8::01f7
-aaaa-504 IN AAAA 2001:db8::01f8
-aaaa-505 IN AAAA 2001:db8::01f9
-aaaa-506 IN AAAA 2001:db8::01fa
-aaaa-507 IN AAAA 2001:db8::01fb
-aaaa-508 IN AAAA 2001:db8::01fc
-aaaa-509 IN AAAA 2001:db8::01fd
-aaaa-510 IN AAAA 2001:db8::01fe
-aaaa-511 IN AAAA 2001:db8::01ff
-
-bbbb-000 IN AAAA 2001:db8::1:0000
-bbbb-001 IN AAAA 2001:db8::1:0001
-bbbb-002 IN AAAA 2001:db8::1:0002
-bbbb-003 IN AAAA 2001:db8::1:0003
-bbbb-004 IN AAAA 2001:db8::1:0004
-bbbb-005 IN AAAA 2001:db8::1:0005
-bbbb-006 IN AAAA 2001:db8::1:0006
-bbbb-007 IN AAAA 2001:db8::1:0007
-bbbb-008 IN AAAA 2001:db8::1:0008
-bbbb-009 IN AAAA 2001:db8::1:0009
-bbbb-010 IN AAAA 2001:db8::1:000a
-bbbb-011 IN AAAA 2001:db8::1:000b
-bbbb-012 IN AAAA 2001:db8::1:000c
-bbbb-013 IN AAAA 2001:db8::1:000d
-bbbb-014 IN AAAA 2001:db8::1:000e
-bbbb-015 IN AAAA 2001:db8::1:000f
-bbbb-016 IN AAAA 2001:db8::1:0010
-bbbb-017 IN AAAA 2001:db8::1:0011
-bbbb-018 IN AAAA 2001:db8::1:0012
-bbbb-019 IN AAAA 2001:db8::1:0013
-bbbb-020 IN AAAA 2001:db8::1:0014
-bbbb-021 IN AAAA 2001:db8::1:0015
-bbbb-022 IN AAAA 2001:db8::1:0016
-bbbb-023 IN AAAA 2001:db8::1:0017
-bbbb-024 IN AAAA 2001:db8::1:0018
-bbbb-025 IN AAAA 2001:db8::1:0019
-bbbb-026 IN AAAA 2001:db8::1:001a
-bbbb-027 IN AAAA 2001:db8::1:001b
-bbbb-028 IN AAAA 2001:db8::1:001c
-bbbb-029 IN AAAA 2001:db8::1:001d
-bbbb-030 IN AAAA 2001:db8::1:001e
-bbbb-031 IN AAAA 2001:db8::1:001f
-bbbb-032 IN AAAA 2001:db8::1:0020
-bbbb-033 IN AAAA 2001:db8::1:0021
-bbbb-034 IN AAAA 2001:db8::1:0022
-bbbb-035 IN AAAA 2001:db8::1:0023
-bbbb-036 IN AAAA 2001:db8::1:0024
-bbbb-037 IN AAAA 2001:db8::1:0025
-bbbb-038 IN AAAA 2001:db8::1:0026
-bbbb-039 IN AAAA 2001:db8::1:0027
-bbbb-040 IN AAAA 2001:db8::1:0028
-bbbb-041 IN AAAA 2001:db8::1:0029
-bbbb-042 IN AAAA 2001:db8::1:002a
-bbbb-043 IN AAAA 2001:db8::1:002b
-bbbb-044 IN AAAA 2001:db8::1:002c
-bbbb-045 IN AAAA 2001:db8::1:002d
-bbbb-046 IN AAAA 2001:db8::1:002e
-bbbb-047 IN AAAA 2001:db8::1:002f
-bbbb-048 IN AAAA 2001:db8::1:0030
-bbbb-049 IN AAAA 2001:db8::1:0031
-bbbb-050 IN AAAA 2001:db8::1:0032
-bbbb-051 IN AAAA 2001:db8::1:0033
-bbbb-052 IN AAAA 2001:db8::1:0034
-bbbb-053 IN AAAA 2001:db8::1:0035
-bbbb-054 IN AAAA 2001:db8::1:0036
-bbbb-055 IN AAAA 2001:db8::1:0037
-bbbb-056 IN AAAA 2001:db8::1:0038
-bbbb-057 IN AAAA 2001:db8::1:0039
-bbbb-058 IN AAAA 2001:db8::1:003a
-bbbb-059 IN AAAA 2001:db8::1:003b
-bbbb-060 IN AAAA 2001:db8::1:003c
-bbbb-061 IN AAAA 2001:db8::1:003d
-bbbb-062 IN AAAA 2001:db8::1:003e
-bbbb-063 IN AAAA 2001:db8::1:003f
-bbbb-064 IN AAAA 2001:db8::1:0040
-bbbb-065 IN AAAA 2001:db8::1:0041
-bbbb-066 IN AAAA 2001:db8::1:0042
-bbbb-067 IN AAAA 2001:db8::1:0043
-bbbb-068 IN AAAA 2001:db8::1:0044
-bbbb-069 IN AAAA 2001:db8::1:0045
-bbbb-070 IN AAAA 2001:db8::1:0046
-bbbb-071 IN AAAA 2001:db8::1:0047
-bbbb-072 IN AAAA 2001:db8::1:0048
-bbbb-073 IN AAAA 2001:db8::1:0049
-bbbb-074 IN AAAA 2001:db8::1:004a
-bbbb-075 IN AAAA 2001:db8::1:004b
-bbbb-076 IN AAAA 2001:db8::1:004c
-bbbb-077 IN AAAA 2001:db8::1:004d
-bbbb-078 IN AAAA 2001:db8::1:004e
-bbbb-079 IN AAAA 2001:db8::1:004f
-bbbb-080 IN AAAA 2001:db8::1:0050
-bbbb-081 IN AAAA 2001:db8::1:0051
-bbbb-082 IN AAAA 2001:db8::1:0052
-bbbb-083 IN AAAA 2001:db8::1:0053
-bbbb-084 IN AAAA 2001:db8::1:0054
-bbbb-085 IN AAAA 2001:db8::1:0055
-bbbb-086 IN AAAA 2001:db8::1:0056
-bbbb-087 IN AAAA 2001:db8::1:0057
-bbbb-088 IN AAAA 2001:db8::1:0058
-bbbb-089 IN AAAA 2001:db8::1:0059
-bbbb-090 IN AAAA 2001:db8::1:005a
-bbbb-091 IN AAAA 2001:db8::1:005b
-bbbb-092 IN AAAA 2001:db8::1:005c
-bbbb-093 IN AAAA 2001:db8::1:005d
-bbbb-094 IN AAAA 2001:db8::1:005e
-bbbb-095 IN AAAA 2001:db8::1:005f
-bbbb-096 IN AAAA 2001:db8::1:0060
-bbbb-097 IN AAAA 2001:db8::1:0061
-bbbb-098 IN AAAA 2001:db8::1:0062
-bbbb-099 IN AAAA 2001:db8::1:0063
-bbbb-100 IN AAAA 2001:db8::1:0064
-bbbb-101 IN AAAA 2001:db8::1:0065
-bbbb-102 IN AAAA 2001:db8::1:0066
-bbbb-103 IN AAAA 2001:db8::1:0067
-bbbb-104 IN AAAA 2001:db8::1:0068
-bbbb-105 IN AAAA 2001:db8::1:0069
-bbbb-106 IN AAAA 2001:db8::1:006a
-bbbb-107 IN AAAA 2001:db8::1:006b
-bbbb-108 IN AAAA 2001:db8::1:006c
-bbbb-109 IN AAAA 2001:db8::1:006d
-bbbb-110 IN AAAA 2001:db8::1:006e
-bbbb-111 IN AAAA 2001:db8::1:006f
-bbbb-112 IN AAAA 2001:db8::1:0070
-bbbb-113 IN AAAA 2001:db8::1:0071
-bbbb-114 IN AAAA 2001:db8::1:0072
-bbbb-115 IN AAAA 2001:db8::1:0073
-bbbb-116 IN AAAA 2001:db8::1:0074
-bbbb-117 IN AAAA 2001:db8::1:0075
-bbbb-118 IN AAAA 2001:db8::1:0076
-bbbb-119 IN AAAA 2001:db8::1:0077
-bbbb-120 IN AAAA 2001:db8::1:0078
-bbbb-121 IN AAAA 2001:db8::1:0079
-bbbb-122 IN AAAA 2001:db8::1:007a
-bbbb-123 IN AAAA 2001:db8::1:007b
-bbbb-124 IN AAAA 2001:db8::1:007c
-bbbb-125 IN AAAA 2001:db8::1:007d
-bbbb-126 IN AAAA 2001:db8::1:007e
-bbbb-127 IN AAAA 2001:db8::1:007f
-bbbb-128 IN AAAA 2001:db8::1:0080
-bbbb-129 IN AAAA 2001:db8::1:0081
-bbbb-130 IN AAAA 2001:db8::1:0082
-bbbb-131 IN AAAA 2001:db8::1:0083
-bbbb-132 IN AAAA 2001:db8::1:0084
-bbbb-133 IN AAAA 2001:db8::1:0085
-bbbb-134 IN AAAA 2001:db8::1:0086
-bbbb-135 IN AAAA 2001:db8::1:0087
-bbbb-136 IN AAAA 2001:db8::1:0088
-bbbb-137 IN AAAA 2001:db8::1:0089
-bbbb-138 IN AAAA 2001:db8::1:008a
-bbbb-139 IN AAAA 2001:db8::1:008b
-bbbb-140 IN AAAA 2001:db8::1:008c
-bbbb-141 IN AAAA 2001:db8::1:008d
-bbbb-142 IN AAAA 2001:db8::1:008e
-bbbb-143 IN AAAA 2001:db8::1:008f
-bbbb-144 IN AAAA 2001:db8::1:0090
-bbbb-145 IN AAAA 2001:db8::1:0091
-bbbb-146 IN AAAA 2001:db8::1:0092
-bbbb-147 IN AAAA 2001:db8::1:0093
-bbbb-148 IN AAAA 2001:db8::1:0094
-bbbb-149 IN AAAA 2001:db8::1:0095
-bbbb-150 IN AAAA 2001:db8::1:0096
-bbbb-151 IN AAAA 2001:db8::1:0097
-bbbb-152 IN AAAA 2001:db8::1:0098
-bbbb-153 IN AAAA 2001:db8::1:0099
-bbbb-154 IN AAAA 2001:db8::1:009a
-bbbb-155 IN AAAA 2001:db8::1:009b
-bbbb-156 IN AAAA 2001:db8::1:009c
-bbbb-157 IN AAAA 2001:db8::1:009d
-bbbb-158 IN AAAA 2001:db8::1:009e
-bbbb-159 IN AAAA 2001:db8::1:009f
-bbbb-160 IN AAAA 2001:db8::1:00a0
-bbbb-161 IN AAAA 2001:db8::1:00a1
-bbbb-162 IN AAAA 2001:db8::1:00a2
-bbbb-163 IN AAAA 2001:db8::1:00a3
-bbbb-164 IN AAAA 2001:db8::1:00a4
-bbbb-165 IN AAAA 2001:db8::1:00a5
-bbbb-166 IN AAAA 2001:db8::1:00a6
-bbbb-167 IN AAAA 2001:db8::1:00a7
-bbbb-168 IN AAAA 2001:db8::1:00a8
-bbbb-169 IN AAAA 2001:db8::1:00a9
-bbbb-170 IN AAAA 2001:db8::1:00aa
-bbbb-171 IN AAAA 2001:db8::1:00ab
-bbbb-172 IN AAAA 2001:db8::1:00ac
-bbbb-173 IN AAAA 2001:db8::1:00ad
-bbbb-174 IN AAAA 2001:db8::1:00ae
-bbbb-175 IN AAAA 2001:db8::1:00af
-bbbb-176 IN AAAA 2001:db8::1:00b0
-bbbb-177 IN AAAA 2001:db8::1:00b1
-bbbb-178 IN AAAA 2001:db8::1:00b2
-bbbb-179 IN AAAA 2001:db8::1:00b3
-bbbb-180 IN AAAA 2001:db8::1:00b4
-bbbb-181 IN AAAA 2001:db8::1:00b5
-bbbb-182 IN AAAA 2001:db8::1:00b6
-bbbb-183 IN AAAA 2001:db8::1:00b7
-bbbb-184 IN AAAA 2001:db8::1:00b8
-bbbb-185 IN AAAA 2001:db8::1:00b9
-bbbb-186 IN AAAA 2001:db8::1:00ba
-bbbb-187 IN AAAA 2001:db8::1:00bb
-bbbb-188 IN AAAA 2001:db8::1:00bc
-bbbb-189 IN AAAA 2001:db8::1:00bd
-bbbb-190 IN AAAA 2001:db8::1:00be
-bbbb-191 IN AAAA 2001:db8::1:00bf
-bbbb-192 IN AAAA 2001:db8::1:00c0
-bbbb-193 IN AAAA 2001:db8::1:00c1
-bbbb-194 IN AAAA 2001:db8::1:00c2
-bbbb-195 IN AAAA 2001:db8::1:00c3
-bbbb-196 IN AAAA 2001:db8::1:00c4
-bbbb-197 IN AAAA 2001:db8::1:00c5
-bbbb-198 IN AAAA 2001:db8::1:00c6
-bbbb-199 IN AAAA 2001:db8::1:00c7
-bbbb-200 IN AAAA 2001:db8::1:00c8
-bbbb-201 IN AAAA 2001:db8::1:00c9
-bbbb-202 IN AAAA 2001:db8::1:00ca
-bbbb-203 IN AAAA 2001:db8::1:00cb
-bbbb-204 IN AAAA 2001:db8::1:00cc
-bbbb-205 IN AAAA 2001:db8::1:00cd
-bbbb-206 IN AAAA 2001:db8::1:00ce
-bbbb-207 IN AAAA 2001:db8::1:00cf
-bbbb-208 IN AAAA 2001:db8::1:00d0
-bbbb-209 IN AAAA 2001:db8::1:00d1
-bbbb-210 IN AAAA 2001:db8::1:00d2
-bbbb-211 IN AAAA 2001:db8::1:00d3
-bbbb-212 IN AAAA 2001:db8::1:00d4
-bbbb-213 IN AAAA 2001:db8::1:00d5
-bbbb-214 IN AAAA 2001:db8::1:00d6
-bbbb-215 IN AAAA 2001:db8::1:00d7
-bbbb-216 IN AAAA 2001:db8::1:00d8
-bbbb-217 IN AAAA 2001:db8::1:00d9
-bbbb-218 IN AAAA 2001:db8::1:00da
-bbbb-219 IN AAAA 2001:db8::1:00db
-bbbb-220 IN AAAA 2001:db8::1:00dc
-bbbb-221 IN AAAA 2001:db8::1:00dd
-bbbb-222 IN AAAA 2001:db8::1:00de
-bbbb-223 IN AAAA 2001:db8::1:00df
-bbbb-224 IN AAAA 2001:db8::1:00e0
-bbbb-225 IN AAAA 2001:db8::1:00e1
-bbbb-226 IN AAAA 2001:db8::1:00e2
-bbbb-227 IN AAAA 2001:db8::1:00e3
-bbbb-228 IN AAAA 2001:db8::1:00e4
-bbbb-229 IN AAAA 2001:db8::1:00e5
-bbbb-230 IN AAAA 2001:db8::1:00e6
-bbbb-231 IN AAAA 2001:db8::1:00e7
-bbbb-232 IN AAAA 2001:db8::1:00e8
-bbbb-233 IN AAAA 2001:db8::1:00e9
-bbbb-234 IN AAAA 2001:db8::1:00ea
-bbbb-235 IN AAAA 2001:db8::1:00eb
-bbbb-236 IN AAAA 2001:db8::1:00ec
-bbbb-237 IN AAAA 2001:db8::1:00ed
-bbbb-238 IN AAAA 2001:db8::1:00ee
-bbbb-239 IN AAAA 2001:db8::1:00ef
-bbbb-240 IN AAAA 2001:db8::1:00f0
-bbbb-241 IN AAAA 2001:db8::1:00f1
-bbbb-242 IN AAAA 2001:db8::1:00f2
-bbbb-243 IN AAAA 2001:db8::1:00f3
-bbbb-244 IN AAAA 2001:db8::1:00f4
-bbbb-245 IN AAAA 2001:db8::1:00f5
-bbbb-246 IN AAAA 2001:db8::1:00f6
-bbbb-247 IN AAAA 2001:db8::1:00f7
-bbbb-248 IN AAAA 2001:db8::1:00f8
-bbbb-249 IN AAAA 2001:db8::1:00f9
-bbbb-250 IN AAAA 2001:db8::1:00fa
-bbbb-251 IN AAAA 2001:db8::1:00fb
-bbbb-252 IN AAAA 2001:db8::1:00fc
-bbbb-253 IN AAAA 2001:db8::1:00fd
-bbbb-254 IN AAAA 2001:db8::1:00fe
-bbbb-255 IN AAAA 2001:db8::1:00ff
-bbbb-256 IN AAAA 2001:db8::1:0100
-bbbb-257 IN AAAA 2001:db8::1:0101
-bbbb-258 IN AAAA 2001:db8::1:0102
-bbbb-259 IN AAAA 2001:db8::1:0103
-bbbb-260 IN AAAA 2001:db8::1:0104
-bbbb-261 IN AAAA 2001:db8::1:0105
-bbbb-262 IN AAAA 2001:db8::1:0106
-bbbb-263 IN AAAA 2001:db8::1:0107
-bbbb-264 IN AAAA 2001:db8::1:0108
-bbbb-265 IN AAAA 2001:db8::1:0109
-bbbb-266 IN AAAA 2001:db8::1:010a
-bbbb-267 IN AAAA 2001:db8::1:010b
-bbbb-268 IN AAAA 2001:db8::1:010c
-bbbb-269 IN AAAA 2001:db8::1:010d
-bbbb-270 IN AAAA 2001:db8::1:010e
-bbbb-271 IN AAAA 2001:db8::1:010f
-bbbb-272 IN AAAA 2001:db8::1:0110
-bbbb-273 IN AAAA 2001:db8::1:0111
-bbbb-274 IN AAAA 2001:db8::1:0112
-bbbb-275 IN AAAA 2001:db8::1:0113
-bbbb-276 IN AAAA 2001:db8::1:0114
-bbbb-277 IN AAAA 2001:db8::1:0115
-bbbb-278 IN AAAA 2001:db8::1:0116
-bbbb-279 IN AAAA 2001:db8::1:0117
-bbbb-280 IN AAAA 2001:db8::1:0118
-bbbb-281 IN AAAA 2001:db8::1:0119
-bbbb-282 IN AAAA 2001:db8::1:011a
-bbbb-283 IN AAAA 2001:db8::1:011b
-bbbb-284 IN AAAA 2001:db8::1:011c
-bbbb-285 IN AAAA 2001:db8::1:011d
-bbbb-286 IN AAAA 2001:db8::1:011e
-bbbb-287 IN AAAA 2001:db8::1:011f
-bbbb-288 IN AAAA 2001:db8::1:0120
-bbbb-289 IN AAAA 2001:db8::1:0121
-bbbb-290 IN AAAA 2001:db8::1:0122
-bbbb-291 IN AAAA 2001:db8::1:0123
-bbbb-292 IN AAAA 2001:db8::1:0124
-bbbb-293 IN AAAA 2001:db8::1:0125
-bbbb-294 IN AAAA 2001:db8::1:0126
-bbbb-295 IN AAAA 2001:db8::1:0127
-bbbb-296 IN AAAA 2001:db8::1:0128
-bbbb-297 IN AAAA 2001:db8::1:0129
-bbbb-298 IN AAAA 2001:db8::1:012a
-bbbb-299 IN AAAA 2001:db8::1:012b
-bbbb-300 IN AAAA 2001:db8::1:012c
-bbbb-301 IN AAAA 2001:db8::1:012d
-bbbb-302 IN AAAA 2001:db8::1:012e
-bbbb-303 IN AAAA 2001:db8::1:012f
-bbbb-304 IN AAAA 2001:db8::1:0130
-bbbb-305 IN AAAA 2001:db8::1:0131
-bbbb-306 IN AAAA 2001:db8::1:0132
-bbbb-307 IN AAAA 2001:db8::1:0133
-bbbb-308 IN AAAA 2001:db8::1:0134
-bbbb-309 IN AAAA 2001:db8::1:0135
-bbbb-310 IN AAAA 2001:db8::1:0136
-bbbb-311 IN AAAA 2001:db8::1:0137
-bbbb-312 IN AAAA 2001:db8::1:0138
-bbbb-313 IN AAAA 2001:db8::1:0139
-bbbb-314 IN AAAA 2001:db8::1:013a
-bbbb-315 IN AAAA 2001:db8::1:013b
-bbbb-316 IN AAAA 2001:db8::1:013c
-bbbb-317 IN AAAA 2001:db8::1:013d
-bbbb-318 IN AAAA 2001:db8::1:013e
-bbbb-319 IN AAAA 2001:db8::1:013f
-bbbb-320 IN AAAA 2001:db8::1:0140
-bbbb-321 IN AAAA 2001:db8::1:0141
-bbbb-322 IN AAAA 2001:db8::1:0142
-bbbb-323 IN AAAA 2001:db8::1:0143
-bbbb-324 IN AAAA 2001:db8::1:0144
-bbbb-325 IN AAAA 2001:db8::1:0145
-bbbb-326 IN AAAA 2001:db8::1:0146
-bbbb-327 IN AAAA 2001:db8::1:0147
-bbbb-328 IN AAAA 2001:db8::1:0148
-bbbb-329 IN AAAA 2001:db8::1:0149
-bbbb-330 IN AAAA 2001:db8::1:014a
-bbbb-331 IN AAAA 2001:db8::1:014b
-bbbb-332 IN AAAA 2001:db8::1:014c
-bbbb-333 IN AAAA 2001:db8::1:014d
-bbbb-334 IN AAAA 2001:db8::1:014e
-bbbb-335 IN AAAA 2001:db8::1:014f
-bbbb-336 IN AAAA 2001:db8::1:0150
-bbbb-337 IN AAAA 2001:db8::1:0151
-bbbb-338 IN AAAA 2001:db8::1:0152
-bbbb-339 IN AAAA 2001:db8::1:0153
-bbbb-340 IN AAAA 2001:db8::1:0154
-bbbb-341 IN AAAA 2001:db8::1:0155
-bbbb-342 IN AAAA 2001:db8::1:0156
-bbbb-343 IN AAAA 2001:db8::1:0157
-bbbb-344 IN AAAA 2001:db8::1:0158
-bbbb-345 IN AAAA 2001:db8::1:0159
-bbbb-346 IN AAAA 2001:db8::1:015a
-bbbb-347 IN AAAA 2001:db8::1:015b
-bbbb-348 IN AAAA 2001:db8::1:015c
-bbbb-349 IN AAAA 2001:db8::1:015d
-bbbb-350 IN AAAA 2001:db8::1:015e
-bbbb-351 IN AAAA 2001:db8::1:015f
-bbbb-352 IN AAAA 2001:db8::1:0160
-bbbb-353 IN AAAA 2001:db8::1:0161
-bbbb-354 IN AAAA 2001:db8::1:0162
-bbbb-355 IN AAAA 2001:db8::1:0163
-bbbb-356 IN AAAA 2001:db8::1:0164
-bbbb-357 IN AAAA 2001:db8::1:0165
-bbbb-358 IN AAAA 2001:db8::1:0166
-bbbb-359 IN AAAA 2001:db8::1:0167
-bbbb-360 IN AAAA 2001:db8::1:0168
-bbbb-361 IN AAAA 2001:db8::1:0169
-bbbb-362 IN AAAA 2001:db8::1:016a
-bbbb-363 IN AAAA 2001:db8::1:016b
-bbbb-364 IN AAAA 2001:db8::1:016c
-bbbb-365 IN AAAA 2001:db8::1:016d
-bbbb-366 IN AAAA 2001:db8::1:016e
-bbbb-367 IN AAAA 2001:db8::1:016f
-bbbb-368 IN AAAA 2001:db8::1:0170
-bbbb-369 IN AAAA 2001:db8::1:0171
-bbbb-370 IN AAAA 2001:db8::1:0172
-bbbb-371 IN AAAA 2001:db8::1:0173
-bbbb-372 IN AAAA 2001:db8::1:0174
-bbbb-373 IN AAAA 2001:db8::1:0175
-bbbb-374 IN AAAA 2001:db8::1:0176
-bbbb-375 IN AAAA 2001:db8::1:0177
-bbbb-376 IN AAAA 2001:db8::1:0178
-bbbb-377 IN AAAA 2001:db8::1:0179
-bbbb-378 IN AAAA 2001:db8::1:017a
-bbbb-379 IN AAAA 2001:db8::1:017b
-bbbb-380 IN AAAA 2001:db8::1:017c
-bbbb-381 IN AAAA 2001:db8::1:017d
-bbbb-382 IN AAAA 2001:db8::1:017e
-bbbb-383 IN AAAA 2001:db8::1:017f
-bbbb-384 IN AAAA 2001:db8::1:0180
-bbbb-385 IN AAAA 2001:db8::1:0181
-bbbb-386 IN AAAA 2001:db8::1:0182
-bbbb-387 IN AAAA 2001:db8::1:0183
-bbbb-388 IN AAAA 2001:db8::1:0184
-bbbb-389 IN AAAA 2001:db8::1:0185
-bbbb-390 IN AAAA 2001:db8::1:0186
-bbbb-391 IN AAAA 2001:db8::1:0187
-bbbb-392 IN AAAA 2001:db8::1:0188
-bbbb-393 IN AAAA 2001:db8::1:0189
-bbbb-394 IN AAAA 2001:db8::1:018a
-bbbb-395 IN AAAA 2001:db8::1:018b
-bbbb-396 IN AAAA 2001:db8::1:018c
-bbbb-397 IN AAAA 2001:db8::1:018d
-bbbb-398 IN AAAA 2001:db8::1:018e
-bbbb-399 IN AAAA 2001:db8::1:018f
-bbbb-400 IN AAAA 2001:db8::1:0190
-bbbb-401 IN AAAA 2001:db8::1:0191
-bbbb-402 IN AAAA 2001:db8::1:0192
-bbbb-403 IN AAAA 2001:db8::1:0193
-bbbb-404 IN AAAA 2001:db8::1:0194
-bbbb-405 IN AAAA 2001:db8::1:0195
-bbbb-406 IN AAAA 2001:db8::1:0196
-bbbb-407 IN AAAA 2001:db8::1:0197
-bbbb-408 IN AAAA 2001:db8::1:0198
-bbbb-409 IN AAAA 2001:db8::1:0199
-bbbb-410 IN AAAA 2001:db8::1:019a
-bbbb-411 IN AAAA 2001:db8::1:019b
-bbbb-412 IN AAAA 2001:db8::1:019c
-bbbb-413 IN AAAA 2001:db8::1:019d
-bbbb-414 IN AAAA 2001:db8::1:019e
-bbbb-415 IN AAAA 2001:db8::1:019f
-bbbb-416 IN AAAA 2001:db8::1:01a0
-bbbb-417 IN AAAA 2001:db8::1:01a1
-bbbb-418 IN AAAA 2001:db8::1:01a2
-bbbb-419 IN AAAA 2001:db8::1:01a3
-bbbb-420 IN AAAA 2001:db8::1:01a4
-bbbb-421 IN AAAA 2001:db8::1:01a5
-bbbb-422 IN AAAA 2001:db8::1:01a6
-bbbb-423 IN AAAA 2001:db8::1:01a7
-bbbb-424 IN AAAA 2001:db8::1:01a8
-bbbb-425 IN AAAA 2001:db8::1:01a9
-bbbb-426 IN AAAA 2001:db8::1:01aa
-bbbb-427 IN AAAA 2001:db8::1:01ab
-bbbb-428 IN AAAA 2001:db8::1:01ac
-bbbb-429 IN AAAA 2001:db8::1:01ad
-bbbb-430 IN AAAA 2001:db8::1:01ae
-bbbb-431 IN AAAA 2001:db8::1:01af
-bbbb-432 IN AAAA 2001:db8::1:01b0
-bbbb-433 IN AAAA 2001:db8::1:01b1
-bbbb-434 IN AAAA 2001:db8::1:01b2
-bbbb-435 IN AAAA 2001:db8::1:01b3
-bbbb-436 IN AAAA 2001:db8::1:01b4
-bbbb-437 IN AAAA 2001:db8::1:01b5
-bbbb-438 IN AAAA 2001:db8::1:01b6
-bbbb-439 IN AAAA 2001:db8::1:01b7
-bbbb-440 IN AAAA 2001:db8::1:01b8
-bbbb-441 IN AAAA 2001:db8::1:01b9
-bbbb-442 IN AAAA 2001:db8::1:01ba
-bbbb-443 IN AAAA 2001:db8::1:01bb
-bbbb-444 IN AAAA 2001:db8::1:01bc
-bbbb-445 IN AAAA 2001:db8::1:01bd
-bbbb-446 IN AAAA 2001:db8::1:01be
-bbbb-447 IN AAAA 2001:db8::1:01bf
-bbbb-448 IN AAAA 2001:db8::1:01c0
-bbbb-449 IN AAAA 2001:db8::1:01c1
-bbbb-450 IN AAAA 2001:db8::1:01c2
-bbbb-451 IN AAAA 2001:db8::1:01c3
-bbbb-452 IN AAAA 2001:db8::1:01c4
-bbbb-453 IN AAAA 2001:db8::1:01c5
-bbbb-454 IN AAAA 2001:db8::1:01c6
-bbbb-455 IN AAAA 2001:db8::1:01c7
-bbbb-456 IN AAAA 2001:db8::1:01c8
-bbbb-457 IN AAAA 2001:db8::1:01c9
-bbbb-458 IN AAAA 2001:db8::1:01ca
-bbbb-459 IN AAAA 2001:db8::1:01cb
-bbbb-460 IN AAAA 2001:db8::1:01cc
-bbbb-461 IN AAAA 2001:db8::1:01cd
-bbbb-462 IN AAAA 2001:db8::1:01ce
-bbbb-463 IN AAAA 2001:db8::1:01cf
-bbbb-464 IN AAAA 2001:db8::1:01d0
-bbbb-465 IN AAAA 2001:db8::1:01d1
-bbbb-466 IN AAAA 2001:db8::1:01d2
-bbbb-467 IN AAAA 2001:db8::1:01d3
-bbbb-468 IN AAAA 2001:db8::1:01d4
-bbbb-469 IN AAAA 2001:db8::1:01d5
-bbbb-470 IN AAAA 2001:db8::1:01d6
-bbbb-471 IN AAAA 2001:db8::1:01d7
-bbbb-472 IN AAAA 2001:db8::1:01d8
-bbbb-473 IN AAAA 2001:db8::1:01d9
-bbbb-474 IN AAAA 2001:db8::1:01da
-bbbb-475 IN AAAA 2001:db8::1:01db
-bbbb-476 IN AAAA 2001:db8::1:01dc
-bbbb-477 IN AAAA 2001:db8::1:01dd
-bbbb-478 IN AAAA 2001:db8::1:01de
-bbbb-479 IN AAAA 2001:db8::1:01df
-bbbb-480 IN AAAA 2001:db8::1:01e0
-bbbb-481 IN AAAA 2001:db8::1:01e1
-bbbb-482 IN AAAA 2001:db8::1:01e2
-bbbb-483 IN AAAA 2001:db8::1:01e3
-bbbb-484 IN AAAA 2001:db8::1:01e4
-bbbb-485 IN AAAA 2001:db8::1:01e5
-bbbb-486 IN AAAA 2001:db8::1:01e6
-bbbb-487 IN AAAA 2001:db8::1:01e7
-bbbb-488 IN AAAA 2001:db8::1:01e8
-bbbb-489 IN AAAA 2001:db8::1:01e9
-bbbb-490 IN AAAA 2001:db8::1:01ea
-bbbb-491 IN AAAA 2001:db8::1:01eb
-bbbb-492 IN AAAA 2001:db8::1:01ec
-bbbb-493 IN AAAA 2001:db8::1:01ed
-bbbb-494 IN AAAA 2001:db8::1:01ee
-bbbb-495 IN AAAA 2001:db8::1:01ef
-bbbb-496 IN AAAA 2001:db8::1:01f0
-bbbb-497 IN AAAA 2001:db8::1:01f1
-bbbb-498 IN AAAA 2001:db8::1:01f2
-bbbb-499 IN AAAA 2001:db8::1:01f3
-bbbb-500 IN AAAA 2001:db8::1:01f4
-bbbb-501 IN AAAA 2001:db8::1:01f5
-bbbb-502 IN AAAA 2001:db8::1:01f6
-bbbb-503 IN AAAA 2001:db8::1:01f7
-bbbb-504 IN AAAA 2001:db8::1:01f8
-bbbb-505 IN AAAA 2001:db8::1:01f9
-bbbb-506 IN AAAA 2001:db8::1:01fa
-bbbb-507 IN AAAA 2001:db8::1:01fb
-bbbb-508 IN AAAA 2001:db8::1:01fc
-bbbb-509 IN AAAA 2001:db8::1:01fd
-bbbb-510 IN AAAA 2001:db8::1:01fe
-bbbb-511 IN AAAA 2001:db8::1:01ff
-
-cccc-000 IN AAAA 2001:db8::2:0000
-cccc-001 IN AAAA 2001:db8::2:0001
-cccc-002 IN AAAA 2001:db8::2:0002
-cccc-003 IN AAAA 2001:db8::2:0003
-cccc-004 IN AAAA 2001:db8::2:0004
-cccc-005 IN AAAA 2001:db8::2:0005
-cccc-006 IN AAAA 2001:db8::2:0006
-cccc-007 IN AAAA 2001:db8::2:0007
-cccc-008 IN AAAA 2001:db8::2:0008
-cccc-009 IN AAAA 2001:db8::2:0009
-cccc-010 IN AAAA 2001:db8::2:000a
-cccc-011 IN AAAA 2001:db8::2:000b
-cccc-012 IN AAAA 2001:db8::2:000c
-cccc-013 IN AAAA 2001:db8::2:000d
-cccc-014 IN AAAA 2001:db8::2:000e
-cccc-015 IN AAAA 2001:db8::2:000f
-cccc-016 IN AAAA 2001:db8::2:0010
-cccc-017 IN AAAA 2001:db8::2:0011
-cccc-018 IN AAAA 2001:db8::2:0012
-cccc-019 IN AAAA 2001:db8::2:0013
-cccc-020 IN AAAA 2001:db8::2:0014
-cccc-021 IN AAAA 2001:db8::2:0015
-cccc-022 IN AAAA 2001:db8::2:0016
-cccc-023 IN AAAA 2001:db8::2:0017
-cccc-024 IN AAAA 2001:db8::2:0018
-cccc-025 IN AAAA 2001:db8::2:0019
-cccc-026 IN AAAA 2001:db8::2:001a
-cccc-027 IN AAAA 2001:db8::2:001b
-cccc-028 IN AAAA 2001:db8::2:001c
-cccc-029 IN AAAA 2001:db8::2:001d
-cccc-030 IN AAAA 2001:db8::2:001e
-cccc-031 IN AAAA 2001:db8::2:001f
-cccc-032 IN AAAA 2001:db8::2:0020
-cccc-033 IN AAAA 2001:db8::2:0021
-cccc-034 IN AAAA 2001:db8::2:0022
-cccc-035 IN AAAA 2001:db8::2:0023
-cccc-036 IN AAAA 2001:db8::2:0024
-cccc-037 IN AAAA 2001:db8::2:0025
-cccc-038 IN AAAA 2001:db8::2:0026
-cccc-039 IN AAAA 2001:db8::2:0027
-cccc-040 IN AAAA 2001:db8::2:0028
-cccc-041 IN AAAA 2001:db8::2:0029
-cccc-042 IN AAAA 2001:db8::2:002a
-cccc-043 IN AAAA 2001:db8::2:002b
-cccc-044 IN AAAA 2001:db8::2:002c
-cccc-045 IN AAAA 2001:db8::2:002d
-cccc-046 IN AAAA 2001:db8::2:002e
-cccc-047 IN AAAA 2001:db8::2:002f
-cccc-048 IN AAAA 2001:db8::2:0030
-cccc-049 IN AAAA 2001:db8::2:0031
-cccc-050 IN AAAA 2001:db8::2:0032
-cccc-051 IN AAAA 2001:db8::2:0033
-cccc-052 IN AAAA 2001:db8::2:0034
-cccc-053 IN AAAA 2001:db8::2:0035
-cccc-054 IN AAAA 2001:db8::2:0036
-cccc-055 IN AAAA 2001:db8::2:0037
-cccc-056 IN AAAA 2001:db8::2:0038
-cccc-057 IN AAAA 2001:db8::2:0039
-cccc-058 IN AAAA 2001:db8::2:003a
-cccc-059 IN AAAA 2001:db8::2:003b
-cccc-060 IN AAAA 2001:db8::2:003c
-cccc-061 IN AAAA 2001:db8::2:003d
-cccc-062 IN AAAA 2001:db8::2:003e
-cccc-063 IN AAAA 2001:db8::2:003f
-cccc-064 IN AAAA 2001:db8::2:0040
-cccc-065 IN AAAA 2001:db8::2:0041
-cccc-066 IN AAAA 2001:db8::2:0042
-cccc-067 IN AAAA 2001:db8::2:0043
-cccc-068 IN AAAA 2001:db8::2:0044
-cccc-069 IN AAAA 2001:db8::2:0045
-cccc-070 IN AAAA 2001:db8::2:0046
-cccc-071 IN AAAA 2001:db8::2:0047
-cccc-072 IN AAAA 2001:db8::2:0048
-cccc-073 IN AAAA 2001:db8::2:0049
-cccc-074 IN AAAA 2001:db8::2:004a
-cccc-075 IN AAAA 2001:db8::2:004b
-cccc-076 IN AAAA 2001:db8::2:004c
-cccc-077 IN AAAA 2001:db8::2:004d
-cccc-078 IN AAAA 2001:db8::2:004e
-cccc-079 IN AAAA 2001:db8::2:004f
-cccc-080 IN AAAA 2001:db8::2:0050
-cccc-081 IN AAAA 2001:db8::2:0051
-cccc-082 IN AAAA 2001:db8::2:0052
-cccc-083 IN AAAA 2001:db8::2:0053
-cccc-084 IN AAAA 2001:db8::2:0054
-cccc-085 IN AAAA 2001:db8::2:0055
-cccc-086 IN AAAA 2001:db8::2:0056
-cccc-087 IN AAAA 2001:db8::2:0057
-cccc-088 IN AAAA 2001:db8::2:0058
-cccc-089 IN AAAA 2001:db8::2:0059
-cccc-090 IN AAAA 2001:db8::2:005a
-cccc-091 IN AAAA 2001:db8::2:005b
-cccc-092 IN AAAA 2001:db8::2:005c
-cccc-093 IN AAAA 2001:db8::2:005d
-cccc-094 IN AAAA 2001:db8::2:005e
-cccc-095 IN AAAA 2001:db8::2:005f
-cccc-096 IN AAAA 2001:db8::2:0060
-cccc-097 IN AAAA 2001:db8::2:0061
-cccc-098 IN AAAA 2001:db8::2:0062
-cccc-099 IN AAAA 2001:db8::2:0063
-cccc-100 IN AAAA 2001:db8::2:0064
-cccc-101 IN AAAA 2001:db8::2:0065
-cccc-102 IN AAAA 2001:db8::2:0066
-cccc-103 IN AAAA 2001:db8::2:0067
-cccc-104 IN AAAA 2001:db8::2:0068
-cccc-105 IN AAAA 2001:db8::2:0069
-cccc-106 IN AAAA 2001:db8::2:006a
-cccc-107 IN AAAA 2001:db8::2:006b
-cccc-108 IN AAAA 2001:db8::2:006c
-cccc-109 IN AAAA 2001:db8::2:006d
-cccc-110 IN AAAA 2001:db8::2:006e
-cccc-111 IN AAAA 2001:db8::2:006f
-cccc-112 IN AAAA 2001:db8::2:0070
-cccc-113 IN AAAA 2001:db8::2:0071
-cccc-114 IN AAAA 2001:db8::2:0072
-cccc-115 IN AAAA 2001:db8::2:0073
-cccc-116 IN AAAA 2001:db8::2:0074
-cccc-117 IN AAAA 2001:db8::2:0075
-cccc-118 IN AAAA 2001:db8::2:0076
-cccc-119 IN AAAA 2001:db8::2:0077
-cccc-120 IN AAAA 2001:db8::2:0078
-cccc-121 IN AAAA 2001:db8::2:0079
-cccc-122 IN AAAA 2001:db8::2:007a
-cccc-123 IN AAAA 2001:db8::2:007b
-cccc-124 IN AAAA 2001:db8::2:007c
-cccc-125 IN AAAA 2001:db8::2:007d
-cccc-126 IN AAAA 2001:db8::2:007e
-cccc-127 IN AAAA 2001:db8::2:007f
-cccc-128 IN AAAA 2001:db8::2:0080
-cccc-129 IN AAAA 2001:db8::2:0081
-cccc-130 IN AAAA 2001:db8::2:0082
-cccc-131 IN AAAA 2001:db8::2:0083
-cccc-132 IN AAAA 2001:db8::2:0084
-cccc-133 IN AAAA 2001:db8::2:0085
-cccc-134 IN AAAA 2001:db8::2:0086
-cccc-135 IN AAAA 2001:db8::2:0087
-cccc-136 IN AAAA 2001:db8::2:0088
-cccc-137 IN AAAA 2001:db8::2:0089
-cccc-138 IN AAAA 2001:db8::2:008a
-cccc-139 IN AAAA 2001:db8::2:008b
-cccc-140 IN AAAA 2001:db8::2:008c
-cccc-141 IN AAAA 2001:db8::2:008d
-cccc-142 IN AAAA 2001:db8::2:008e
-cccc-143 IN AAAA 2001:db8::2:008f
-cccc-144 IN AAAA 2001:db8::2:0090
-cccc-145 IN AAAA 2001:db8::2:0091
-cccc-146 IN AAAA 2001:db8::2:0092
-cccc-147 IN AAAA 2001:db8::2:0093
-cccc-148 IN AAAA 2001:db8::2:0094
-cccc-149 IN AAAA 2001:db8::2:0095
-cccc-150 IN AAAA 2001:db8::2:0096
-cccc-151 IN AAAA 2001:db8::2:0097
-cccc-152 IN AAAA 2001:db8::2:0098
-cccc-153 IN AAAA 2001:db8::2:0099
-cccc-154 IN AAAA 2001:db8::2:009a
-cccc-155 IN AAAA 2001:db8::2:009b
-cccc-156 IN AAAA 2001:db8::2:009c
-cccc-157 IN AAAA 2001:db8::2:009d
-cccc-158 IN AAAA 2001:db8::2:009e
-cccc-159 IN AAAA 2001:db8::2:009f
-cccc-160 IN AAAA 2001:db8::2:00a0
-cccc-161 IN AAAA 2001:db8::2:00a1
-cccc-162 IN AAAA 2001:db8::2:00a2
-cccc-163 IN AAAA 2001:db8::2:00a3
-cccc-164 IN AAAA 2001:db8::2:00a4
-cccc-165 IN AAAA 2001:db8::2:00a5
-cccc-166 IN AAAA 2001:db8::2:00a6
-cccc-167 IN AAAA 2001:db8::2:00a7
-cccc-168 IN AAAA 2001:db8::2:00a8
-cccc-169 IN AAAA 2001:db8::2:00a9
-cccc-170 IN AAAA 2001:db8::2:00aa
-cccc-171 IN AAAA 2001:db8::2:00ab
-cccc-172 IN AAAA 2001:db8::2:00ac
-cccc-173 IN AAAA 2001:db8::2:00ad
-cccc-174 IN AAAA 2001:db8::2:00ae
-cccc-175 IN AAAA 2001:db8::2:00af
-cccc-176 IN AAAA 2001:db8::2:00b0
-cccc-177 IN AAAA 2001:db8::2:00b1
-cccc-178 IN AAAA 2001:db8::2:00b2
-cccc-179 IN AAAA 2001:db8::2:00b3
-cccc-180 IN AAAA 2001:db8::2:00b4
-cccc-181 IN AAAA 2001:db8::2:00b5
-cccc-182 IN AAAA 2001:db8::2:00b6
-cccc-183 IN AAAA 2001:db8::2:00b7
-cccc-184 IN AAAA 2001:db8::2:00b8
-cccc-185 IN AAAA 2001:db8::2:00b9
-cccc-186 IN AAAA 2001:db8::2:00ba
-cccc-187 IN AAAA 2001:db8::2:00bb
-cccc-188 IN AAAA 2001:db8::2:00bc
-cccc-189 IN AAAA 2001:db8::2:00bd
-cccc-190 IN AAAA 2001:db8::2:00be
-cccc-191 IN AAAA 2001:db8::2:00bf
-cccc-192 IN AAAA 2001:db8::2:00c0
-cccc-193 IN AAAA 2001:db8::2:00c1
-cccc-194 IN AAAA 2001:db8::2:00c2
-cccc-195 IN AAAA 2001:db8::2:00c3
-cccc-196 IN AAAA 2001:db8::2:00c4
-cccc-197 IN AAAA 2001:db8::2:00c5
-cccc-198 IN AAAA 2001:db8::2:00c6
-cccc-199 IN AAAA 2001:db8::2:00c7
-cccc-200 IN AAAA 2001:db8::2:00c8
-cccc-201 IN AAAA 2001:db8::2:00c9
-cccc-202 IN AAAA 2001:db8::2:00ca
-cccc-203 IN AAAA 2001:db8::2:00cb
-cccc-204 IN AAAA 2001:db8::2:00cc
-cccc-205 IN AAAA 2001:db8::2:00cd
-cccc-206 IN AAAA 2001:db8::2:00ce
-cccc-207 IN AAAA 2001:db8::2:00cf
-cccc-208 IN AAAA 2001:db8::2:00d0
-cccc-209 IN AAAA 2001:db8::2:00d1
-cccc-210 IN AAAA 2001:db8::2:00d2
-cccc-211 IN AAAA 2001:db8::2:00d3
-cccc-212 IN AAAA 2001:db8::2:00d4
-cccc-213 IN AAAA 2001:db8::2:00d5
-cccc-214 IN AAAA 2001:db8::2:00d6
-cccc-215 IN AAAA 2001:db8::2:00d7
-cccc-216 IN AAAA 2001:db8::2:00d8
-cccc-217 IN AAAA 2001:db8::2:00d9
-cccc-218 IN AAAA 2001:db8::2:00da
-cccc-219 IN AAAA 2001:db8::2:00db
-cccc-220 IN AAAA 2001:db8::2:00dc
-cccc-221 IN AAAA 2001:db8::2:00dd
-cccc-222 IN AAAA 2001:db8::2:00de
-cccc-223 IN AAAA 2001:db8::2:00df
-cccc-224 IN AAAA 2001:db8::2:00e0
-cccc-225 IN AAAA 2001:db8::2:00e1
-cccc-226 IN AAAA 2001:db8::2:00e2
-cccc-227 IN AAAA 2001:db8::2:00e3
-cccc-228 IN AAAA 2001:db8::2:00e4
-cccc-229 IN AAAA 2001:db8::2:00e5
-cccc-230 IN AAAA 2001:db8::2:00e6
-cccc-231 IN AAAA 2001:db8::2:00e7
-cccc-232 IN AAAA 2001:db8::2:00e8
-cccc-233 IN AAAA 2001:db8::2:00e9
-cccc-234 IN AAAA 2001:db8::2:00ea
-cccc-235 IN AAAA 2001:db8::2:00eb
-cccc-236 IN AAAA 2001:db8::2:00ec
-cccc-237 IN AAAA 2001:db8::2:00ed
-cccc-238 IN AAAA 2001:db8::2:00ee
-cccc-239 IN AAAA 2001:db8::2:00ef
-cccc-240 IN AAAA 2001:db8::2:00f0
-cccc-241 IN AAAA 2001:db8::2:00f1
-cccc-242 IN AAAA 2001:db8::2:00f2
-cccc-243 IN AAAA 2001:db8::2:00f3
-cccc-244 IN AAAA 2001:db8::2:00f4
-cccc-245 IN AAAA 2001:db8::2:00f5
-cccc-246 IN AAAA 2001:db8::2:00f6
-cccc-247 IN AAAA 2001:db8::2:00f7
-cccc-248 IN AAAA 2001:db8::2:00f8
-cccc-249 IN AAAA 2001:db8::2:00f9
-cccc-250 IN AAAA 2001:db8::2:00fa
-cccc-251 IN AAAA 2001:db8::2:00fb
-cccc-252 IN AAAA 2001:db8::2:00fc
-cccc-253 IN AAAA 2001:db8::2:00fd
-cccc-254 IN AAAA 2001:db8::2:00fe
-cccc-255 IN AAAA 2001:db8::2:00ff
-cccc-256 IN AAAA 2001:db8::2:0100
-cccc-257 IN AAAA 2001:db8::2:0101
-cccc-258 IN AAAA 2001:db8::2:0102
-cccc-259 IN AAAA 2001:db8::2:0103
-cccc-260 IN AAAA 2001:db8::2:0104
-cccc-261 IN AAAA 2001:db8::2:0105
-cccc-262 IN AAAA 2001:db8::2:0106
-cccc-263 IN AAAA 2001:db8::2:0107
-cccc-264 IN AAAA 2001:db8::2:0108
-cccc-265 IN AAAA 2001:db8::2:0109
-cccc-266 IN AAAA 2001:db8::2:010a
-cccc-267 IN AAAA 2001:db8::2:010b
-cccc-268 IN AAAA 2001:db8::2:010c
-cccc-269 IN AAAA 2001:db8::2:010d
-cccc-270 IN AAAA 2001:db8::2:010e
-cccc-271 IN AAAA 2001:db8::2:010f
-cccc-272 IN AAAA 2001:db8::2:0110
-cccc-273 IN AAAA 2001:db8::2:0111
-cccc-274 IN AAAA 2001:db8::2:0112
-cccc-275 IN AAAA 2001:db8::2:0113
-cccc-276 IN AAAA 2001:db8::2:0114
-cccc-277 IN AAAA 2001:db8::2:0115
-cccc-278 IN AAAA 2001:db8::2:0116
-cccc-279 IN AAAA 2001:db8::2:0117
-cccc-280 IN AAAA 2001:db8::2:0118
-cccc-281 IN AAAA 2001:db8::2:0119
-cccc-282 IN AAAA 2001:db8::2:011a
-cccc-283 IN AAAA 2001:db8::2:011b
-cccc-284 IN AAAA 2001:db8::2:011c
-cccc-285 IN AAAA 2001:db8::2:011d
-cccc-286 IN AAAA 2001:db8::2:011e
-cccc-287 IN AAAA 2001:db8::2:011f
-cccc-288 IN AAAA 2001:db8::2:0120
-cccc-289 IN AAAA 2001:db8::2:0121
-cccc-290 IN AAAA 2001:db8::2:0122
-cccc-291 IN AAAA 2001:db8::2:0123
-cccc-292 IN AAAA 2001:db8::2:0124
-cccc-293 IN AAAA 2001:db8::2:0125
-cccc-294 IN AAAA 2001:db8::2:0126
-cccc-295 IN AAAA 2001:db8::2:0127
-cccc-296 IN AAAA 2001:db8::2:0128
-cccc-297 IN AAAA 2001:db8::2:0129
-cccc-298 IN AAAA 2001:db8::2:012a
-cccc-299 IN AAAA 2001:db8::2:012b
-cccc-300 IN AAAA 2001:db8::2:012c
-cccc-301 IN AAAA 2001:db8::2:012d
-cccc-302 IN AAAA 2001:db8::2:012e
-cccc-303 IN AAAA 2001:db8::2:012f
-cccc-304 IN AAAA 2001:db8::2:0130
-cccc-305 IN AAAA 2001:db8::2:0131
-cccc-306 IN AAAA 2001:db8::2:0132
-cccc-307 IN AAAA 2001:db8::2:0133
-cccc-308 IN AAAA 2001:db8::2:0134
-cccc-309 IN AAAA 2001:db8::2:0135
-cccc-310 IN AAAA 2001:db8::2:0136
-cccc-311 IN AAAA 2001:db8::2:0137
-cccc-312 IN AAAA 2001:db8::2:0138
-cccc-313 IN AAAA 2001:db8::2:0139
-cccc-314 IN AAAA 2001:db8::2:013a
-cccc-315 IN AAAA 2001:db8::2:013b
-cccc-316 IN AAAA 2001:db8::2:013c
-cccc-317 IN AAAA 2001:db8::2:013d
-cccc-318 IN AAAA 2001:db8::2:013e
-cccc-319 IN AAAA 2001:db8::2:013f
-cccc-320 IN AAAA 2001:db8::2:0140
-cccc-321 IN AAAA 2001:db8::2:0141
-cccc-322 IN AAAA 2001:db8::2:0142
-cccc-323 IN AAAA 2001:db8::2:0143
-cccc-324 IN AAAA 2001:db8::2:0144
-cccc-325 IN AAAA 2001:db8::2:0145
-cccc-326 IN AAAA 2001:db8::2:0146
-cccc-327 IN AAAA 2001:db8::2:0147
-cccc-328 IN AAAA 2001:db8::2:0148
-cccc-329 IN AAAA 2001:db8::2:0149
-cccc-330 IN AAAA 2001:db8::2:014a
-cccc-331 IN AAAA 2001:db8::2:014b
-cccc-332 IN AAAA 2001:db8::2:014c
-cccc-333 IN AAAA 2001:db8::2:014d
-cccc-334 IN AAAA 2001:db8::2:014e
-cccc-335 IN AAAA 2001:db8::2:014f
-cccc-336 IN AAAA 2001:db8::2:0150
-cccc-337 IN AAAA 2001:db8::2:0151
-cccc-338 IN AAAA 2001:db8::2:0152
-cccc-339 IN AAAA 2001:db8::2:0153
-cccc-340 IN AAAA 2001:db8::2:0154
-cccc-341 IN AAAA 2001:db8::2:0155
-cccc-342 IN AAAA 2001:db8::2:0156
-cccc-343 IN AAAA 2001:db8::2:0157
-cccc-344 IN AAAA 2001:db8::2:0158
-cccc-345 IN AAAA 2001:db8::2:0159
-cccc-346 IN AAAA 2001:db8::2:015a
-cccc-347 IN AAAA 2001:db8::2:015b
-cccc-348 IN AAAA 2001:db8::2:015c
-cccc-349 IN AAAA 2001:db8::2:015d
-cccc-350 IN AAAA 2001:db8::2:015e
-cccc-351 IN AAAA 2001:db8::2:015f
-cccc-352 IN AAAA 2001:db8::2:0160
-cccc-353 IN AAAA 2001:db8::2:0161
-cccc-354 IN AAAA 2001:db8::2:0162
-cccc-355 IN AAAA 2001:db8::2:0163
-cccc-356 IN AAAA 2001:db8::2:0164
-cccc-357 IN AAAA 2001:db8::2:0165
-cccc-358 IN AAAA 2001:db8::2:0166
-cccc-359 IN AAAA 2001:db8::2:0167
-cccc-360 IN AAAA 2001:db8::2:0168
-cccc-361 IN AAAA 2001:db8::2:0169
-cccc-362 IN AAAA 2001:db8::2:016a
-cccc-363 IN AAAA 2001:db8::2:016b
-cccc-364 IN AAAA 2001:db8::2:016c
-cccc-365 IN AAAA 2001:db8::2:016d
-cccc-366 IN AAAA 2001:db8::2:016e
-cccc-367 IN AAAA 2001:db8::2:016f
-cccc-368 IN AAAA 2001:db8::2:0170
-cccc-369 IN AAAA 2001:db8::2:0171
-cccc-370 IN AAAA 2001:db8::2:0172
-cccc-371 IN AAAA 2001:db8::2:0173
-cccc-372 IN AAAA 2001:db8::2:0174
-cccc-373 IN AAAA 2001:db8::2:0175
-cccc-374 IN AAAA 2001:db8::2:0176
-cccc-375 IN AAAA 2001:db8::2:0177
-cccc-376 IN AAAA 2001:db8::2:0178
-cccc-377 IN AAAA 2001:db8::2:0179
-cccc-378 IN AAAA 2001:db8::2:017a
-cccc-379 IN AAAA 2001:db8::2:017b
-cccc-380 IN AAAA 2001:db8::2:017c
-cccc-381 IN AAAA 2001:db8::2:017d
-cccc-382 IN AAAA 2001:db8::2:017e
-cccc-383 IN AAAA 2001:db8::2:017f
-cccc-384 IN AAAA 2001:db8::2:0180
-cccc-385 IN AAAA 2001:db8::2:0181
-cccc-386 IN AAAA 2001:db8::2:0182
-cccc-387 IN AAAA 2001:db8::2:0183
-cccc-388 IN AAAA 2001:db8::2:0184
-cccc-389 IN AAAA 2001:db8::2:0185
-cccc-390 IN AAAA 2001:db8::2:0186
-cccc-391 IN AAAA 2001:db8::2:0187
-cccc-392 IN AAAA 2001:db8::2:0188
-cccc-393 IN AAAA 2001:db8::2:0189
-cccc-394 IN AAAA 2001:db8::2:018a
-cccc-395 IN AAAA 2001:db8::2:018b
-cccc-396 IN AAAA 2001:db8::2:018c
-cccc-397 IN AAAA 2001:db8::2:018d
-cccc-398 IN AAAA 2001:db8::2:018e
-cccc-399 IN AAAA 2001:db8::2:018f
-cccc-400 IN AAAA 2001:db8::2:0190
-cccc-401 IN AAAA 2001:db8::2:0191
-cccc-402 IN AAAA 2001:db8::2:0192
-cccc-403 IN AAAA 2001:db8::2:0193
-cccc-404 IN AAAA 2001:db8::2:0194
-cccc-405 IN AAAA 2001:db8::2:0195
-cccc-406 IN AAAA 2001:db8::2:0196
-cccc-407 IN AAAA 2001:db8::2:0197
-cccc-408 IN AAAA 2001:db8::2:0198
-cccc-409 IN AAAA 2001:db8::2:0199
-cccc-410 IN AAAA 2001:db8::2:019a
-cccc-411 IN AAAA 2001:db8::2:019b
-cccc-412 IN AAAA 2001:db8::2:019c
-cccc-413 IN AAAA 2001:db8::2:019d
-cccc-414 IN AAAA 2001:db8::2:019e
-cccc-415 IN AAAA 2001:db8::2:019f
-cccc-416 IN AAAA 2001:db8::2:01a0
-cccc-417 IN AAAA 2001:db8::2:01a1
-cccc-418 IN AAAA 2001:db8::2:01a2
-cccc-419 IN AAAA 2001:db8::2:01a3
-cccc-420 IN AAAA 2001:db8::2:01a4
-cccc-421 IN AAAA 2001:db8::2:01a5
-cccc-422 IN AAAA 2001:db8::2:01a6
-cccc-423 IN AAAA 2001:db8::2:01a7
-cccc-424 IN AAAA 2001:db8::2:01a8
-cccc-425 IN AAAA 2001:db8::2:01a9
-cccc-426 IN AAAA 2001:db8::2:01aa
-cccc-427 IN AAAA 2001:db8::2:01ab
-cccc-428 IN AAAA 2001:db8::2:01ac
-cccc-429 IN AAAA 2001:db8::2:01ad
-cccc-430 IN AAAA 2001:db8::2:01ae
-cccc-431 IN AAAA 2001:db8::2:01af
-cccc-432 IN AAAA 2001:db8::2:01b0
-cccc-433 IN AAAA 2001:db8::2:01b1
-cccc-434 IN AAAA 2001:db8::2:01b2
-cccc-435 IN AAAA 2001:db8::2:01b3
-cccc-436 IN AAAA 2001:db8::2:01b4
-cccc-437 IN AAAA 2001:db8::2:01b5
-cccc-438 IN AAAA 2001:db8::2:01b6
-cccc-439 IN AAAA 2001:db8::2:01b7
-cccc-440 IN AAAA 2001:db8::2:01b8
-cccc-441 IN AAAA 2001:db8::2:01b9
-cccc-442 IN AAAA 2001:db8::2:01ba
-cccc-443 IN AAAA 2001:db8::2:01bb
-cccc-444 IN AAAA 2001:db8::2:01bc
-cccc-445 IN AAAA 2001:db8::2:01bd
-cccc-446 IN AAAA 2001:db8::2:01be
-cccc-447 IN AAAA 2001:db8::2:01bf
-cccc-448 IN AAAA 2001:db8::2:01c0
-cccc-449 IN AAAA 2001:db8::2:01c1
-cccc-450 IN AAAA 2001:db8::2:01c2
-cccc-451 IN AAAA 2001:db8::2:01c3
-cccc-452 IN AAAA 2001:db8::2:01c4
-cccc-453 IN AAAA 2001:db8::2:01c5
-cccc-454 IN AAAA 2001:db8::2:01c6
-cccc-455 IN AAAA 2001:db8::2:01c7
-cccc-456 IN AAAA 2001:db8::2:01c8
-cccc-457 IN AAAA 2001:db8::2:01c9
-cccc-458 IN AAAA 2001:db8::2:01ca
-cccc-459 IN AAAA 2001:db8::2:01cb
-cccc-460 IN AAAA 2001:db8::2:01cc
-cccc-461 IN AAAA 2001:db8::2:01cd
-cccc-462 IN AAAA 2001:db8::2:01ce
-cccc-463 IN AAAA 2001:db8::2:01cf
-cccc-464 IN AAAA 2001:db8::2:01d0
-cccc-465 IN AAAA 2001:db8::2:01d1
-cccc-466 IN AAAA 2001:db8::2:01d2
-cccc-467 IN AAAA 2001:db8::2:01d3
-cccc-468 IN AAAA 2001:db8::2:01d4
-cccc-469 IN AAAA 2001:db8::2:01d5
-cccc-470 IN AAAA 2001:db8::2:01d6
-cccc-471 IN AAAA 2001:db8::2:01d7
-cccc-472 IN AAAA 2001:db8::2:01d8
-cccc-473 IN AAAA 2001:db8::2:01d9
-cccc-474 IN AAAA 2001:db8::2:01da
-cccc-475 IN AAAA 2001:db8::2:01db
-cccc-476 IN AAAA 2001:db8::2:01dc
-cccc-477 IN AAAA 2001:db8::2:01dd
-cccc-478 IN AAAA 2001:db8::2:01de
-cccc-479 IN AAAA 2001:db8::2:01df
-cccc-480 IN AAAA 2001:db8::2:01e0
-cccc-481 IN AAAA 2001:db8::2:01e1
-cccc-482 IN AAAA 2001:db8::2:01e2
-cccc-483 IN AAAA 2001:db8::2:01e3
-cccc-484 IN AAAA 2001:db8::2:01e4
-cccc-485 IN AAAA 2001:db8::2:01e5
-cccc-486 IN AAAA 2001:db8::2:01e6
-cccc-487 IN AAAA 2001:db8::2:01e7
-cccc-488 IN AAAA 2001:db8::2:01e8
-cccc-489 IN AAAA 2001:db8::2:01e9
-cccc-490 IN AAAA 2001:db8::2:01ea
-cccc-491 IN AAAA 2001:db8::2:01eb
-cccc-492 IN AAAA 2001:db8::2:01ec
-cccc-493 IN AAAA 2001:db8::2:01ed
-cccc-494 IN AAAA 2001:db8::2:01ee
-cccc-495 IN AAAA 2001:db8::2:01ef
-cccc-496 IN AAAA 2001:db8::2:01f0
-cccc-497 IN AAAA 2001:db8::2:01f1
-cccc-498 IN AAAA 2001:db8::2:01f2
-cccc-499 IN AAAA 2001:db8::2:01f3
-cccc-500 IN AAAA 2001:db8::2:01f4
-cccc-501 IN AAAA 2001:db8::2:01f5
-cccc-502 IN AAAA 2001:db8::2:01f6
-cccc-503 IN AAAA 2001:db8::2:01f7
-cccc-504 IN AAAA 2001:db8::2:01f8
-cccc-505 IN AAAA 2001:db8::2:01f9
-cccc-506 IN AAAA 2001:db8::2:01fa
-cccc-507 IN AAAA 2001:db8::2:01fb
-cccc-508 IN AAAA 2001:db8::2:01fc
-cccc-509 IN AAAA 2001:db8::2:01fd
-cccc-510 IN AAAA 2001:db8::2:01fe
-cccc-511 IN AAAA 2001:db8::2:01ff
diff --git a/tests/system/ixfr/db.example.n0.in b/tests/system/ixfr/db.example.n0.in
deleted file mode 100644
index 92fa0b0..0000000
--- a/tests/system/ixfr/db.example.n0.in
+++ /dev/null
@@ -1,29 +0,0 @@
-; Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
-;
-; Permission to use, copy, modify, and/or distribute this software for any
-; purpose with or without fee is hereby granted, provided that the above
-; copyright notice and this permission notice appear in all copies.
-;
-; THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-; REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-; AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-; INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-; LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-; PERFORMANCE OF THIS SOFTWARE.
-
-$ORIGIN example.
-$TTL 3600
-
-@ IN SOA ns1.example. hostmaster.example. 100 3600 900 7200 300
-
- IN NS ns1.example.
- IN NS ns2.example.
-
-ns1 IN A 192.0.2.1
-ns2 IN A 192.0.2.2
-
-a-1 IN A 192.0.2.101
-b-1 IN A 192.0.2.201
-
-$INCLUDE @abs_top_builddir@/tests/system/ixfr/db.example.common
diff --git a/tests/system/ixfr/db.example.n2.in b/tests/system/ixfr/db.example.n2.in
deleted file mode 100644
index 6a999af..0000000
--- a/tests/system/ixfr/db.example.n2.in
+++ /dev/null
@@ -1,28 +0,0 @@
-; Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
-;
-; Permission to use, copy, modify, and/or distribute this software for any
-; purpose with or without fee is hereby granted, provided that the above
-; copyright notice and this permission notice appear in all copies.
-;
-; THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-; REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-; AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-; INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-; LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-; PERFORMANCE OF THIS SOFTWARE.
-
-$ORIGIN example.
-$TTL 3600
-
-@ IN SOA ns1.example. hostmaster.example. 98 3600 900 7200 300
-
- IN NS ns1.example.
- IN NS ns2.example.
-
-ns1 IN A 192.0.2.1
-ns2 IN A 192.0.2.2
-
-a-1 IN A 192.0.2.101
-
-$INCLUDE @abs_top_builddir@/tests/system/ixfr/db.example.common
diff --git a/tests/system/ixfr/db.example.n2.refresh.in b/tests/system/ixfr/db.example.n2.refresh.in
deleted file mode 100644
index 2c59416..0000000
--- a/tests/system/ixfr/db.example.n2.refresh.in
+++ /dev/null
@@ -1,28 +0,0 @@
-; Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
-;
-; Permission to use, copy, modify, and/or distribute this software for any
-; purpose with or without fee is hereby granted, provided that the above
-; copyright notice and this permission notice appear in all copies.
-;
-; THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-; REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-; AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-; INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-; LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-; PERFORMANCE OF THIS SOFTWARE.
-
-$ORIGIN example.
-$TTL 3600
-
-@ IN SOA ns1.example. hostmaster.example. 98 30 2 7200 300
-
- IN NS ns1.example.
- IN NS ns2.example.
-
-ns1 IN A 192.0.2.1
-ns2 IN A 192.0.2.2
-
-a-1 IN A 192.0.2.101
-
-$INCLUDE @abs_top_builddir@/tests/system/ixfr/db.example.common
diff --git a/tests/system/ixfr/db.example.n4.in b/tests/system/ixfr/db.example.n4.in
deleted file mode 100644
index ae15a54..0000000
--- a/tests/system/ixfr/db.example.n4.in
+++ /dev/null
@@ -1,31 +0,0 @@
-; Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
-;
-; Permission to use, copy, modify, and/or distribute this software for any
-; purpose with or without fee is hereby granted, provided that the above
-; copyright notice and this permission notice appear in all copies.
-;
-; THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-; REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-; AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-; INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-; LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-; PERFORMANCE OF THIS SOFTWARE.
-
-$ORIGIN example.
-$TTL 3600
-
-@ IN SOA ns1.example. hostmaster.example. 96 3600 900 7200 300
-
- IN NS ns1.example.
- IN NS ns2.example.
-
-ns1 IN A 192.0.2.1
-ns2 IN A 192.0.2.2
-
-a-1 IN A 192.0.2.101
-a-2 IN A 192.0.2.102
-b-1 IN A 192.0.2.201
-b-2 IN A 192.0.2.202
-
-$INCLUDE @abs_top_builddir@/tests/system/ixfr/db.example.common
diff --git a/tests/system/ixfr/db.example.n6.in b/tests/system/ixfr/db.example.n6.in
deleted file mode 100644
index 33a82a0..0000000
--- a/tests/system/ixfr/db.example.n6.in
+++ /dev/null
@@ -1,29 +0,0 @@
-; Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
-;
-; Permission to use, copy, modify, and/or distribute this software for any
-; purpose with or without fee is hereby granted, provided that the above
-; copyright notice and this permission notice appear in all copies.
-;
-; THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-; REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-; AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-; INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-; LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-; PERFORMANCE OF THIS SOFTWARE.
-
-$ORIGIN example.
-$TTL 3600
-
-@ IN SOA ns1.example. hostmaster.example. 94 3600 900 7200 300
-
- IN NS ns1.example.
- IN NS ns2.example.
-
-ns1 IN A 192.0.2.1
-ns2 IN A 192.0.2.2
-
-a-1 IN A 192.0.2.101
-a-2 IN A 192.0.2.102
-b-1 IN A 192.0.2.201
-b-2 IN A 192.0.2.202
diff --git a/tests/system/ixfr/in-1/.gitignore b/tests/system/ixfr/in-1/.gitignore
deleted file mode 100644
index 87e08bf..0000000
--- a/tests/system/ixfr/in-1/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/setup.sh
diff --git a/tests/system/ixfr/in-1/clean.sh b/tests/system/ixfr/in-1/clean.sh
deleted file mode 120000
index 099bebd..0000000
--- a/tests/system/ixfr/in-1/clean.sh
+++ /dev/null
@@ -1 +0,0 @@
-../clean_ns.sh
\ No newline at end of file
diff --git a/tests/system/ixfr/in-1/ns1/README b/tests/system/ixfr/in-1/ns1/README
deleted file mode 100644
index aaa8a31..0000000
--- a/tests/system/ixfr/in-1/ns1/README
+++ /dev/null
@@ -1,3 +0,0 @@
-This directory should be empty. A README file is placed here to ensure git
-notes the directory's presence. It can be removed if other files are placed
-here.
diff --git a/tests/system/ixfr/in-1/nsx2/README b/tests/system/ixfr/in-1/nsx2/README
deleted file mode 100644
index aaa8a31..0000000
--- a/tests/system/ixfr/in-1/nsx2/README
+++ /dev/null
@@ -1,3 +0,0 @@
-This directory should be empty. A README file is placed here to ensure git
-notes the directory's presence. It can be removed if other files are placed
-here.
diff --git a/tests/system/ixfr/in-1/setup.sh.in b/tests/system/ixfr/in-1/setup.sh.in
deleted file mode 100644
index 4332930..0000000
--- a/tests/system/ixfr/in-1/setup.sh.in
+++ /dev/null
@@ -1,30 +0,0 @@
-#!/bin/sh
-#
-# Copyright (C) 2004, 2007, 2011 Internet Systems Consortium, Inc. ("ISC")
-# Copyright (C) 2001, 2002 Internet Software Consortium.
-#
-# 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.
-
-. @abs_top_builddir@/tests/system/conf.sh
-
-# Clean up from last time
-sh clean.sh
-
-# Set up the initial version of the IXFR server - load the n-4 version of the
-# zone. The configuration file enables IXFR and disabled notifies.
-cp -f $IXFR_TOP/named_nonotify.conf ns1/named.conf
-cp -f $IXFR_TOP/db.example.n4 ns1/db.example
-
-# Set up the IXFR client - load the same version of the zone.
-cp -f $IXFR_TOP/b10-config.db nsx2/b10-config.db
-${B10_LOADZONE} -c "{\"database_file\": \"$IXFR_TOP/zone.sqlite3\"}" example. $IXFR_TOP/db.example.n4
diff --git a/tests/system/ixfr/in-1/tests.sh b/tests/system/ixfr/in-1/tests.sh
deleted file mode 100644
index 2f49ddf..0000000
--- a/tests/system/ixfr/in-1/tests.sh
+++ /dev/null
@@ -1,37 +0,0 @@
-#!/bin/sh
-#
-# Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-# PERFORMANCE OF THIS SOFTWARE.
-
-# \file
-# This script performs the first IXFR-IN test. A BIND 9 nameserver (the
-# "server") contains a version of the zone (version N) and two previous
-# versions, N-2 and N-4. A BIND 10 nameserver (the "client") is loaded with
-# version N-4 of the zone. A NOTIFY is sent to it, and it is expected that
-# it will send an IXFR to the server and update itself with the latest version
-# of the zone. (The changes are such that the update should be in the form of
-# a single UDP packet.)
-#
-# The pre-requisites for this test are the same as for the common tests, so
-# we can execute that directly.
-
-. ../common_tests.sh
-status=$?
-
-# TODO: Check the BIND 10 log, looking for the IXFR messages that indicate that
-# it has initiated an IXFR and that it received the update within a single
-# packet.
-
-echo "I:exit status: $status"
-exit $status
diff --git a/tests/system/ixfr/in-2/.gitignore b/tests/system/ixfr/in-2/.gitignore
deleted file mode 100644
index 87e08bf..0000000
--- a/tests/system/ixfr/in-2/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/setup.sh
diff --git a/tests/system/ixfr/in-2/clean.sh b/tests/system/ixfr/in-2/clean.sh
deleted file mode 120000
index 099bebd..0000000
--- a/tests/system/ixfr/in-2/clean.sh
+++ /dev/null
@@ -1 +0,0 @@
-../clean_ns.sh
\ No newline at end of file
diff --git a/tests/system/ixfr/in-2/ns1/.gitignore b/tests/system/ixfr/in-2/ns1/.gitignore
deleted file mode 100644
index 35ae1cb..0000000
--- a/tests/system/ixfr/in-2/ns1/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/named.run
diff --git a/tests/system/ixfr/in-2/ns1/README b/tests/system/ixfr/in-2/ns1/README
deleted file mode 100644
index aaa8a31..0000000
--- a/tests/system/ixfr/in-2/ns1/README
+++ /dev/null
@@ -1,3 +0,0 @@
-This directory should be empty. A README file is placed here to ensure git
-notes the directory's presence. It can be removed if other files are placed
-here.
diff --git a/tests/system/ixfr/in-2/nsx2/.gitignore b/tests/system/ixfr/in-2/nsx2/.gitignore
deleted file mode 100644
index d31eb18..0000000
--- a/tests/system/ixfr/in-2/nsx2/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/bindctl.out
diff --git a/tests/system/ixfr/in-2/nsx2/README b/tests/system/ixfr/in-2/nsx2/README
deleted file mode 100644
index aaa8a31..0000000
--- a/tests/system/ixfr/in-2/nsx2/README
+++ /dev/null
@@ -1,3 +0,0 @@
-This directory should be empty. A README file is placed here to ensure git
-notes the directory's presence. It can be removed if other files are placed
-here.
diff --git a/tests/system/ixfr/in-2/setup.sh.in b/tests/system/ixfr/in-2/setup.sh.in
deleted file mode 100644
index a210636..0000000
--- a/tests/system/ixfr/in-2/setup.sh.in
+++ /dev/null
@@ -1,29 +0,0 @@
-#!/bin/sh
-#
-# Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-# PERFORMANCE OF THIS SOFTWARE.
-
-. @abs_top_builddir@/tests/system/conf.sh
-
-# Clean up from last time
-sh clean.sh
-
-# Set up the initial version of the IXFR server - load the n-6 version of the
-# zone. The configuration file enables IXFR and disables notifies.
-cp -f $IXFR_TOP/named_nonotify.conf ns1/named.conf
-cp -f $IXFR_TOP/db.example.n6 ns1/db.example
-
-# Set up the IXFR client - load an earlier version of the zone
-cp -f $IXFR_TOP/b10-config.db nsx2/b10-config.db
-${B10_LOADZONE} -c "{\"database_file\": \"$IXFR_TOP/zone.sqlite3\"}" example. $IXFR_TOP/db.example.n6
diff --git a/tests/system/ixfr/in-2/tests.sh b/tests/system/ixfr/in-2/tests.sh
deleted file mode 100644
index 3050713..0000000
--- a/tests/system/ixfr/in-2/tests.sh
+++ /dev/null
@@ -1,81 +0,0 @@
-#!/bin/sh
-#
-# Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-# PERFORMANCE OF THIS SOFTWARE.
-
-# \file
-# This script performs the first IXFR-IN test. A BIND 9 nameserver (the
-# "server") contains a version of the zone (version N) and three previous
-# versions, N-2, N-4 and N-6. A BIND 10 nameserver (the "client") is loaded
-# with version N-6 of the zone. A NOTIFY is sent to it, and it is expected that
-# it will send an IXFR to the server and update itself with the latest version
-# of the zone. (The changes are such that the update will have to take place
-# over TCP.)
-
-. ../ixfr_init.sh
-
-# On entry, the IXFR server is at version N-6. The common tests assume that
-# it is an N-4, so update it.
-echo "I:$SERVER_NAME updating IXFR-server to suitable start version"
-update_server_zone $SERVER_NAME $SERVER_IP $IXFR_TOP/db.example.n4
-if [ $? -ne 0 ];
-then
- exit 1
-fi
-
-# The pre-requisites for this test are the same as for the common tests, so
-# we can execute that directly.
-. ../common_tests.sh
-if [ $? -ne 0 ];
-then
- exit 1
-fi
-
-# TEMPORARY: at the time of writing (October 2011) BIND 10 does not attempt
-# a UDP transfer first. Therefore just check for TCP transfer.
-
-# Check that the client initiated and completed an IXFR. Use a simple grep as
-# the syntax and capabilities of egrep may vary between systems.
-grep XFRIN_XFR_TRANSFER_STARTED nsx2/bind10.run | grep IXFR > /dev/null
-if [ $? -ne 0 ];
-then
- echo "R:$CLIENT_NAME FAIL no 'IXFR started' message in the BIND 10 log"
- exit 1
-fi
-
-grep XFRIN_IXFR_TRANSFER_SUCCESS nsx2/bind10.run | grep IXFR > /dev/null
-if [ $? -ne 0 ];
-then
- echo "R:$CLIENT_NAME FAIL no 'IXFR successful' message in the BIND 10 log"
- exit 1
-fi
-
-# Look in the named log file to see if a TCP IXFR was requested. Again use a
-# simple grep.
-grep "transfer of" ns1/named.run | grep "sending TCP message" > /dev/null
-if [ $? -ne 0 ];
-then
- echo "R:$SERVER_NAME FAIL no 'sending TCP' message in the BIND 9 log"
- exit 1
-fi
-
-grep "IXFR ended" ns1/named.run > /dev/null
-if [ $? -ne 0 ];
-then
- echo "R:$SERVER_NAME FAIL no 'IXFR ended' message in the BIND 9 log"
- exit 1
-fi
-
-echo "I:exit status: 0"
-exit 0
diff --git a/tests/system/ixfr/in-3/.gitignore b/tests/system/ixfr/in-3/.gitignore
deleted file mode 100644
index 87e08bf..0000000
--- a/tests/system/ixfr/in-3/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/setup.sh
diff --git a/tests/system/ixfr/in-3/clean.sh b/tests/system/ixfr/in-3/clean.sh
deleted file mode 120000
index 099bebd..0000000
--- a/tests/system/ixfr/in-3/clean.sh
+++ /dev/null
@@ -1 +0,0 @@
-../clean_ns.sh
\ No newline at end of file
diff --git a/tests/system/ixfr/in-3/ns1/README b/tests/system/ixfr/in-3/ns1/README
deleted file mode 100644
index aaa8a31..0000000
--- a/tests/system/ixfr/in-3/ns1/README
+++ /dev/null
@@ -1,3 +0,0 @@
-This directory should be empty. A README file is placed here to ensure git
-notes the directory's presence. It can be removed if other files are placed
-here.
diff --git a/tests/system/ixfr/in-3/nsx2/README b/tests/system/ixfr/in-3/nsx2/README
deleted file mode 100644
index aaa8a31..0000000
--- a/tests/system/ixfr/in-3/nsx2/README
+++ /dev/null
@@ -1,3 +0,0 @@
-This directory should be empty. A README file is placed here to ensure git
-notes the directory's presence. It can be removed if other files are placed
-here.
diff --git a/tests/system/ixfr/in-3/setup.sh.in b/tests/system/ixfr/in-3/setup.sh.in
deleted file mode 100644
index 2a08c58..0000000
--- a/tests/system/ixfr/in-3/setup.sh.in
+++ /dev/null
@@ -1,29 +0,0 @@
-#!/bin/sh
-#
-# Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-# PERFORMANCE OF THIS SOFTWARE.
-
-. @abs_top_builddir@/tests/system/conf.sh
-# Clean up from last time
-
-sh clean.sh
-
-# Set up the initial version of the IXFR server - load the latest version of
-# the zone.
-cp -f $IXFR_TOP/named_noixfr.conf ns1/named.conf
-cp -f $IXFR_TOP/db.example.n0 ns1/db.example
-
-# Set up the IXFR client - load a previous version of the zone.
-cp -f $IXFR_TOP/b10-config.db nsx2/b10-config.db
-${B10_LOADZONE} -c "{\"database_file\": \"$IXFR_TOP/zone.sqlite3\"}" example. $IXFR_TOP/db.example.n2
diff --git a/tests/system/ixfr/in-3/tests.sh b/tests/system/ixfr/in-3/tests.sh
deleted file mode 100644
index d47a221..0000000
--- a/tests/system/ixfr/in-3/tests.sh
+++ /dev/null
@@ -1,66 +0,0 @@
-#!/bin/sh
-#
-# Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-# PERFORMANCE OF THIS SOFTWARE.
-
-# \file
-# This script performs the third IXFR-IN test. A BIND 9 nameserver (the
-# "server") contains a version of the zone (version N) and has IXFRs disabled.
-# A BIND 10 nameserver (the "client") is loaded with version N-2 of the zone.
-# A NOTIFY is sent to it, and it is expected that it will send an IXFR to the
-# server; the server should not respond to the request, so the client should
-# then send an AXFR request and receive the latest copy of the zone.
-
-# TODO It seems bind9 still allows IXFR even when provide-ixfr on;
-
-. ../ixfr_init.sh
-status=$?
-
-# Store the SOA serial number of the BIND 10 client for later use.
-old_client_serial=`$DIG_SOA @$CLIENT_IP | $AWK '{print $3}'`
-echo "I:SOA serial of IXFR client $CLIENT_NAME is $old_client_serial"
-
-# If required, get the IXFR server to notify the IXFR client of the new zone.
-# Do this by allowing notifies and then triggering a re-notification of the
-# server.
-echo "I:notifying IXFR-client $CLIENT_NAME of presence of new version of zone"
-do_rndc $SERVER_NAME $SERVER_IP notify example
-status=`expr $status + $?`
-
-# Wait for the client to update itself.
-wait_for_update $CLIENT_NAME $CLIENT_IP $old_client_serial
-status=`expr $status + $?`
-
-# Has updated, get the latest serial of the client and server - they
-# should be the same.
-compare_soa $SERVER_NAME $SERVER_IP $CLIENT_NAME $CLIENT_IP
-status=`expr $status + $?`
-
-# Check the log there's the IXFR and fallback
-grep XFRIN_XFR_TRANSFER_STARTED nsx2/bind10.run | grep IXFR
-if [ $? -ne 0 ];
-then
- echo "R:$CLIENT_NAME FAIL no 'IXFR started' message in the BIND 10 log"
- exit 1
-fi
-
-grep XFRIN_XFR_TRANSFER_FALLBACK nsx2/bind10.run
-if [ $? -ne 0 ];
-then
- echo "R:$CLIENT_NAME FAIL no fallback message in BIND10 log"
- exit 1
-fi
-
-echo "I:exit status: $status"
-exit $status
diff --git a/tests/system/ixfr/in-4/.gitignore b/tests/system/ixfr/in-4/.gitignore
deleted file mode 100644
index 87e08bf..0000000
--- a/tests/system/ixfr/in-4/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/setup.sh
diff --git a/tests/system/ixfr/in-4/clean.sh b/tests/system/ixfr/in-4/clean.sh
deleted file mode 120000
index 099bebd..0000000
--- a/tests/system/ixfr/in-4/clean.sh
+++ /dev/null
@@ -1 +0,0 @@
-../clean_ns.sh
\ No newline at end of file
diff --git a/tests/system/ixfr/in-4/ns1/README b/tests/system/ixfr/in-4/ns1/README
deleted file mode 100644
index aaa8a31..0000000
--- a/tests/system/ixfr/in-4/ns1/README
+++ /dev/null
@@ -1,3 +0,0 @@
-This directory should be empty. A README file is placed here to ensure git
-notes the directory's presence. It can be removed if other files are placed
-here.
diff --git a/tests/system/ixfr/in-4/nsx2/README b/tests/system/ixfr/in-4/nsx2/README
deleted file mode 100644
index aaa8a31..0000000
--- a/tests/system/ixfr/in-4/nsx2/README
+++ /dev/null
@@ -1,3 +0,0 @@
-This directory should be empty. A README file is placed here to ensure git
-notes the directory's presence. It can be removed if other files are placed
-here.
diff --git a/tests/system/ixfr/in-4/setup.sh.in b/tests/system/ixfr/in-4/setup.sh.in
deleted file mode 100644
index 1c2e9c8..0000000
--- a/tests/system/ixfr/in-4/setup.sh.in
+++ /dev/null
@@ -1,30 +0,0 @@
-#!/bin/sh
-#
-# Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-# PERFORMANCE OF THIS SOFTWARE.
-
-. @abs_top_builddir@/tests/system/conf.sh
-# Clean up from last time
-
-sh clean.sh
-
-# Set up the initial version of the ixfr server - load the last-but-one version
-# of the zone.
-cp $IXFR_TOP/named_nonotify.conf ns1/named.conf
-cp -f $IXFR_TOP/db.example.n2.refresh ns1/db.example
-
-# Set up the IXFR client - load a previous version of the zone with a short
-# refresh time.
-cp -f $IXFR_TOP/b10-config.db nsx2/b10-config.db
-${B10_LOADZONE} -c "{\"database_file\": \"$IXFR_TOP/zone.sqlite3\"}" example. $IXFR_TOP/db.example.n2.refresh
diff --git a/tests/system/ixfr/in-4/tests.sh b/tests/system/ixfr/in-4/tests.sh
deleted file mode 100644
index 3024253..0000000
--- a/tests/system/ixfr/in-4/tests.sh
+++ /dev/null
@@ -1,53 +0,0 @@
-#!/bin/sh
-#
-# Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-# PERFORMANCE OF THIS SOFTWARE.
-
-# \file
-# This script performs the fourth IXFR-IN test. A BIND 9 nameserver (the
-# "server") contains a version of the zone (version N) and has IXFRs enabled.
-# A BIND 10 nameserver (the "client") is loaded with version N-2 of the zone
-# and a small refresh time. After this expires, the IXFR client should send
-# an IXFR request to the IXFR server.
-
-. ../ixfr_init.sh
-status=$?
-
-# Ensure the server has the latest copy of the zone. The implicit assumption
-# here is that starting the two systems and reloading the IXFR server takes
-# less time than the SOA refresh time set in the "db.example.n2.refresh" zone
-# file.
-cp $IXFR_TOP/db.example.n0 ns1/db.example
-do_rndc $SERVER_NAME $SERVER_IP reload
-
-# Store the SOA serial number of the BIND 10 client for later use.
-old_client_serial=`$DIG_SOA @$CLIENT_IP | $AWK '{print $3}'`
-echo "I:SOA serial of IXFR client $CLIENT_NAME is $old_client_serial"
-
-# Wait for the client to update itself. 30 seconds has been given as the
-# refresh interface and 2 seconds as the retry interval. The wait_for_update
-# function will check for up to a minute looking for the new serial.
-wait_for_update $CLIENT_NAME $CLIENT_IP $old_client_serial
-status=`expr $status + $?`
-
-# Has updated, get the latest serial of the client and server - they
-# should be the same.
-compare_soa $SERVER_NAME $SERVER_IP $CLIENT_NAME $CLIENT_IP
-status=`expr $status + $?`
-
-# TODO: Check the BIND 10 log, looking for the IXFR messages that indicate that
-# the client has initiated the IXFR.
-
-echo "I:exit status: $status"
-exit $status
diff --git a/tests/system/ixfr/ixfr_init.sh.in b/tests/system/ixfr/ixfr_init.sh.in
deleted file mode 100644
index ba6049e..0000000
--- a/tests/system/ixfr/ixfr_init.sh.in
+++ /dev/null
@@ -1,330 +0,0 @@
-#!/bin/sh
-#
-# Copyright (C) 2011 Internet Software Consortium.
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-# PERFORMANCE OF THIS SOFTWARE.
-
-# \file
-# This file should be run by all IXFR tests before doing anything else. It
-# includes the main configuration script to set the environment variables as
-# well as defining useful shell subroutines.
-
-. @abs_top_builddir@/tests/system/conf.sh
-
-# Useful symbols used in the IXFR tests.
-
-# Short-hand for getting SOA - just supply address of the server
-DIG_SOA="$DIG +norecurse +short -p $DNS_PORT example. SOA"
-
-# All IXFR tests use a BIND 9 server serving a BIND 10 client. These have the
-# smae name and use the same address in all tests.
-SERVER_NAME=ns1
-SERVER_IP=10.53.0.1 # BIND 9
-
-CLIENT_NAME=nsx2
-CLIENT_IP=10.53.0.2 # BIND 10
-
-# \brief Check Arguments
-#
-# Most functions take the name of nameserver as the first argument and its IP
-# address as the second. This function is passed "$*" and just checks that
-# both $1 and $2 are defined.
-#
-# \arg $* Arguments passed to caller
-#
-# \return status 0 => $1 and $2 are defined, 1 => they are not.
-check_name_ip() {
-
- if [ "$1" = "" ];
- then
- echo "R:FAIL name of server not supplied"
- return 1
- fi
-
- if [ "$2" = "" ];
- then
- echo "R:FAIL IP address of server not supplied"
- return 1
- fi
-
- return 0
-}
-
-
-# \brief Perform RNDC Command
-#
-# Controls the BIND 9 IXFR server. Called do_rndc (instead of rndc) to avoid
-# confusion if rndc itself is in the search path.
-#
-# \arg $1 - Name of the server (ns1, nsx2 etc.)
-# \arg $2 - IP address of the server
-# \arg $* - Command to execute (which may be multiple tokens)
-#
-# \return 0 on success, 1 on failure (in which case an error message will
-# have been output).
-do_rndc () {
-
- # If the following checks fail, the code is wrong.
-
- check_name_ip $*
- if [ $? -ne 0 ];
- then
- echo "R:FAIL do_rndc - name or ip address of server not supplied"
- return 1
- fi
-
- name=$1
- shift
- ip=$1
- shift
-
- if [ "$1" = "" ];
- then
- echo "R:FAIL do_rndc - rndc command not supplied"
- return 1
- fi
-
- $RNDC -c $SYSTEM_TOP/common/rndc.conf -s $ip -p $RNDC_PORT $* 2>&1 \
- | sed "s/^/I:$name /"
-}
-
-# \brief Wait for update
-#
-# Given a serial number and a server, poll the nameserver until the SOA serial
-# number is different from that given. The poll takes place every five seconds
-# for a minute.
-#
-# \arg $1 - Name of the server
-# \arg $2 - IP address of the server
-# \arg $3 - Serial number to check against
-#
-# \return 0 if the serial number is different (requires another poll to obtain
-# it), 1 if the serial number has not changed after one minute.
-wait_for_update() {
-
- # If the following checks fail, the code is wrong.
-
- check_name_ip $*
- if [ $? -ne 0 ];
- then
- echo "R:FAIL wait_for_update - name or ip address of system not supplied"
- return 1
-
- fi
-
- name=$1
- shift
- ip=$1
- shift
-
- serial=$1
- if [ "$serial" = "" ];
- then
- echo "R:FAIL wait_for_update - serial number not supplied"
- return 1
- fi
-
- # Now poll the server looking for the new serial number
-
- echo "I:$name waiting for SOA serial to change from $serial"
- for i in 1 2 3 4 5 6 7 8 9 10 11 12
- do
- if [ $i -gt 1 ];
- then
- sleep 5
- fi
-
- new_serial=`$DIG_SOA @$ip | $AWK '{print $3}'`
- if [ "$new_serial" != "$serial" ];
- then
- echo "I:$name SOA serial was at $serial, now at $new_serial"
- return 0
- fi
- done
-
- echo "R:$name FAIL serial number has not updated"
- return 1
-}
-
-
-
-# \brief Update server zone
-#
-# Reloads the example. zone in the BIND 9 IXFR server and waits a maximum of
-# one minute for it to be served.
-#
-# \arg $1 - Name of the server (ns1, nsx2 etc.)
-# \arg $2 - IP address of the server
-# \arg $3 - Zone file to load
-# \arg $* - Command to execute (which may be multiple tokens)
-#
-# \return 0 on success, 1 on failure (for which an error message will have
-# been output).
-update_server_zone() {
-
- # If the following checks fail, the code is wrong.
-
- check_name_ip $*
- if [ $? -ne 0 ];
- then
- echo "R:FAIL update_server_zone - name or ip address of server not supplied"
- return 1
- fi
-
- name=$1
- shift
- ip=$1
- shift
-
- file=$1
- shift
- if [ "$file" = "" ];
- then
- echo "R:FAIL update_server_zone - new zone file not supplied"
- return 1
- fi
-
- if [ ! -e $file ];
- then
- echo "R:FAIL update_server_zone - zone file does not exist: $file"
- return 1
- fi
-
- old_serial=`$DIG_SOA @$ip | $AWK '{print $3}'`
-
- echo "I:$name IXFR server loading $file"
- cp $file $name/db.example
- do_rndc $name $ip reload
- if [ $? -ne 0 ];
- then
- return 1 # Message will have already been output
- fi
-
- wait_for_update $name $ip $old_serial
- if [ $? -ne 0 ];
- then
- echo "R:$name FAIL IXFR server did not update zone after reload"
- return 1
- fi
- new_serial=`$DIG_SOA @$ip | $AWK '{print $3}'`
-
- return 0
-}
-
-# \brief Compare client and server SOAs
-#
-# Checks the SOAs of two systems and reports if they are not equal.
-#
-# \arg $1 Name of the IXFR server
-# \arg $2 IP of the IXFR server
-# \arg $3 Name of the IXFR client
-# \arg $4 IP of the IXFR client
-#
-# \return 0 if the systems have the same SOA, 1 if not. In the latter case,
-# an error will be output.
-compare_soa() {
-
- # If the following checks fail, the code is wrong.
-
- check_name_ip $*
- if [ $? -ne 0 ];
- then
- echo "R:FAIL compare_soa - name or ip address of server not supplied"
- return 1
- fi
-
- server_name=$1
- shift
- server_ip=$1
- shift
-
- check_name_ip $*
- if [ $? -ne 0 ];
- then
- echo "R:FAIL compare_soa - name or ip address of client not supplied"
- return 1
- fi
-
- client_name=$1
- shift
- client_ip=$1
- shift
-
- client_serial=`$DIG_SOA @$client_ip | $AWK '{print $3}'`
- server_serial=`$DIG_SOA @$server_ip | $AWK '{print $3}'`
- if [ "$client_serial" != "$server_serial" ];
- then
- echo "R:FAIL client $client_name serial $client_serial not same as server $server_name serial $server_serial"
- return 1
- fi
-
- return 0
-}
-
-# \brief Compare client and server zones
-#
-# Checks the zones of two systems and reports if they are not identical.
-#
-# The check is simplistic. Each zone is listed via "dig", after which comment
-# lines, blank lines and spaces/tabs are removed, and the result sorted. The
-# output from each system is then compared. They should be identical.
-#
-# \arg $1 Name of the IXFR server
-# \arg $2 IP of the IXFR server
-# \arg $3 Name of the IXFR client
-# \arg $4 IP of the IXFR client
-#
-# \return 0 if the zones are the same, 1 if not.
-compare_zones() {
-
- # If the following checks fail, the code is wrong.
-
- check_name_ip $*
- if [ $? -ne 0 ];
- then
- echo "R:FAIL compare_zones - name or ip address of server not supplied"
- return 1
- fi
-
- server_name=$1
- shift
- server_ip=$1
- shift
-
- check_name_ip $*
- if [ $? -ne 0 ];
- then
- echo "R:FAIL compare_zones - name or ip address of client not supplied"
- return 1
- fi
-
- client_name=$1
- shift
- client_ip=$1
- shift
-
- $DIG @$client_ip -p $DNS_PORT example. axfr | grep -v '^;' | grep -v '^$' \
- | sed -e 's/ //g' -e 's/\t//g' | sort > client.dig
- $DIG @$server_ip -p $DNS_PORT example. axfr | grep -v '^;' | grep -v '^$' \
- | sed -e 's/ //g' -e 's/\t//g' | sort > server.dig
- diff client.dig server.dig
- if [ $? -eq 0 ];
- then
- echo "I:client and server zones identical"
- else
- echo "R:FAIL client $client_name zone not same as server $server_name zone"
- return 1
- fi
-
- return 0
-}
diff --git a/tests/system/ixfr/named_noixfr.conf b/tests/system/ixfr/named_noixfr.conf
deleted file mode 100644
index d171876..0000000
--- a/tests/system/ixfr/named_noixfr.conf
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2004, 2007, 2011 Internet Systems Consortium, Inc. ("ISC")
- * Copyright (C) 2000, 2001 Internet Software Consortium.
- *
- * 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 "../../../common/rndc.key";
-
-controls {
- inet 10.53.0.1 port 9953 allow { any; } keys { rndc_key; };
-};
-
-options {
- query-source address 10.53.0.1;
- notify-source 10.53.0.1;
- transfer-source 10.53.0.1;
- port 53210;
- pid-file "named.pid";
- listen-on { 10.53.0.1; };
- listen-on-v6 { none; };
- recursion no;
- ixfr-from-differences no;
- notify explicit;
- also-notify { 10.53.0.2; };
- provide-ixfr no;
-};
-
-zone "example" {
- type master;
- file "db.example";
-};
diff --git a/tests/system/ixfr/named_nonotify.conf b/tests/system/ixfr/named_nonotify.conf
deleted file mode 100644
index c08c212..0000000
--- a/tests/system/ixfr/named_nonotify.conf
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2004, 2007, 2011 Internet Systems Consortium, Inc. ("ISC")
- * Copyright (C) 2000, 2001 Internet Software Consortium.
- *
- * 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 "../../../common/rndc.key";
-
-controls {
- inet 10.53.0.1 port 9953 allow { any; } keys { rndc_key; };
-};
-
-options {
- query-source address 10.53.0.1;
- notify-source 10.53.0.1;
- transfer-source 10.53.0.1;
- port 53210;
- pid-file "named.pid";
- listen-on { 10.53.0.1; };
- listen-on-v6 { none; };
- recursion no;
- ixfr-from-differences yes;
- notify no;
-};
-
-zone "example" {
- type master;
- file "db.example";
-};
diff --git a/tests/system/ixfr/named_notify.conf b/tests/system/ixfr/named_notify.conf
deleted file mode 100644
index df45e6f..0000000
--- a/tests/system/ixfr/named_notify.conf
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2004, 2007, 2011 Internet Systems Consortium, Inc. ("ISC")
- * Copyright (C) 2000, 2001 Internet Software Consortium.
- *
- * 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 "../../../common/rndc.key";
-
-controls {
- inet 10.53.0.1 port 9953 allow { any; } keys { rndc_key; };
-};
-
-options {
- query-source address 10.53.0.1;
- notify-source 10.53.0.1;
- transfer-source 10.53.0.1;
- port 53210;
- pid-file "named.pid";
- listen-on { 10.53.0.1; };
- listen-on-v6 { none; };
- recursion no;
- ixfr-from-differences yes;
- notify explicit;
- also-notify { 10.53.0.2; };
-};
-
-zone "example" {
- type master;
- file "db.example";
-};
diff --git a/tests/system/run.sh.in b/tests/system/run.sh.in
deleted file mode 100755
index 619b865..0000000
--- a/tests/system/run.sh.in
+++ /dev/null
@@ -1,125 +0,0 @@
-#!/bin/sh
-#
-# Copyright (C) 2004, 2007, 2010 Internet Systems Consortium, Inc. ("ISC")
-# Copyright (C) 2000, 2001 Internet Software Consortium.
-#
-# 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.
-
-#
-# Run a system test.
-#
-
-SYSTEMTOP=@abs_top_builddir@/tests/system
-. $SYSTEMTOP/conf.sh
-
-stopservers=true
-
-case $1 in
- --keep) stopservers=false; shift ;;
-esac
-
-test $# -gt 0 || { echo "usage: $0 [--keep] test-directory" >&2; exit 1; }
-
-test=$1
-shift
-
-test -d $test || { echo "$0: $test: no such test" >&2; exit 1; }
-
-echo "S:$test:`date`" >&2
-echo "T:$test:1:A" >&2
-echo "A:System test $test" >&2
-
-if [ x$PERL = x ]
-then
- echo "I:Perl not available. Skipping test." >&2
- echo "R:UNTESTED" >&2
- echo "E:$test:`date`" >&2
- exit 0;
-fi
-
-$PERL $TESTSOCK || {
- echo "I:Network interface aliases not set up. Skipping test." >&2;
- echo "R:UNTESTED" >&2;
- echo "E:$test:`date`" >&2;
- exit 0;
-}
-
-
-# Check for test-specific prerequisites.
-test ! -f $test/prereq.sh || ( cd $test && sh prereq.sh "$@" )
-result=$?
-
-if [ $result -eq 0 ]; then
- : prereqs ok
-else
- echo "I:Prerequisites for $test missing, skipping test." >&2
- [ $result -eq 255 ] && echo "R:SKIPPED" || echo "R:UNTESTED"
- echo "E:$test:`date`" >&2
- exit 0
-fi
-
-# Check for PKCS#11 support
-if
- test ! -f $test/usepkcs11 || sh cleanpkcs11.sh
-then
- : pkcs11 ok
-else
- echo "I:Need PKCS#11 for $test, skipping test." >&2
- echo "R:PKCS11ONLY" >&2
- echo "E:$test:`date`" >&2
- exit 0
-fi
-
-# Set up any dynamically generated test data
-if test -f $test/setup.sh
-then
- ( cd $test && sh setup.sh "$@" )
-fi
-
-# Start name servers running
-$PERL $SYSTEMTOP/start.pl $test || exit 1
-
-# Run the tests
-( cd $test ; sh tests.sh )
-
-status=$?
-
-if $stopservers
-then
- :
-else
- exit $status
-fi
-
-# Shutdown
-$PERL $SYSTEMTOP/stop.pl $test
-
-status=`expr $status + $?`
-
-if [ $status != 0 ]; then
- echo "R:FAIL"
- # Don't clean up - we need the evidence.
- find . -name core -exec chmod 0644 '{}' \;
-else
- echo "R:PASS"
-
- # Clean up.
- if test -f $test/clean.sh
- then
- ( cd $test && sh clean.sh "$@" )
- fi
-fi
-
-echo "E:$test:`date`"
-
-exit $status
diff --git a/tests/system/runall.sh b/tests/system/runall.sh
deleted file mode 100755
index 5d0fe9b..0000000
--- a/tests/system/runall.sh
+++ /dev/null
@@ -1,44 +0,0 @@
-#!/bin/sh
-#
-# Copyright (C) 2004, 2007, 2010 Internet Systems Consortium, Inc. ("ISC")
-# Copyright (C) 2000, 2001 Internet Software Consortium.
-#
-# 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.
-
-#
-# Run all the system tests.
-#
-
-SYSTEMTESTTOP=.
-. $SYSTEMTESTTOP/conf.sh
-
-status=0
-
-for d in $SUBDIRS
-do
- sh run.sh $d || status=1
-done
-
-$PERL $TESTSOCK || {
- cat <<EOF >&2
-I:
-I:NOTE: Many of the tests were skipped because they require that
-I: the IP addresses 10.53.0.1 through 10.53.0.7 are configured
-I: as alias addresses on the loopback interface. Please run
-I: "tests/system/ifconfig.sh up" as root to configure them
-I: and rerun the tests.
-EOF
- exit 0;
-}
-
-exit $status
diff --git a/tests/system/start.pl b/tests/system/start.pl
deleted file mode 100755
index 32284de..0000000
--- a/tests/system/start.pl
+++ /dev/null
@@ -1,229 +0,0 @@
-#!/usr/bin/perl -w
-#
-# Copyright (C) 2004-2008, 2010 Internet Systems Consortium, Inc. ("ISC")
-# Copyright (C) 2001 Internet Software Consortium.
-#
-# 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.
-
-# Framework for starting test servers.
-# Based on the type of server specified, check for port availability, remove
-# temporary files, start the server, and verify that the server is running.
-# If a server is specified, start it. Otherwise, start all servers for test.
-
-use strict;
-use Cwd 'abs_path';
-use Getopt::Long;
-
-# Option handling
-# --noclean test [server [options]]
-#
-# --noclean - Do not cleanup files in server directory
-# test - name of the test directory
-# server - name of the server directory
-# options - alternate options for the server
-
-my $usage = "usage: $0 [--noclean] test-directory [server-directory [server-options]]";
-my $noclean;
-GetOptions('noclean' => \$noclean);
-my $test = $ARGV[0];
-my $server = $ARGV[1];
-my $options = $ARGV[2];
-
-if (!$test) {
- print "$usage\n";
-}
-if (!-d $test) {
- print "No test directory: \"$test\"\n";
-}
-if ($server && !-d "$test/$server") {
- print "No server directory: \"$test/$server\"\n";
-}
-
-# Global variables
-my $topdir = abs_path("$test/..");
-my $testdir = abs_path("$test");
-my $RUN_BIND10 = $ENV{'RUN_BIND10'};
-my $RUN_BINDCTL = $ENV{'RUN_BINDCTL'};
-my $BINDCTL_CSV_DIR = $ENV{'BINDCTL_CSV_DIR'};
-my $NAMED = $ENV{'BIND9_NAMED'};
-my $LWRESD = $ENV{'LWRESD'};
-my $DIG = $ENV{'DIG'};
-my $PERL = $ENV{'PERL'};
-my $TESTSOCK = $ENV{'TESTSOCK'};
-
-# Start the server(s)
-
-if ($server) {
- if ($server =~ /^ns/) {
- &check_ports($server);
- }
- &start_server($server, $options);
- if ($server =~ /^ns/) {
- &verify_server($server);
- }
-} else {
- # Determine which servers need to be started for this test.
- opendir DIR, $testdir;
- my @files = sort readdir DIR;
- closedir DIR;
-
- my @ns = grep /^nsx?[0-9]*$/, @files;
- my @lwresd = grep /^lwresd[0-9]*$/, @files;
- my @ans = grep /^ans[0-9]*$/, @files;
-
- # Start the servers we found.
- &check_ports();
- foreach my $s (@ns, @lwresd, @ans) {
- &start_server($s);
- }
- foreach my $s (@ns) {
- &verify_server($s);
- }
-}
-
-# Subroutines
-
-sub check_ports {
- my $server = shift;
- my $options = "";
-
- if ($server && $server =~ /(\d+)$/) {
- $options = "-i $1";
- }
-
- my $tries = 0;
- while (1) {
- my $return = system("$PERL $TESTSOCK -p 53210 $options");
- last if ($return == 0);
- if (++$tries > 4) {
- print "$0: could not bind to server addresses, still running?\n";
- print "I:server sockets not available\n";
- print "R:FAIL\n";
- system("$PERL $topdir/stop.pl $testdir"); # Is this the correct behavior?
- exit 1;
- }
- print "I:Couldn't bind to socket (yet)\n";
- sleep 2;
- }
-}
-
-sub start_server {
- my $server = shift;
- my $options = shift;
-
- my $cleanup_files;
- my $command;
- my $pid_file;
-
- if ($server =~ /^nsx/) {
- $cleanup_files = "{bind10.run}";
- $command = "B10_FROM_SOURCE_LOCALSTATEDIR=$testdir/$server/ ";
- $command .= "$RUN_BIND10 ";
- if ($options) {
- $command .= "$options";
- } else {
- $command .= "--msgq-socket-file=$testdir/$server/msgq_socket ";
- $command .= "--pid-file=$testdir/$server/bind10.pid ";
- $command .= "-v";
- }
- $command .= " >bind10.run 2>&1 &";
- $pid_file = "bind10.pid";
- } elsif ($server =~ /^ns/) {
- $cleanup_files = "{*.jnl,*.bk,*.st,named.run}";
- $command = "$NAMED ";
- if ($options) {
- $command .= "$options";
- } else {
- $command .= "-m record,size,mctx ";
- $command .= "-T clienttest ";
- $command .= "-T nosoa "
- if (-e "$testdir/$server/named.nosoa");
- $command .= "-T noaa "
- if (-e "$testdir/$server/named.noaa");
- $command .= "-c named.conf -d 99 -g";
- }
- $command .= " >named.run 2>&1 &";
- $pid_file = "named.pid";
- } elsif ($server =~ /^lwresd/) {
- $cleanup_files = "{lwresd.run}";
- $command = "$LWRESD ";
- if ($options) {
- $command .= "$options";
- } else {
- $command .= "-m record,size,mctx ";
- $command .= "-T clienttest ";
- $command .= "-C resolv.conf -d 99 -g ";
- $command .= "-i lwresd.pid -P 9210 -p 53210";
- }
- $command .= " >lwresd.run 2>&1 &";
- $pid_file = "lwresd.pid";
- } elsif ($server =~ /^ans/) {
- $cleanup_files = "{ans.run}";
- $command = "$PERL ./ans.pl ";
- if ($options) {
- $command .= "$options";
- } else {
- $command .= "";
- }
- $command .= " >ans.run 2>&1 &";
- $pid_file = "ans.pid";
- } else {
- print "I:Unknown server type $server\n";
- print "R:FAIL\n";
- system "$PERL $topdir/stop.pl $testdir";
- exit 1;
- }
-
- print "I:starting server $server\n";
-
- chdir "$testdir/$server";
-
- unless ($noclean) {
- unlink glob $cleanup_files;
- }
-
- system "$command";
-
- my $tries = 0;
- while (!-f $pid_file) {
- if (++$tries > 14) {
- print "I:Couldn't start server $server\n";
- print "R:FAIL\n";
- system "$PERL $topdir/stop.pl $testdir";
- exit 1;
- }
- sleep 1;
- }
-}
-
-sub verify_server {
- my $server = shift;
- my $n = $server;
- $n =~ s/^nsx?//;
-
- my $tries = 0;
- while (1) {
- my $return = system("echo \"Stats show\" | $RUN_BINDCTL --csv-file-dir=$BINDCTL_CSV_DIR > bindctl.out");
- last if ($return == 0);
- if (++$tries >= 30) {
- print "I:no response from $server\n";
- print "R:FAIL\n";
- system("$PERL $topdir/stop.pl $testdir");
- exit 1;
- } else {
- print "I:no response from $server. retrying.\n";
- }
- sleep 2;
- }
- unlink "dig.out";
-}
diff --git a/tests/system/stop.pl b/tests/system/stop.pl
deleted file mode 100755
index a803f52..0000000
--- a/tests/system/stop.pl
+++ /dev/null
@@ -1,188 +0,0 @@
-#!/usr/bin/perl -w
-#
-# Copyright (C) 2004-2007 Internet Systems Consortium, Inc. ("ISC")
-# Copyright (C) 2001 Internet Software Consortium.
-#
-# 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.
-
-# Framework for stopping test servers
-# Based on the type of server specified, signal the server to stop, wait
-# briefly for it to die, and then kill it if it is still alive.
-# If a server is specified, stop it. Otherwise, stop all servers for test.
-
-use strict;
-use Cwd 'abs_path';
-
-# Option handling
-# [--use-rndc] test [server]
-#
-# test - name of the test directory
-# server - name of the server directory
-
-my $usage = "usage: $0 [--use-rndc] test-directory [server-directory]";
-my $use_rndc;
-
-while (@ARGV && $ARGV[0] =~ /^-/) {
- my $opt = shift @ARGV;
- if ($opt eq '--use-rndc') {
- $use_rndc = 1;
- } else {
- die "$usage\n";
- }
-}
-
-my $test = $ARGV[0];
-my $server = $ARGV[1];
-
-my $errors = 0;
-
-die "$usage\n" unless defined($test);
-die "No test directory: \"$test\"\n" unless (-d $test);
-die "No server directory: \"$server\"\n" if (defined($server) && !-d "$test/$server");
-
-# Global variables
-my $testdir = abs_path($test);
-my @servers;
-
-
-# Determine which servers need to be stopped.
-if (defined $server) {
- @servers = ($server);
-} else {
- local *DIR;
- opendir DIR, $testdir or die "$testdir: $!\n";
- my @files = sort readdir DIR;
- closedir DIR;
-
- my @ns = grep /^nsx?[0-9]*$/, @files;
- my @lwresd = grep /^lwresd[0-9]*$/, @files;
- my @ans = grep /^ans[0-9]*$/, @files;
-
- push @servers, @ns, @lwresd, @ans;
-}
-
-
-# Stop the server(s), pass 1: rndc.
-if ($use_rndc) {
- foreach my $server (grep /^ns/, @servers) {
- stop_rndc($server);
- }
-
- wait_for_servers(30, grep /^ns/, @servers);
-}
-
-
-# Pass 2: SIGTERM
-foreach my $server (@servers) {
- stop_signal($server, "TERM");
-}
-
-wait_for_servers(60, @servers);
-
-# Pass 3: SIGABRT
-foreach my $server (@servers) {
- stop_signal($server, "ABRT");
-}
-
-exit($errors ? 1 : 0);
-
-# Subroutines
-
-# Return the full path to a given server's PID file.
-sub server_pid_file {
- my($server) = @_;
-
- my $pid_file;
- if ($server =~ /^nsx/) {
- $pid_file = "bind10.pid";
- } elsif ($server =~ /^ns/) {
- $pid_file = "named.pid";
- } elsif ($server =~ /^lwresd/) {
- $pid_file = "lwresd.pid";
- } elsif ($server =~ /^ans/) {
- $pid_file = "ans.pid";
- } else {
- print "I:Unknown server type $server\n";
- exit 1;
- }
- $pid_file = "$testdir/$server/$pid_file";
-}
-
-# Read a PID.
-sub read_pid {
- my($pid_file) = @_;
-
- local *FH;
- my $result = open FH, "< $pid_file";
- if (!$result) {
- print "I:$pid_file: $!\n";
- unlink $pid_file;
- return;
- }
-
- my $pid = <FH>;
- chomp($pid);
- return $pid;
-}
-
-# Stop a named process with rndc.
-sub stop_rndc {
- my($server) = @_;
-
- return unless ($server =~ /^ns(\d+)$/);
- my $ip = "10.53.0.$1";
-
- # Ugly, but should work.
- system("$ENV{RNDC} -c $testdir/../common/rndc.conf -s $ip -p 9953 stop | sed 's/^/I:$server /'");
- return;
-}
-
-# Stop a server by sending a signal to it.
-sub stop_signal {
- my($server, $sig) = @_;
-
- my $pid_file = server_pid_file($server);
- return unless -f $pid_file;
-
- my $pid = read_pid($pid_file);
- return unless defined($pid);
-
- if ($sig eq 'ABRT') {
- print "I:$server didn't die when sent a SIGTERM\n";
- $errors++;
- }
-
- my $result = kill $sig, $pid;
- if (!$result) {
- print "I:$server died before a SIG$sig was sent\n";
- unlink $pid_file;
- $errors++;
- }
-
- return;
-}
-
-sub wait_for_servers {
- my($timeout, @servers) = @_;
-
- my @pid_files = grep { defined($_) }
- map { server_pid_file($_) } @servers;
-
- while ($timeout > 0 && @pid_files > 0) {
- @pid_files = grep { -f $_ } @pid_files;
- sleep 1 if (@pid_files > 0);
- $timeout--;
- }
-
- return;
-}
diff --git a/tests/tools/badpacket/option_info.h b/tests/tools/badpacket/option_info.h
index 944f60e..8cc0fe9 100644
--- a/tests/tools/badpacket/option_info.h
+++ b/tests/tools/badpacket/option_info.h
@@ -151,7 +151,7 @@ public:
/// \param index A valid index (one of the values in the 'Index' enum).
///
/// \return Maximum allowed value for this option. If the option is a bit
- /// in the flags field of the DNS message hearder, this will be 1.
+ /// in the flags field of the DNS message header, this will be 1.
static uint32_t maxval(int index);
/// \brief Check Array Index
diff --git a/tests/tools/dhcp-ubench/dhcp-perf-guide.xml b/tests/tools/dhcp-ubench/dhcp-perf-guide.xml
index 7ff534c..0115cd6 100644
--- a/tests/tools/dhcp-ubench/dhcp-perf-guide.xml
+++ b/tests/tools/dhcp-ubench/dhcp-perf-guide.xml
@@ -316,7 +316,7 @@
turned to several modes of operation. Its value can be
modified in SQLite_uBenchmark::connect(). See
http://www.sqlite.org/pragma.html#pragma_journal_mode for
- detailed explanantion.</para>
+ a detailed explanation.</para>
<para>sqlite_bench supports precompiled statements. Please use
'-c no|yes' to define which should be used: basic SQL query (no) or
@@ -898,7 +898,7 @@ SQLite version: 3.7.9sourceid version is 2011-11-01 00:52:41 c7c6050ef060877ebe7
</para>
<para>
- It should be emphaisized that obtained measurements indicate
+ It should be emphasized that obtained measurements indicate
only database performance and they cannot be directly
translated to expected leases per second or queries per second
performance by an actual server. The DHCP server must do much
@@ -1384,7 +1384,7 @@ collected packets: 0
The content in template files is encoded as series of ASCII hexadecimal
digits (each byte represented by two ASCII chars 00..FF). Data in a
template file is laid in network byte order and it can be used on the
- systems with different endianess.
+ systems with different endianness.
perfdhcp forms the packet by replacing parts of the message buffer read
from the file with variable data such as elapsed time, hardware address, DUID
etc. The offsets where such variable data is placed is specific to the
diff --git a/tests/tools/perfdhcp/command_options.cc b/tests/tools/perfdhcp/command_options.cc
index 9433442..c0ae6fa 100644
--- a/tests/tools/perfdhcp/command_options.cc
+++ b/tests/tools/perfdhcp/command_options.cc
@@ -433,7 +433,7 @@ CommandOptions::initialize(int argc, char** argv, bool print_cmd_line) {
}
if (server_name_.empty()) {
isc_throw(InvalidParameter,
- "without an inteface server is required");
+ "without an interface, server is required");
}
// If DUID is not specified from command line we need to
@@ -548,7 +548,7 @@ CommandOptions::decodeDuid(const std::string& base) {
} catch (isc::InvalidParameter&) {
isc_throw(isc::InvalidParameter,
"invalid characters in DUID provided,"
- " exepected hex digits");
+ " expected hex digits");
}
duid_template.push_back(static_cast<uint8_t>(ui));
}
diff --git a/tests/tools/perfdhcp/pkt_transform.h b/tests/tools/perfdhcp/pkt_transform.h
index c94e9ba..51c1c0b 100644
--- a/tests/tools/perfdhcp/pkt_transform.h
+++ b/tests/tools/perfdhcp/pkt_transform.h
@@ -54,7 +54,7 @@ public:
/// if the option's offset + its size is beyond the packet's size.
///
/// \param universe Universe used, V4 or V6
- /// \param in_buffer Input buffer holding intial packet
+ /// \param in_buffer Input buffer holding initial packet
/// data, this can be directly read from template file
/// \param options Options collection with offsets
/// \param transid_offset offset of transaction id in a packet,
diff --git a/tests/tools/perfdhcp/stats_mgr.h b/tests/tools/perfdhcp/stats_mgr.h
index 0a2df66..c51ba6d 100644
--- a/tests/tools/perfdhcp/stats_mgr.h
+++ b/tests/tools/perfdhcp/stats_mgr.h
@@ -262,7 +262,8 @@ public:
/// In this mode all packets are stored throughout the test execution.
ExchangeStats(const ExchangeType xchg_type,
const double drop_time,
- const bool archive_enabled)
+ const bool archive_enabled,
+ const boost::posix_time::ptime boot_time)
: xchg_type_(xchg_type),
sent_packets_(),
rcvd_packets_(),
@@ -279,7 +280,8 @@ public:
unordered_lookups_(0),
ordered_lookups_(0),
sent_packets_num_(0),
- rcvd_packets_num_(0)
+ rcvd_packets_num_(0),
+ boot_time_(boot_time)
{
next_sent_ = sent_packets_.begin();
}
@@ -668,7 +670,7 @@ public:
if (rcvd_packets_num_ == 0) {
std::cout << "Unavailable! No packets received." << std::endl;
}
- // We will be using boost::posix_time extensivelly here
+ // We will be using boost::posix_time extensively here
using namespace boost::posix_time;
// Iterate through all received packets.
@@ -699,9 +701,8 @@ public:
"packet time is not set");
}
// Calculate durations of packets from beginning of epoch.
- ptime epoch_time(min_date_time);
- time_period sent_period(epoch_time, sent_time);
- time_period rcvd_period(epoch_time, rcvd_time);
+ time_period sent_period(boot_time_, sent_time);
+ time_period rcvd_period(boot_time_, rcvd_time);
// Print timestamps for sent and received packet.
std::cout << "sent / received: "
<< to_iso_string(sent_period.length())
@@ -719,7 +720,7 @@ public:
/// \brief Private default constructor.
///
/// Default constructor is private because we want the client
- /// class to specify exchange type explicitely.
+ /// class to specify exchange type explicitly.
ExchangeStats();
/// \brief Erase packet from the list of sent packets.
@@ -803,6 +804,7 @@ public:
uint64_t sent_packets_num_; ///< Total number of sent packets.
uint64_t rcvd_packets_num_; ///< Total number of received packets.
+ boost::posix_time::ptime boot_time_; ///< Time when test is started.
};
/// Pointer to ExchangeStats.
@@ -853,7 +855,8 @@ public:
exchanges_[xchg_type] =
ExchangeStatsPtr(new ExchangeStats(xchg_type,
drop_time,
- archive_enabled_));
+ archive_enabled_,
+ boot_time_));
}
/// \brief Add named custom uint64 counter.
@@ -905,7 +908,7 @@ public:
/// \brief Increment specified counter.
///
- /// Increement counter value by one.
+ /// Increment counter value by one.
///
/// \param counter_key key poiting to the counter in the counters map.
/// \param value value to increment counter by.
diff --git a/tests/tools/perfdhcp/test_control.cc b/tests/tools/perfdhcp/test_control.cc
index 4a3075a..925e9b6 100644
--- a/tests/tools/perfdhcp/test_control.cc
+++ b/tests/tools/perfdhcp/test_control.cc
@@ -57,7 +57,7 @@ TestControl::TestControlSocket::TestControlSocket(const int socket) :
}
TestControl::TestControlSocket::~TestControlSocket() {
- IfaceMgr::Iface* iface = IfaceMgr::instance().getIface(ifindex_);
+ Iface* iface = IfaceMgr::instance().getIface(ifindex_);
if (iface) {
iface->delSocket(sockfd_);
}
@@ -70,9 +70,9 @@ TestControl::TestControlSocket::initSocketData() {
for (IfaceMgr::IfaceCollection::const_iterator it = ifaces.begin();
it != ifaces.end();
++it) {
- const IfaceMgr::SocketCollection& socket_collection =
+ const Iface::SocketCollection& socket_collection =
it->getSockets();
- for (IfaceMgr::SocketCollection::const_iterator s =
+ for (Iface::SocketCollection::const_iterator s =
socket_collection.begin();
s != socket_collection.end();
++s) {
@@ -592,19 +592,27 @@ TestControl::openSocket() const {
std::string localname = options.getLocalName();
std::string servername = options.getServerName();
uint16_t port = options.getLocalPort();
- uint8_t family = AF_INET;
int sock = 0;
+
+ uint8_t family = (options.getIpVersion() == 6) ? AF_INET6 : AF_INET;
IOAddress remoteaddr(servername);
+
+ // Check for mismatch between IP option and server address
+ if (family != remoteaddr.getFamily()) {
+ isc_throw(InvalidParameter,
+ "Values for IP version: " <<
+ static_cast<unsigned int>(options.getIpVersion()) <<
+ " and server address: " << servername << " are mismatched.");
+ }
+
if (port == 0) {
- if (options.getIpVersion() == 6) {
+ if (family == AF_INET6) {
port = DHCP6_CLIENT_PORT;
} else if (options.getIpVersion() == 4) {
port = 67; // TODO: find out why port 68 is wrong here.
}
}
- if (options.getIpVersion() == 6) {
- family = AF_INET6;
- }
+
// Local name is specified along with '-l' option.
// It may point to interface name or local address.
if (!localname.empty()) {
@@ -653,7 +661,7 @@ TestControl::openSocket() const {
// If user specified interface name with '-l' the
// IPV6_MULTICAST_IF has to be set.
if ((ret >= 0) && options.isInterface()) {
- IfaceMgr::Iface* iface =
+ Iface* iface =
IfaceMgr::instance().getIface(options.getLocalName());
if (iface == NULL) {
isc_throw(Unexpected, "unknown interface "
@@ -905,8 +913,8 @@ TestControl::readPacketTemplate(const std::string& file_name) {
hex_digits.push_back(file_contents[i]);
} else if (!isxdigit(file_contents[i]) &&
!isspace(file_contents[i])) {
- isc_throw(BadValue, "the '" << file_contents[i] << "' is not a"
- " heaxadecimal digit");
+ isc_throw(BadValue, "'" << file_contents[i] << "' is not a"
+ " hexadecimal digit");
}
}
// Expect even number of digits.
@@ -1791,7 +1799,7 @@ TestControl::setDefaults4(const TestControlSocket& socket,
const Pkt4Ptr& pkt) {
CommandOptions& options = CommandOptions::instance();
// Interface name.
- IfaceMgr::Iface* iface = IfaceMgr::instance().getIface(socket.ifindex_);
+ Iface* iface = IfaceMgr::instance().getIface(socket.ifindex_);
if (iface == NULL) {
isc_throw(BadValue, "unable to find interface with given index");
}
@@ -1817,7 +1825,7 @@ TestControl::setDefaults6(const TestControlSocket& socket,
const Pkt6Ptr& pkt) {
CommandOptions& options = CommandOptions::instance();
// Interface name.
- IfaceMgr::Iface* iface = IfaceMgr::instance().getIface(socket.ifindex_);
+ Iface* iface = IfaceMgr::instance().getIface(socket.ifindex_);
if (iface == NULL) {
isc_throw(BadValue, "unable to find interface with given index");
}
@@ -1864,7 +1872,7 @@ TestControl::updateSendDue() {
// timer resolution.
duration = time_duration::ticks_per_second() / rate;
}
- // Calculate due time to initate next chunk of exchanges.
+ // Calculate due time to initiate next chunk of exchanges.
send_due_ = last_sent_ + time_duration(0, 0, 0, duration);
// Check if it is already due.
ptime now(microsec_clock::universal_time());
diff --git a/tests/tools/perfdhcp/test_control.h b/tests/tools/perfdhcp/test_control.h
index 936d256..3983fa6 100644
--- a/tests/tools/perfdhcp/test_control.h
+++ b/tests/tools/perfdhcp/test_control.h
@@ -85,7 +85,7 @@ static const size_t DHCPV6_IA_NA_OFFSET = 40;
/// - calculate how many packets must be send to satisfy desired rate,
/// - receive incoming packets from the server,
/// - check the exit conditions - terminate the program if the exit criteria
-/// are fulfiled, e.g. reached maximum number of packet drops,
+/// are fulfilled, e.g. reached maximum number of packet drops,
/// - send the number of packets appropriate to satisfy the desired rate,
/// - optionally print intermediate reports,
/// - print statistics, e.g. achieved rate,
@@ -143,7 +143,7 @@ public:
/// when exception occurs). This structure extends parent
/// structure with new field ifindex_ that holds interface
/// index where socket is bound to.
- struct TestControlSocket : public dhcp::IfaceMgr::SocketInfo {
+ struct TestControlSocket : public dhcp::SocketInfo {
/// Interface index.
uint16_t ifindex_;
/// Is socket valid. It will not be valid if the provided socket
@@ -282,14 +282,14 @@ protected:
/// \brief Check if test exit condtitions fulfilled.
///
- /// Method checks if the test exit conditions are fulfiled.
+ /// Method checks if the test exit conditions are fulfilled.
/// Exit conditions are checked periodically from the
/// main loop. Program should break the main loop when
/// this method returns true. It is calling function
/// responsibility to break main loop gracefully and
/// cleanup after test execution.
///
- /// \return true if any of the exit conditions is fulfiled.
+ /// \return true if any of the exit conditions is fulfilled.
bool checkExitConditions() const;
/// \brief Factory function to create DHCPv6 ELAPSED_TIME option.
@@ -643,7 +643,7 @@ protected:
/// \brief Send DHCPv4 DISCOVER message from template.
///
/// Method sends DHCPv4 DISCOVER message from template. The
- /// template data is exepcted to be in binary format. Provided
+ /// template data is expected to be in binary format. Provided
/// buffer is copied and parts of it are replaced with actual
/// data (e.g. MAC address, transaction id etc.).
/// Copy of sent packet is stored in the stats_mgr4_ object to
@@ -909,7 +909,7 @@ private:
/// \brief Handle interrupt signal.
///
/// Function sets flag indicating that program has been
- /// interupted.
+ /// interrupted.
///
/// \param sig signal (ignored)
static void handleInterrupt(int sig);
@@ -970,7 +970,7 @@ private:
NumberGeneratorPtr transid_gen_; ///< Transaction id generator.
NumberGeneratorPtr macaddr_gen_; ///< Numbers generator for MAC address.
- /// Buffer holiding server id received in first packet
+ /// Buffer holding server id received in first packet
dhcp::OptionBuffer first_packet_serverid_;
/// Packet template buffers.
diff --git a/tests/tools/perfdhcp/tests/command_options_unittest.cc b/tests/tools/perfdhcp/tests/command_options_unittest.cc
index bda256f..7a109fb 100644
--- a/tests/tools/perfdhcp/tests/command_options_unittest.cc
+++ b/tests/tools/perfdhcp/tests/command_options_unittest.cc
@@ -546,7 +546,7 @@ TEST_F(CommandOptionsTest, Interface) {
// In order to make this test portable we need to know
// at least one interface name on OS where test is run.
// Interface Manager has ability to detect interfaces.
- // Although we don't call initIsInterface explicitely
+ // Although we don't call initIsInterface explicitly
// here it is called by CommandOptions object interally
// so this function is covered by the test.
dhcp::IfaceMgr& iface_mgr = dhcp::IfaceMgr::instance();
diff --git a/tests/tools/perfdhcp/tests/test_control_unittest.cc b/tests/tools/perfdhcp/tests/test_control_unittest.cc
index f7666ae..3e0145c 100644
--- a/tests/tools/perfdhcp/tests/test_control_unittest.cc
+++ b/tests/tools/perfdhcp/tests/test_control_unittest.cc
@@ -672,6 +672,18 @@ TEST_F(TestControlTest, GenerateDuid) {
testDuid();
}
+TEST_F(TestControlTest, MisMatchVerionServer) {
+ NakedTestControl tc;
+
+ // make sure we catch -6 paired with v4 address
+ ASSERT_NO_THROW(processCmdLine("perfdhcp -l 127.0.0.1 -6 192.168.1.1"));
+ EXPECT_THROW(tc.openSocket(), isc::InvalidParameter);
+
+ // make sure we catch -4 paired with v6 address
+ ASSERT_NO_THROW(processCmdLine("perfdhcp -l 127.0.0.1 -4 ff02::1:2"));
+ EXPECT_THROW(tc.openSocket(), isc::InvalidParameter);
+}
+
TEST_F(TestControlTest, GenerateMacAddress) {
// Simulate one client only. Always the same MAC address will be
// generated.
diff --git a/tools/system_messages.py b/tools/system_messages.py
index 08a35e1..869a164 100644
--- a/tools/system_messages.py
+++ b/tools/system_messages.py
@@ -268,7 +268,7 @@ def addToDictionary(msgid, msgtext, desc, filename):
name of the message file in the messages manual.
"""
- # If the ID is in the dictionary, append a "(n)" to the name - this wil
+ # If the ID is in the dictionary, append a "(n)" to the name - this will
# flag that there are multiple instances. (However, this is an error -
# each ID should be unique in BIND-10.)
if msgid in dictionary:
More information about the bind10-changes
mailing list