BIND 10 trac2642, updated. 7bed043f816679ee3ee3c6d75202d47b1531b37f [2642] Merge branch 'master' into trac2642
BIND 10 source code commits
bind10-changes at lists.isc.org
Wed Jan 23 10:25:36 UTC 2013
The branch, trac2642 has been updated
via 7bed043f816679ee3ee3c6d75202d47b1531b37f (commit)
via 8b05639cdcea9e7fd65a50c1e62fb449a4526916 (commit)
via 91aa998226f1f91a232f2be59a53c9568c4ece77 (commit)
via d72eca9cdec9acb880e136c1b908fb136c7e1957 (commit)
via a3cd0126b46392c4c150d9e865ff82811e24006f (commit)
via 27e6119093723a1e46a239ec245a8b4b10677635 (commit)
via 79acb1197b7046a21034d5a1dadeb6b2efbf030a (commit)
via de49cb6406849be06c5452ca532bb1007fe5c509 (commit)
via 5b8a824054313bdecb8988b46e55cb2e94cb2d6c (commit)
via cf78a006585e1918376e7e5847102c4dc435be19 (commit)
via 217087aad01f35b863da3b69012d4b3904b35a7c (commit)
via 4b7b2f9a808352bb4ef1a71de3a46cdd79076050 (commit)
via 9bb9dbe6234769cbe525c24887eefe16ef1d7cee (commit)
via 4890e1207ce2c4fd0875c321e14c8931c6313e6b (commit)
via 8ea9fa7608fbb63b0effbef970535ab96ef4edee (commit)
via 5d3f0570a21d753d681d170622df02ef39ebb70d (commit)
via b5b877d1ec728e2dd0d8992a825dd2145c1277a9 (commit)
via 2cbabde53f226cb4fa4883b9578268719f28732b (commit)
via 6c6f405188cc02d2358e114c33daff58edabd52a (commit)
via 5237d9415f6fb14c932695d59d1d2721b185bfe3 (commit)
via d117d8720025b9e57fd1cd2eae7792c0700cdfff (commit)
via 7e31b4ded3983b1271fec3799798b52c105b140e (commit)
via 525333e187cc4bbbbde288105c9582c1024caa4a (commit)
via 2023dc6ee89d9e43dccbf85e55505ef54e4e8895 (commit)
via ca962cbac01e4c3c79050b57ca07165c3b7662ae (commit)
via 016925ef2437e0396127e135c937d3a55539d224 (commit)
via 0be5967e2ff6364d8d38ad297be99925dfe20cc4 (commit)
via c4df99eac2910af079f19b0bdacc95d7cd88192f (commit)
via 99a6e8470907e678cd8c208eeef2810b4bd0bacb (commit)
via 6167f31e01864656cf1a26a0d6bea75f08783a01 (commit)
via d9421d93e3a65e6025281ece92706f7c2003ae77 (commit)
via 32973fceb52e1da15903df796fc1fa55dc3486e4 (commit)
via 1bbf2ae947fdfb85b57fcd41a5c3f386bbadd221 (commit)
via c4a5a733f2e6729f4d91bc7c1117f4ff0fa70e1c (commit)
via f4a107a8f8775523e32d8ba0bc3e28e2f26eff3a (commit)
via 0a89b374d57877e3b1ea1f379276d74a34ea7d8f (commit)
via d27b4c92e0aa23a8d39338377d705a75f78d1f3e (commit)
via a4bbade712c4a082d8ae95c9389021d27b74b382 (commit)
via aad83b6edde86e70a460cdf887b985841c161016 (commit)
via 95d6795825d9df9634d2282b28d1d9a5761b6634 (commit)
via 39a3150a0fe822b177c53379b2c93ad6f24585b9 (commit)
via 610f3ecc55290ff4c2d3453f0f90c222b00a6f38 (commit)
via ea6e8ea2e28a3c7b22ca2390f3509d791522959b (commit)
via 5204b8af562627419798146f71f7402849bfaedc (commit)
via a70f6172194a976b514cd7d67ce097bbca3c2798 (commit)
via 0a433790559aa0e0fe148a06bbe50d9df522312a (commit)
via c80274000aed9ab258b3e0b46ddb2c3212a7b877 (commit)
via f2c0ce1adf8090ecb9ed76e6f3f201d0b044e060 (commit)
via 88d25efcbc3e7b6a8fe7dea5c78e40115be1b1f4 (commit)
via dd637a386748c2a393acc3d437c071a10b9cc7f8 (commit)
via c0b406791e7d3be3fb7962d52f6b6a320b4a7736 (commit)
via dc2aa27ea58ec3be03e742f2f4dd123a2c044200 (commit)
via 6fcb36d000e1da11d66baeb4b97278dd974d6a01 (commit)
via bca4daf3e3ea6538b6796e570daaeedb516b2dcb (commit)
via b7221c64cd88051434a3d254a0ec6a1fe07ddf6b (commit)
via 0f244147145019810ae16b36383352cbf4672919 (commit)
via 9423db0d25e30b0a3e1334b56c0ae754675bb2ce (commit)
via fe931e2f1c65e3bde969aa3bb0a2a031201fab95 (commit)
via 003f49d97927cc4dd85c4a631611152e7e10bd1c (commit)
via fd29a774079b86f2b62c857d13ef450f0da29a00 (commit)
via e149d57f779c6597a6ed1d497c40a9641ac6651c (commit)
via ce19e77d9f6b9715502b5636511421dc09148f86 (commit)
via 88ff44432d5fed287aa8a91d620692f4a715ae1c (commit)
via 9f03e9dd3f34b0a502b0d69ec7f08b582011dcf6 (commit)
via f3709cc11e26e2bb2a0cfa4f447efce885160276 (commit)
via d31f5bd476744ced094fc2aecb557b945586b056 (commit)
via bb6afeceea763fdfd012b8a2b6e9e4247f2d623d (commit)
via fa342a994de5dbefe32996be7eebe58f6304cff7 (commit)
via 931a65b517fd80cf03afbd8ff3c5c70a121f66e5 (commit)
via 671b6ae757f8a4f22bec449afa9b743c4ec2d719 (commit)
via 4d44f0bf193dac303be3a15258fe8faaf857c6f9 (commit)
via 58c958d4503a9a45be0267dc833c6e3be178dc14 (commit)
via 45466d27ef5c4f095c7d3bf23b4d0a4ad30a8bcb (commit)
via 24e0b47e82d29460f4dabfb66558204db9b3b83d (commit)
via 54d482bf70ce2878423df57efd6bbb8138805914 (commit)
via 002b5ff189dde721ac10f21d83aadb0b9e641295 (commit)
via 4c38d52144ef9a80ee8edb705d98ce6baaf4c378 (commit)
via 738540ce3a7b5d06707d5b298a6223629ab66050 (commit)
via 3b37e9c152b05606bb5401c16c5b9f6d37155539 (commit)
via 81ed5b0185f7b37aafada49c52880ba87b03785f (commit)
via 37a8e164d7e3267b42ad438f49ae2505bd5b952b (commit)
via e3bde86b2e51188e37fab58965709fe2f22f9610 (commit)
via 2992f269ed62c67a5fb60ad60c0f7dcd89fba957 (commit)
via a4aa18ebf42a9f94818519a5805e7caf7bff61c7 (commit)
via 2f061fe969a7af07cecd9cd5d166f5b2d5b327aa (commit)
via 1ea439fadbf39dc980cff8c8848994bdbeeb1e9a (commit)
via 6fbf5830da2a9ed728a865715bab0d59e1a5431b (commit)
via 79f0d62ef03b3774a0b0c6237eff78af821bdbc4 (commit)
via 3886d1174c6552cc99c3d5fa2c727c3ee0bcc281 (commit)
via fdbba41a48cffe7efc185b8eb59fd3471aeb78d5 (commit)
via d47e9e008a7eb572f370ab7b5527c8188bb24017 (commit)
via 3f1577b50df4375e51d94be2650ab2ae5e4d6698 (commit)
via 30276d77d28c951226859d2f1f452abfabdcf6f4 (commit)
via 2cecfb45e98d30493019d4664fc7ca320e377a2e (commit)
via f56d5006adee1ae27e38960d26e46b5fe488c965 (commit)
via fc075a50ee9a1d31c4851afc1f6df64c67951547 (commit)
via 6a2437122d5dddfc0ddfd923c5036331ca23fb12 (commit)
via f16d079d34dc61a5cea082328c3eb38beeea3d7c (commit)
via 45b03507d6ec3868648713f86e7789b15f78a367 (commit)
via 656b5d1cb045b35499ced064fae6bc1962f91ad6 (commit)
via 77c9f2b09e47184c42f56658dcd93c4eee36587a (commit)
via 2b81c6b347116512da7c653b0ca542a04b82015a (commit)
via 32a5e02895ad3faf098172a08c11ce4df535521e (commit)
via 2a337c756ad1064af1cbcaa8cd2c79be65f0b4d5 (commit)
via 28a20a3ee754d03ea90f13f76fa8e6d44c696b69 (commit)
via 2ad5c3c52b9106f68a6fb24a2ebfc85067877a04 (commit)
via 695d9fc3bc135de080369b0b7213aca3c264216c (commit)
via 5f6ae1f6c5c0f484c1e2f3073d713784cf3c5373 (commit)
via 4c2bed018107dea2091ac26f51e81d2f14f33582 (commit)
via f65147ed98354cb39e52568a6ff1f7f98ad885ff (commit)
via 1c59e1ee00a8d1ebabfabb592ee910b22c72f1d9 (commit)
via 63c3bee3545216695dacb5b06c52c90ab82f31ec (commit)
via 6b6cbde6a1ac6ad0f3f11923ee6633ced00d529b (commit)
via ae4b62b7f869cc7e66ef691ab20faff93566ac5b (commit)
via d7d8d1bf1a37b33891f09a2b3641c9bf5231d19d (commit)
via 38439b178eeddb7148df3da5ebab555b294cc967 (commit)
via 579f5abd033f56e772c6ee0286c58634d12aabfe (commit)
via 867d9a39ee23b4e02ef9d36d241688f0ad525e5d (commit)
via 1b32af4e5cb9349d34b468cd0333bbcfdb338d5a (commit)
via c61ae0fbbba155063ada04a08ec44f3dc27ab6bc (commit)
via be258adbecd48f1d46e47566f54843db1b579b9c (commit)
via 02a31879b8d8db032883d6e28d57c2369f2299cf (commit)
via df03141495a568971ff26d78a229beaa5b373e19 (commit)
via 808decc6c1639304e030a84d2bb2a04bd1b27db8 (commit)
via f4e272c729e2a77f720b06a22819013f6695f06b (commit)
via ff5861d1c7d7a689d775b251086d12032fcabd54 (commit)
via aae75525fe351f4df368402e7e05aa0e52b234b1 (commit)
via 5267d959d14763af2a7c938a136267655722cd77 (commit)
via 6a47ad417ae15c4cc9bad53347ae06088a642ec3 (commit)
via 1ba7dea264623db66cbf62fed95db34cde362cf5 (commit)
via b927f6e09523bcadf613b4e409a0039e5bfbb03c (commit)
via 0cce212fefe299518426d10812c74ce14d547782 (commit)
via 878a31b83ab5df9441804a67a2f9a16ad615ae84 (commit)
via c846c5417c22b4a3b02122860ce9a6977a6ffa44 (commit)
via ae4e532f39b85f619ff99d8feb91898d8ee741a9 (commit)
via ba27028c44e71d0fa19077209b412538be635c81 (commit)
via 9a7261d103fb09bf87b1f827a733aca3cb87d46c (commit)
via ff502308eef747bad13e1e4c763ca7fc2d820a99 (commit)
via 0e9831ebbe7ff8c719605c36f3c3292db31d28c8 (commit)
via d33133715b3f211459678061e0f2d3c40738c07f (commit)
via a6e58431e3582cc73e5c3e2aa20ee09c5da199a4 (commit)
via f2fa03f0b6156c8a64376d1524fe822e3a74cb16 (commit)
via 869ad53a069b3fe4836d818fe820160e914a017c (commit)
via e601cb8dbb5dae21aa90061d2dbf0e23223186c0 (commit)
via 76b963dc31b5df73631b3689868b991759414ba9 (commit)
via 5fa3d0e12c20cd9d07fc83c98dbd32c64eadca3e (commit)
via cd894167c8d367f7367b790d60c3dd23f740ffdf (commit)
via add399ede802585b4606e6b07d60fe831aa8ec7a (commit)
via 7773035d231a2a55664dbe04baa4744da4ada7a7 (commit)
via 8d160f47d4cd2b6bf265580331215fa9d48a9571 (commit)
via cd917b3660e1d944d749fb2fb19ce9dfb622c39c (commit)
via cc0c7a0a7b9b4abdb94b7c5a2007334b914218f8 (commit)
via e12dee22bbc97e49c7e5641cf50e39f64e5cbed2 (commit)
via 927c2a94869d76fdbb30018861ba235ddf9389f2 (commit)
via 6443e079d1d0dd3b0de766196a2f9b711bd4f4f5 (commit)
via 78f11bb1db706b793f2b45d94da3c613c2f8ca9e (commit)
via 08d9023ba335f084034ec31cf7e304964b267acb (commit)
via dc7e03f9cca07ef79b5741f4353f570f383dbb9b (commit)
via bc768d296fbd5b86b9d100672792605c79c81739 (commit)
via a9f24702174e022332b528b8d39a21c203b67d3e (commit)
via ef7776ea48e1ddbfb3b727836722cbfc8b65f4fa (commit)
via f098bc21fd0c528fdd55bdbd8f99f6fec36c9222 (commit)
via 607d55c17a1d1becedf6e0fb81673627cab763f8 (commit)
via 903087f3c9e778e762483cf11992e4f5ddc9b7d1 (commit)
via 65bbcd0970063c0923f597ee4413ccbcc969ba58 (commit)
via e8ed9fd77960f2e3c4cfc5948d356b47524d441c (commit)
via 31b16f3568b95073fc15b68e435215572f25c5b8 (commit)
via 6fe2c80c65634db3c5b9ef7fab2e2f48a507fc94 (commit)
via 87ff7feca9801dc6c78a5553c090819a66afe918 (commit)
via 48974a8f8351415321dcabf039296e9e7854fbe1 (commit)
via 652859d50201a5fd7d7ec3bc4266ead82fc86c4b (commit)
via aa70659de52c486695a6fc822b673adf51a2abcf (commit)
via c9e1f35632323f09a1e61f96f88df384e337f885 (commit)
via ad322965f290c5d594e084eb57f9bf16eff87717 (commit)
via ad6e16d25942d9bb308fcc1cf4ec839769956981 (commit)
via 2b861742dc6b8461984a1c1c0437b46a313f672b (commit)
via f0779391ff33e44afac4fc7911400d9b3f345446 (commit)
via a2e1d3f2d9f4bd9680932a46cf2b00c1cd2a1a64 (commit)
via 09a0594ddec459ff3d7bf1e0f0814847d5904883 (commit)
via a25a5fca02c5aca0a29113ca293581b1b48bcfdd (commit)
via 8e118d8e64ff03678c4dd4bdbc6e8e8c64737ac6 (commit)
via 13b8d3383b390dfb30ba39d9f7c69d1c0a7fd572 (commit)
via 7eaf1f34a92aef6e6e82b59e1a8200c858820b77 (commit)
via 45b538d01cc68a759ed8eb1d9e069def986e2ae9 (commit)
via c74f64796e9901c453ab7d4e509d736d037884fd (commit)
via 90f9161b787dfe68158c12bd07d2e362f2db8cc6 (commit)
via 99d1141934d6bb027bfd402110b5706aa114adaf (commit)
via 6f2e770cfbec4ffed1f314bed4e1b8b480c32c16 (commit)
via 0c8094b0e3e9ecae8fe44cb889b959ed467bac8b (commit)
via cf41749b421bd09330ca5cbec3e3e9a3cdae06d7 (commit)
via 02e63b13f8e523d913de3a516c442677dc15e4c6 (commit)
via 1e45b7161d9405b25072a4b6e979a18fa26981ce (commit)
via e78b80b2a929ab815dee2d4ff6c7e29315a5f2f7 (commit)
via 966b5ee5cf9512da8f4a4ea31f7f67ca3836df26 (commit)
via b95523d5350585a707693d680152903ede08bb0a (commit)
via 58771328093e3bb51463437cae9555da0ed945cc (commit)
via 17e0f403185b1e0136cf1059856c1f30a5bdfdec (commit)
via 1150b0ef4bae79e2ffe254dddd5863690a71d60f (commit)
via 65a2fbba5becb20d276aeb90e5d13d02190828c7 (commit)
via 32bb433cb895fd5013c050612abfe62fb09a689c (commit)
via 525dc51879ae8164ed085f5f6c48f8566853b230 (commit)
via 9a08bfa4933a435a4911cef029e63f57deed0dc3 (commit)
via e1aca7e9404c4064f0125b7596d67cbd8af932e5 (commit)
via f5dc6575c10281b39320167809689df16ae913cf (commit)
via 46dba3a3c60b81e609ee7ac2acc9641a8b62c9f3 (commit)
via 96ee507bbc179b6e3732114dfad67e6c5168c538 (commit)
via 3836cadb5368169a86a27e8af12374c8b715f5a5 (commit)
via 02e9061387eff505391762b5568c2e9700b021e5 (commit)
via b53ecd7c2508d159e92bf890f9d3b2e3747d053b (commit)
via a1fcd146a64717e59311eaeae9b97fb66857e636 (commit)
via 0e4b3df29c59a4b4cf4c75f37fd3c3180332e7fb (commit)
via 13c2eb59454d17b9d00c3cc7205ba19e8b69ec8f (commit)
via 8e0ebda2f002e9ef7530ac16ab7947104767d20d (commit)
via ad4eae2bc3410084b4ea07dd96118c7c1da17a25 (commit)
via 36df68ce895eefd601857fa5d8e1d701137fe218 (commit)
via a9a229b2314c336cf4dd7a257371231530dbae13 (commit)
via 48625915d6898ddca0eba5b3be512b1dcb29487b (commit)
via 0a10b2ed25935e83cd0402ae4df81e6d321d75e8 (commit)
via 78a4e4cd8735ad72dcaafe9701482c34d72fef3e (commit)
via d834a1a1b95f67b2ea2467b75e00fdfe9ac0c763 (commit)
via abd4fe9ffe48402cafdac9c1145f25c43da62332 (commit)
via 2c8b5b294bf5498b9525dcc1dcba7ca1869f01b6 (commit)
via c6aa9a4c2e17c51b28b91cef5b773b935caf317e (commit)
via 7dee66ffe8b7e3f5c49c20608f0f38508d861ced (commit)
via 117645fa11500c467045a380dc41d6999518c5dd (commit)
via 0ca61cffc9fb7f55789f1db88a41a9a5c1f5d34f (commit)
via 83e4b764a988e7774312ff75e0623bcff66f7009 (commit)
via 69bc301dec66ffd80960a45c882fd50c498121cc (commit)
via 2ba2f0269087e5dae4640091e711e4f2964d2fa3 (commit)
via 2f29fad03bb3c9885d0bcf84d39102e6ee902830 (commit)
via a52d10a93a24f736ed4112ea76778a61407a0a03 (commit)
via 123f5a29a7d5c40b0ca7a6ebe8af3b3790c29eca (commit)
via 525ca6149da521a00d02895a5184deef7f8359f9 (commit)
via 889dc09ad441f3e5a3ce2170fba402db258c2a08 (commit)
via 23818a5f2f8e5c68300b11ba8e71178dee8ad794 (commit)
via 949f8945514ac7d422a68f34d7cbf097c864bef8 (commit)
via 57e46e4378c4d8b8dd39cca093500672fa124e42 (commit)
via b89a21c32f1dbea865f632cf517cc335b9b70919 (commit)
via e0098c29f519e84214d012cd5d0ae7f25aa40f73 (commit)
via 977568b91a6595c7d2fd8a618d5f15783962d908 (commit)
via e5e001e23347199841f14f2c0880e2f5f304bd0f (commit)
via 8f73baf041ffda0351e5b9072e1ea43401ce497e (commit)
via c38d780b1390e3dc4c3bf702f6c059a8c3de3406 (commit)
via 5cc65af294ab05bba59614684d71d450eed57c37 (commit)
via 74aa99552605bb7639719501807a13c393e560b9 (commit)
via f950449fc2ffc2e31672ab99d0e432c3b3efc4bc (commit)
via 62c7075662a25c06ee6d877404b8ddad16dd41ee (commit)
via 7c00e40c1c815ea1b8dd13cc718772140cf575dc (commit)
via 96edf56fbab807319a975fe7df264ace0c5dbb37 (commit)
via 1c6a6aa89ebb3b512a09ad19609f3682b198993a (commit)
from 8765402aa09dbbf42b7cf3e8449a633a850221c4 (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 7bed043f816679ee3ee3c6d75202d47b1531b37f
Merge: 8765402 8b05639
Author: Stephen Morris <stephen at isc.org>
Date: Wed Jan 23 10:25:12 2013 +0000
[2642] Merge branch 'master' into trac2642
Conflicts:
doc/guide/bind10-guide.xml
-----------------------------------------------------------------------
Summary of changes:
ChangeLog | 59 +-
configure.ac | 9 +-
doc/guide/bind10-guide.xml | 45 ++
.../auth/tests/datasrc_clients_builder_unittest.cc | 3 +-
src/bin/bind10/bind10_src.py.in | 2 +
src/bin/bindctl/run_bindctl.sh.in | 5 +-
src/bin/dbutil/run_dbutil.sh.in | 5 +-
src/bin/dhcp4/config_parser.cc | 178 ++++--
src/bin/dhcp4/ctrl_dhcp4_srv.cc | 70 ++-
src/bin/dhcp4/ctrl_dhcp4_srv.h | 21 +
src/bin/dhcp4/dhcp4.spec | 52 +-
src/bin/dhcp4/dhcp4_messages.mes | 52 ++
src/bin/dhcp4/dhcp4_srv.cc | 126 +++-
src/bin/dhcp4/dhcp4_srv.h | 36 +-
src/bin/dhcp4/tests/config_parser_unittest.cc | 494 ++++++++++++++-
src/bin/dhcp4/tests/dhcp4_srv_unittest.cc | 45 +-
src/bin/dhcp6/config_parser.cc | 194 ++++--
src/bin/dhcp6/ctrl_dhcp6_srv.cc | 86 ++-
src/bin/dhcp6/ctrl_dhcp6_srv.h | 27 +-
src/bin/dhcp6/dhcp6.spec | 58 +-
src/bin/dhcp6/dhcp6_messages.mes | 65 ++
src/bin/dhcp6/dhcp6_srv.cc | 114 +++-
src/bin/dhcp6/dhcp6_srv.h | 51 +-
src/bin/dhcp6/main.cc | 15 +-
src/bin/dhcp6/tests/config_parser_unittest.cc | 655 +++++++++++++++++---
src/bin/dhcp6/tests/dhcp6_srv_unittest.cc | 172 ++++-
src/bin/loadzone/loadzone.py.in | 55 +-
src/bin/loadzone/loadzone_messages.mes | 11 +-
src/bin/loadzone/tests/loadzone_test.py | 41 +-
src/bin/msgq/msgq.py.in | 13 +-
src/bin/stats/tests/b10-stats-httpd_test.py | 2 +
src/bin/stats/tests/b10-stats_test.py | 7 +-
src/bin/stats/tests/test_utils.py | 37 +-
src/bin/sysinfo/run_sysinfo.sh.in | 9 +-
src/lib/cc/data.cc | 119 ++--
src/lib/cc/tests/data_unittests.cc | 4 +
src/lib/datasrc/memory/Makefile.am | 1 +
src/lib/datasrc/memory/memory_messages.mes | 13 +
src/lib/datasrc/memory/rrset_collection.cc | 57 ++
src/lib/datasrc/memory/rrset_collection.h | 85 +++
src/lib/datasrc/memory/zone_data_loader.cc | 32 +-
src/lib/datasrc/memory/zone_data_loader.h | 8 +-
src/lib/datasrc/sqlite3_accessor_link.cc | 8 +-
src/lib/datasrc/tests/client_list_unittest.cc | 70 ++-
src/lib/datasrc/tests/memory/Makefile.am | 1 +
.../datasrc/tests/memory/memory_client_unittest.cc | 43 +-
.../tests/memory/rrset_collection_unittest.cc | 88 +++
src/lib/datasrc/tests/memory/testdata/Makefile.am | 1 +
.../testdata/example.org-duplicate-type.zone | 3 +-
.../tests/memory/testdata/example.org-empty.zone | 3 +-
.../memory/testdata/example.org-multiple.zone | 3 +-
.../example.org-rrsig-follows-nothing.zone | 3 +-
.../tests/memory/testdata/example.org-rrsigs.zone | 3 +-
.../tests/memory/testdata/rrset-collection.zone | 4 +
src/lib/datasrc/tests/zone_loader_unittest.cc | 182 +++++-
src/lib/datasrc/zone_loader.cc | 67 +-
src/lib/datasrc/zone_loader.h | 69 ++-
src/lib/dhcp/Makefile.am | 1 +
src/lib/dhcp/libdhcp++.cc | 75 ++-
src/lib/dhcp/libdhcp++.h | 10 -
src/lib/dhcp/option.cc | 62 +-
src/lib/dhcp/option.h | 24 +-
src/lib/dhcp/option4_addrlst.cc | 2 +-
src/lib/dhcp/option4_addrlst.h | 5 +-
src/lib/dhcp/option_custom.cc | 25 +-
src/lib/dhcp/option_custom.h | 22 +-
src/lib/dhcp/option_definition.cc | 53 +-
src/lib/dhcp/option_definition.h | 66 +-
src/lib/{dhcpsrv => dhcp}/option_space.cc | 2 +-
src/lib/{dhcpsrv => dhcp}/option_space.h | 0
src/lib/dhcp/pkt4.cc | 11 +-
src/lib/dhcp/pkt6.cc | 6 +-
src/lib/dhcp/std_option_defs.h | 244 ++++----
src/lib/dhcp/tests/Makefile.am | 1 +
src/lib/dhcp/tests/libdhcp++_unittest.cc | 36 +-
src/lib/dhcp/tests/option4_addrlst_unittest.cc | 4 +-
src/lib/dhcp/tests/option_definition_unittest.cc | 51 +-
.../tests/option_space_unittest.cc | 2 +-
src/lib/dhcp/tests/option_unittest.cc | 14 +-
src/lib/dhcpsrv/.gitignore | 2 +
src/lib/dhcpsrv/Makefile.am | 4 +-
src/lib/dhcpsrv/alloc_engine.h | 4 +-
src/lib/dhcpsrv/cfgmgr.cc | 32 +-
src/lib/dhcpsrv/cfgmgr.h | 23 +-
src/lib/dhcpsrv/dbaccess_parser.cc | 116 ++++
src/lib/dhcpsrv/dbaccess_parser.h | 133 ++++
src/lib/dhcpsrv/dhcp_config_parser.h | 3 +
src/lib/dhcpsrv/dhcpsrv_messages.mes | 30 +-
src/lib/dhcpsrv/lease_mgr.h | 14 +-
src/lib/dhcpsrv/lease_mgr_factory.cc | 14 +-
src/lib/dhcpsrv/memfile_lease_mgr.cc | 5 +-
src/lib/dhcpsrv/option_space_container.h | 2 +-
src/lib/dhcpsrv/subnet.cc | 10 +
src/lib/dhcpsrv/subnet.h | 15 +
src/lib/dhcpsrv/tests/Makefile.am | 4 +-
src/lib/dhcpsrv/tests/cfgmgr_unittest.cc | 4 +
src/lib/dhcpsrv/tests/dbaccess_parser_unittest.cc | 434 +++++++++++++
src/lib/dhcpsrv/tests/subnet_unittest.cc | 11 +
src/lib/dns/master_lexer.cc | 35 +-
src/lib/dns/master_lexer.h | 42 +-
src/lib/dns/master_loader.cc | 19 +-
src/lib/dns/master_loader.h | 42 ++
src/lib/dns/python/rrset_collection_python.cc | 73 ++-
src/lib/dns/python/rrset_collection_python.h | 10 +-
src/lib/dns/python/rrset_collection_python_inc.cc | 12 +-
src/lib/dns/rrset_collection_base.h | 2 +
src/lib/dns/tests/master_lexer_unittest.cc | 37 +-
src/lib/dns/tests/master_loader_unittest.cc | 69 ++-
src/lib/python/isc/Makefile.am | 2 +-
src/lib/python/isc/datasrc/datasrc.cc | 28 +-
src/lib/python/isc/datasrc/tests/datasrc_test.py | 35 ++
.../python/isc/datasrc/tests/zone_loader_test.py | 34 +-
src/lib/python/isc/datasrc/updater_inc.cc | 36 ++
src/lib/python/isc/datasrc/updater_python.cc | 154 +++++
src/lib/python/isc/datasrc/updater_python.h | 2 +-
src/lib/python/isc/datasrc/zone_loader_inc.cc | 60 ++
src/lib/python/isc/datasrc/zone_loader_python.cc | 16 +
src/lib/python/isc/statistics/Makefile.am | 9 +
src/lib/python/isc/statistics/__init__.py | 1 +
src/lib/python/isc/statistics/counters.py | 403 ++++++++++++
.../python/isc/statistics}/tests/Makefile.am | 15 +-
.../python/isc/statistics/tests/counters_test.py | 416 +++++++++++++
.../isc/statistics/tests/testdata/test_spec1.spec | 26 +
.../isc/statistics/tests/testdata/test_spec2.spec | 187 ++++++
.../isc/statistics/tests/testdata/test_spec3.spec | 382 ++++++++++++
tests/system/ifconfig.sh | 4 +-
tests/tools/perfdhcp/command_options.cc | 2 +-
127 files changed, 6488 insertions(+), 922 deletions(-)
create mode 100644 src/lib/datasrc/memory/rrset_collection.cc
create mode 100644 src/lib/datasrc/memory/rrset_collection.h
create mode 100644 src/lib/datasrc/tests/memory/rrset_collection_unittest.cc
create mode 100644 src/lib/datasrc/tests/memory/testdata/rrset-collection.zone
rename src/lib/{dhcpsrv => dhcp}/option_space.cc (98%)
rename src/lib/{dhcpsrv => dhcp}/option_space.h (100%)
rename src/lib/{dhcpsrv => dhcp}/tests/option_space_unittest.cc (99%)
create mode 100644 src/lib/dhcpsrv/.gitignore
create mode 100644 src/lib/dhcpsrv/dbaccess_parser.cc
create mode 100644 src/lib/dhcpsrv/dbaccess_parser.h
create mode 100644 src/lib/dhcpsrv/tests/dbaccess_parser_unittest.cc
create mode 100644 src/lib/python/isc/statistics/Makefile.am
create mode 100644 src/lib/python/isc/statistics/__init__.py
create mode 100644 src/lib/python/isc/statistics/counters.py
copy src/{bin/xfrin => lib/python/isc/statistics}/tests/Makefile.am (75%)
create mode 100644 src/lib/python/isc/statistics/tests/counters_test.py
create mode 100644 src/lib/python/isc/statistics/tests/testdata/test_spec1.spec
create mode 100644 src/lib/python/isc/statistics/tests/testdata/test_spec2.spec
create mode 100644 src/lib/python/isc/statistics/tests/testdata/test_spec3.spec
-----------------------------------------------------------------------
diff --git a/ChangeLog b/ChangeLog
index c999ea6..19d52a2 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,4 +1,61 @@
-248. [func] vorner
+556. [bug] marcin
+ Fixed DHCP servers configuration whereby the servers did not
+ receive a configuration stored in the database on their startup.
+ Also, the configuration handler function now uses full configuration
+ instead of partial to configure the server. This guarantees that
+ dependencies between various configuration parameters are
+ fulfilled.
+ (Trac #2637, git 91aa998226f1f91a232f2be59a53c9568c4ece77)
+
+555. [func] marcin
+ The encapsulated option space name can be specified for
+ a DHCP option. It comprises sub-options being sent within
+ an option that encapsulates this option space.
+ (Trac #2314, git 27e6119093723a1e46a239ec245a8b4b10677635)
+
+554. [func] jinmei
+ b10-loadzone: improved completion log message and intermediate
+ reports: It now logs the precise number of loaded RRs on
+ completion, and intermediate reports show additional information
+ such as the estimated progress in percentage and estimated time
+ to complete.
+ (Trac #2574, git 5b8a824054313bdecb8988b46e55cb2e94cb2d6c)
+
+553. [func] stephen
+ Values of the parameters to access the DHCP server lease database
+ can now be set through the BIND 10 configuration mechanism.
+ (Trac #2559, git 6c6f405188cc02d2358e114c33daff58edabd52a)
+
+552. [bug] shane
+ Build on Raspberry PI.
+ The main issue was use of char for reading from input streams,
+ which is incorrect, as EOF is returned as an int -1, which would
+ then get cast into a char -1.
+ A number of other minor issues were also fixed.
+ (Trac #2571, git 525333e187cc4bbbbde288105c9582c1024caa4a)
+
+551. [bug] shane
+ Kill msgq if we cannot connect to it on startup.
+ When the boss process was unable to connect to the msgq, it would
+ exit. However, it would leave the msgq process running. This has
+ been fixed, and the msgq is now stopped in this case.
+ (Trac #2608, git 016925ef2437e0396127e135c937d3a55539d224)
+
+550. [func] tomek
+ b10-dhcp4: The DHCPv4 server now generates a server identifier
+ the first time it is run. The identifier is preserved in a file
+ across server restarts.
+ b10-dhcp6: The server identifier is now preserved in a file across
+ server restarts.
+ (Trac #2597, git fa342a994de5dbefe32996be7eebe58f6304cff7)
+
+549. [func] tomek
+ b10-dhcp6: It is now possible to specify that a configured subnet
+ is reachable locally over specified interface (see "interface"
+ parameter in Subnet6 configuration).
+ (Trac #2596, git a70f6172194a976b514cd7d67ce097bbca3c2798)
+
+548. [func] vorner
The message queue daemon now appears on the bus. This has two
effects, one is it obeys logging configuration and logs to the
correct place like the rest of the modules. The other is it
diff --git a/configure.ac b/configure.ac
index 892bc6c..6d1a388 100644
--- a/configure.ac
+++ b/configure.ac
@@ -232,7 +232,7 @@ AM_CONDITIONAL(SET_ENV_LIBRARY_PATH, test $SET_ENV_LIBRARY_PATH = yes)
AC_SUBST(SET_ENV_LIBRARY_PATH)
AC_SUBST(ENV_LIBRARY_PATH)
-m4_define([_AM_PYTHON_INTERPRETER_LIST], [python python3.2 python3.1 python3])
+m4_define([_AM_PYTHON_INTERPRETER_LIST], [python python3.3 python3.2 python3.1 python3])
AC_ARG_WITH([pythonpath],
AC_HELP_STRING([--with-pythonpath=PATH],
[specify an absolute path to python executable when automatic version check (incorrectly) fails]),
@@ -282,7 +282,10 @@ AC_SUBST(PYTHON_LOGMSGPKG_DIR)
# This is python package paths commonly used in python tests. See
# README of log_messages for why it's included.
-COMMON_PYTHON_PATH="\$(abs_top_builddir)/src/lib/python/isc/log_messages:\$(abs_top_srcdir)/src/lib/python:\$(abs_top_builddir)/src/lib/python"
+# lib/dns/python/.libs is necessary because __init__.py of isc package
+# automatically imports isc.datasrc, which then requires the DNS loadable
+# module. #2145 should eliminate the need for it.
+COMMON_PYTHON_PATH="\$(abs_top_builddir)/src/lib/python/isc/log_messages:\$(abs_top_srcdir)/src/lib/python:\$(abs_top_builddir)/src/lib/python:\$(abs_top_builddir)/src/lib/dns/python/.libs"
AC_SUBST(COMMON_PYTHON_PATH)
# Check for python development environments
@@ -1214,6 +1217,8 @@ AC_CONFIG_FILES([Makefile
src/lib/python/isc/server_common/tests/Makefile
src/lib/python/isc/sysinfo/Makefile
src/lib/python/isc/sysinfo/tests/Makefile
+ src/lib/python/isc/statistics/Makefile
+ src/lib/python/isc/statistics/tests/Makefile
src/lib/config/Makefile
src/lib/config/tests/Makefile
src/lib/config/tests/testdata/Makefile
diff --git a/doc/guide/bind10-guide.xml b/doc/guide/bind10-guide.xml
index 804761e..37934cd 100644
--- a/doc/guide/bind10-guide.xml
+++ b/doc/guide/bind10-guide.xml
@@ -3611,6 +3611,26 @@ Dhcp4/subnet4 [] list (default)
</section>
</section>
+ <section id="dhcp4-serverid">
+ <title>Server Identifier in DHCPv4</title>
+ <para>The DHCPv4 protocol uses a "server identifier" for clients to be able
+ to discriminate between several servers present on the same link: this
+ value is an IPv4 address of the server. When started for the first time,
+ the DHCPv4 server will choose one of its IPv4 addresses as its server-id,
+ and store the chosen value to a file. (The file is named b10-dhcp4-serverid and is
+ stored in the "local state directory". This is set during installation
+ when "configure" is run, and can be changed by using "--localstatedir"
+ on the "configure" command line.) That file will be read by the server
+ and the contained value used whenever the server is subsequently started.
+ </para>
+ <para>
+ It is unlikely that this parameter needs to be changed. If such a need
+ arises, please stop the server, edit the file and restart the server.
+ It is a text file that should contain an IPv4 address. Spaces are
+ ignored. No extra characters are allowed in this file.
+ </para>
+ </section>
+
<section id="dhcp4-std">
<title>Supported Standards</title>
<para>The following standards and draft standards are currently
@@ -3978,6 +3998,31 @@ Dhcp6/subnet6/ list
</section>
+ <section id="dhcp6-serverid">
+ <title>Server Identifier in DHCPv6</title>
+ <para>The DHCPv6 protocol uses a "server identifier" (also known
+ as a DUID) for clients to be able to discriminate between several
+ servers present on the same link. There are several types of
+ DUIDs defined, but RFC 3315 instructs servers to use DUID-LLT if
+ possible. This format consists of a link-layer (MAC) address and a
+ timestamp. When started for the first time, the DHCPv6 server will
+ automatically generate such a DUID and store the chosen value to
+ a file (The file is named b10-dhcp6-serverid and is stored in the
+ "local state directory". This is set during installation when
+ "configure" is run, and can be changed by using "--localstatedir"
+ on the "configure" command line.) That file will be read by the server
+ and the contained value used whenever the server is subsequently started.
+ </para>
+ <para>
+ It is unlikely that this parameter needs to be changed. If such a need
+ arises, please stop the server, edit the file and start the server
+ again. It is a text file that contains double digit hexadecimal values
+ separated by colons. This format is similar to typical MAC address
+ format. Spaces are ignored. No extra characters are allowed in this
+ file.
+ </para>
+ </section>
+
<section id="dhcp6-std">
<title>Supported DHCPv6 Standards</title>
<para>The following standards and draft standards are currently
diff --git a/src/bin/auth/tests/datasrc_clients_builder_unittest.cc b/src/bin/auth/tests/datasrc_clients_builder_unittest.cc
index 838e032..01ac47e 100644
--- a/src/bin/auth/tests/datasrc_clients_builder_unittest.cc
+++ b/src/bin/auth/tests/datasrc_clients_builder_unittest.cc
@@ -328,7 +328,8 @@ TEST_F(DataSrcClientsBuilderTest,
{
// Prepare the database first
const std::string test_db = TEST_DATA_BUILDDIR "/auth_test.sqlite3.copied";
- std::stringstream ss("example.org. 3600 IN SOA . . 0 0 0 0 0\n");
+ std::stringstream ss("example.org. 3600 IN SOA . . 0 0 0 0 0\n"
+ "example.org. 3600 IN NS ns1.example.org.\n");
createSQLite3DB(rrclass, Name("example.org"), test_db.c_str(), ss);
// This describes the data source in the configuration
const ConstElementPtr config(Element::fromJSON("{"
diff --git a/src/bin/bind10/bind10_src.py.in b/src/bin/bind10/bind10_src.py.in
index 6fe5485..9f41804 100755
--- a/src/bin/bind10/bind10_src.py.in
+++ b/src/bin/bind10/bind10_src.py.in
@@ -491,6 +491,8 @@ class BoB:
# if we have been trying for "a while" give up
if (time.time() - cc_connect_start) > self.msgq_timeout:
+ if msgq_proc.process:
+ msgq_proc.process.kill()
logger.error(BIND10_CONNECTING_TO_CC_FAIL)
raise CChannelConnectError("Unable to connect to c-channel after 5 seconds")
diff --git a/src/bin/bindctl/run_bindctl.sh.in b/src/bin/bindctl/run_bindctl.sh.in
index f4cc40c..999d7ee 100755
--- a/src/bin/bindctl/run_bindctl.sh.in
+++ b/src/bin/bindctl/run_bindctl.sh.in
@@ -20,7 +20,10 @@ export PYTHON_EXEC
BINDCTL_PATH=@abs_top_builddir@/src/bin/bindctl
-PYTHONPATH=@abs_top_srcdir@/src/bin:@abs_top_builddir@/src/lib/python/isc/log_messages:@abs_top_builddir@/src/lib/python:@abs_top_builddir@/src/bin:@abs_top_srcdir@/src/lib/python
+# Note: lib/dns/python/.libs is necessary because __init__.py of isc package
+# automatically imports isc.datasrc, which then requires the DNS loadable
+# module. #2145 should eliminate the need for it.
+PYTHONPATH=@abs_top_srcdir@/src/bin:@abs_top_builddir@/src/lib/python/isc/log_messages:@abs_top_builddir@/src/lib/python:@abs_top_builddir@/src/bin:@abs_top_srcdir@/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/dbutil/run_dbutil.sh.in b/src/bin/dbutil/run_dbutil.sh.in
index fea7482..f0c6dbd 100755
--- a/src/bin/dbutil/run_dbutil.sh.in
+++ b/src/bin/dbutil/run_dbutil.sh.in
@@ -20,7 +20,10 @@ export PYTHON_EXEC
DBUTIL_PATH=@abs_top_builddir@/src/bin/dbutil
-PYTHONPATH=@abs_top_srcdir@/src/bin:@abs_top_builddir@/src/lib/python/isc/log_messages:@abs_top_builddir@/src/lib/python:@abs_top_builddir@/src/bin:@abs_top_srcdir@/src/lib/python
+# Note: lib/dns/python/.libs is necessary because __init__.py of isc package
+# automatically imports isc.datasrc, which then requires the DNS loadable
+# module. #2145 should eliminate the need for it.
+PYTHONPATH=@abs_top_srcdir@/src/bin:@abs_top_builddir@/src/lib/python/isc/log_messages:@abs_top_builddir@/src/lib/python:@abs_top_builddir@/src/bin:@abs_top_srcdir@/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/dhcp4/config_parser.cc b/src/bin/dhcp4/config_parser.cc
index fdb4240..916bdad 100644
--- a/src/bin/dhcp4/config_parser.cc
+++ b/src/bin/dhcp4/config_parser.cc
@@ -18,6 +18,7 @@
#include <dhcp/libdhcp++.h>
#include <dhcp/option_definition.h>
#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/dbaccess_parser.h>
#include <dhcpsrv/dhcp_config_parser.h>
#include <dhcpsrv/option_space_container.h>
#include <util/encode/hex.h>
@@ -49,9 +50,6 @@ typedef boost::shared_ptr<BooleanParser> BooleanParserPtr;
typedef boost::shared_ptr<StringParser> StringParserPtr;
typedef boost::shared_ptr<Uint32Parser> Uint32ParserPtr;
-/// @brief auxiliary type used for storing element name and its parser
-typedef pair<string, ConstElementPtr> ConfigPair;
-
/// @brief a factory method that will create a parser for a given element name
typedef isc::dhcp::DhcpConfigParser* ParserFactory(const std::string& config_id);
@@ -745,30 +743,34 @@ private:
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.
+ // 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, "Parser error: value of 'code' must not"
- << " be equal to zero. Option code '0' is reserved in"
- << " DHCPv4.");
- } else if (option_code > std::numeric_limits<uint16_t>::max()) {
- isc_throw(DhcpConfigError, "Parser error: value of 'code' must not"
- << " exceed " << std::numeric_limits<uint16_t>::max());
+ 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.
- // @todo possibly some more restrictions apply here?
std::string option_name = getParam<std::string>("name", string_values_);
if (option_name.empty()) {
- isc_throw(DhcpConfigError, "Parser error: option name must not be"
- << " 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, "Parser error: option name must not contain"
- << " spaces");
+ isc_throw(DhcpConfigError, "invalid option name '" << option_name
+ << "', space character is not allowed");
}
std::string option_space = getParam<std::string>("space", string_values_);
- /// @todo Validate option space once #2313 is merged.
+ if (!OptionSpace::validateName(option_space)) {
+ isc_throw(DhcpConfigError, "invalid option space name '"
+ << option_space << "' specified for option '"
+ << option_name << "' (code '" << option_code
+ << "')");
+ }
OptionDefinitionPtr def;
if (option_space == "dhcp4" &&
@@ -822,7 +824,7 @@ private:
try {
util::encode::decodeHex(option_data, binary);
} catch (...) {
- isc_throw(DhcpConfigError, "Parser error: option data is not a valid"
+ isc_throw(DhcpConfigError, "option data is not a valid"
<< " string of hexadecimal digits: " << option_data);
}
}
@@ -857,7 +859,7 @@ private:
// definition of option value makes sense.
if (def->getName() != option_name) {
isc_throw(DhcpConfigError, "specified option name '"
- << option_name << " does not match the "
+ << option_name << "' does not match the "
<< "option definition: '" << option_space
<< "." << def->getName() << "'");
}
@@ -877,6 +879,7 @@ private:
<< ", code: " << option_code << "): "
<< ex.what());
}
+
}
// All went good, so we can set the option space name.
option_space_ = option_space;
@@ -978,7 +981,7 @@ public:
/// @brief Parser for a single option definition.
///
/// This parser creates an instance of a single option definition.
-class OptionDefParser: DhcpConfigParser {
+class OptionDefParser : public DhcpConfigParser {
public:
/// @brief Constructor.
@@ -1005,7 +1008,8 @@ public:
std::string entry(param.first);
ParserPtr parser;
if (entry == "name" || entry == "type" ||
- entry == "record-types" || entry == "space") {
+ entry == "record-types" || entry == "space" ||
+ entry == "encapsulate") {
StringParserPtr
str_parser(dynamic_cast<StringParser*>(StringParser::factory(entry)));
if (str_parser) {
@@ -1055,8 +1059,8 @@ public:
/// @brief Stores the parsed option definition in a storage.
void commit() {
- // @todo validate option space name once 2313 is merged.
- if (storage_ && option_definition_) {
+ if (storage_ && option_definition_ &&
+ OptionSpace::validateName(option_space_name_)) {
storage_->addItem(option_definition_, option_space_name_);
}
}
@@ -1078,11 +1082,10 @@ private:
void createOptionDef() {
// Get the option space name and validate it.
std::string space = getParam<std::string>("space", string_values_);
- // @todo uncomment the code below when the #2313 is merged.
- /* if (!OptionSpace::validateName()) {
+ if (!OptionSpace::validateName(space)) {
isc_throw(DhcpConfigError, "invalid option space name '"
<< space << "'");
- } */
+ }
// Get other parameters that are needed to create the
// option definition.
@@ -1090,9 +1093,35 @@ private:
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 << "'");
- OptionDefinitionPtr def(new OptionDefinition(name, code,
- type, array_type));
+ } 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",
@@ -1110,7 +1139,7 @@ private:
}
} catch (const Exception& ex) {
isc_throw(DhcpConfigError, "invalid record type values"
- << " specified for the option definition: "
+ << " specified for the option definition: "
<< ex.what());
}
}
@@ -1332,6 +1361,63 @@ private:
return (false);
}
+ /// @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 Create a new subnet using a data from child parsers.
///
/// @throw isc::dhcp::DhcpConfigError if subnet configuration parsing failed.
@@ -1407,6 +1493,8 @@ private:
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);
}
@@ -1434,6 +1522,9 @@ private:
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);
}
}
@@ -1607,6 +1698,7 @@ DhcpConfigParser* createGlobalDhcp4ConfigParser(const std::string& config_id) {
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()) {
@@ -1661,13 +1753,18 @@ configureDhcp4Server(Dhcpv4Srv&, ConstElementPtr config_set) {
// 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
+ // the parsers. It is declared outside the loops so in case of an error,
+ // the name of the failing parser can be retrieved in the "catch" clause.
+ ConfigPair config_pair;
try {
// Make parsers grouping.
const std::map<std::string, ConstElementPtr>& values_map =
config_set->mapValue();
- BOOST_FOREACH(ConfigPair config_pair, values_map) {
+ 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;
@@ -1702,16 +1799,19 @@ configureDhcp4Server(Dhcpv4Srv&, ConstElementPtr config_set) {
}
} catch (const isc::Exception& ex) {
- answer =
- isc::config::createAnswer(1, string("Configuration parsing failed: ") + ex.what());
+ LOG_ERROR(dhcp4_logger, DHCP4_PARSER_FAIL)
+ .arg(config_pair.first).arg(ex.what());
+ answer = isc::config::createAnswer(1,
+ string("Configuration parsing failed: ") + ex.what());
// An error occured, so make sure that we restore original data.
rollback = true;
} catch (...) {
// for things like bad_cast in boost::lexical_cast
- answer =
- isc::config::createAnswer(1, string("Configuration parsing failed"));
+ LOG_ERROR(dhcp4_logger, DHCP4_PARSER_EXCEPTION).arg(config_pair.first);
+ answer = isc::config::createAnswer(1,
+ string("Configuration parsing failed"));
// An error occured, so make sure that we restore original data.
rollback = true;
@@ -1728,14 +1828,16 @@ configureDhcp4Server(Dhcpv4Srv&, ConstElementPtr config_set) {
}
}
catch (const isc::Exception& ex) {
- answer =
- isc::config::createAnswer(2, string("Configuration commit failed: ") + ex.what());
+ LOG_ERROR(dhcp4_logger, DHCP4_PARSER_COMMIT_FAIL).arg(ex.what());
+ answer = isc::config::createAnswer(2,
+ string("Configuration commit failed: ") + ex.what());
rollback = true;
} catch (...) {
// for things like bad_cast in boost::lexical_cast
- answer =
- isc::config::createAnswer(2, string("Configuration commit failed"));
+ LOG_ERROR(dhcp4_logger, DHCP4_PARSER_COMMIT_EXCEPTION);
+ answer = isc::config::createAnswer(2,
+ string("Configuration commit failed"));
rollback = true;
}
diff --git a/src/bin/dhcp4/ctrl_dhcp4_srv.cc b/src/bin/dhcp4/ctrl_dhcp4_srv.cc
index 435a25e..8ac49d3 100644
--- a/src/bin/dhcp4/ctrl_dhcp4_srv.cc
+++ b/src/bin/dhcp4/ctrl_dhcp4_srv.cc
@@ -47,18 +47,61 @@ namespace dhcp {
ControlledDhcpv4Srv* ControlledDhcpv4Srv::server_ = NULL;
ConstElementPtr
+ControlledDhcpv4Srv::dhcp4StubConfigHandler(ConstElementPtr) {
+ // This configuration handler is intended to be used only
+ // when the initial configuration comes in. To receive this
+ // configuration a pointer to this handler must be passed
+ // using ModuleCCSession constructor. This constructor will
+ // invoke the handler and will store the configuration for
+ // the configuration session when the handler returns success.
+ // Since this configuration is partial we just pretend to
+ // parse it and always return success. The function that
+ // initiates the session must get the configuration on its
+ // own using getFullConfig.
+ return (isc::config::createAnswer(0, "Configuration accepted."));
+}
+
+ConstElementPtr
ControlledDhcpv4Srv::dhcp4ConfigHandler(ConstElementPtr new_config) {
- LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND, DHCP4_CONFIG_UPDATE)
- .arg(new_config->str());
- if (server_) {
- return (configureDhcp4Server(*server_, new_config));
+ if (!server_ || !server_->config_session_) {
+ // That should never happen as we install config_handler
+ // after we instantiate the server.
+ ConstElementPtr answer =
+ isc::config::createAnswer(1, "Configuration rejected,"
+ " server is during startup/shutdown phase.");
+ return (answer);
}
- // That should never happen as we install config_handler after we instantiate
- // the server.
- ConstElementPtr answer = isc::config::createAnswer(1,
- "Configuration rejected, server is during startup/shutdown phase.");
- return (answer);
+ // The configuration passed to this handler function is partial.
+ // In other words, it just includes the values being modified.
+ // In the same time, there are dependencies between various
+ // DHCP configuration parsers. For example: the option value can
+ // be set if the definition of this option is set. If someone removes
+ // an existing option definition then the partial configuration that
+ // removes that definition is triggered while a relevant option value
+ // may remain configured. This eventually results in the DHCP server
+ // configuration being in the inconsistent state.
+ // In order to work around this problem we need to merge the new
+ // configuration with the existing (full) configuration.
+
+ // Let's create a new object that will hold the merged configuration.
+ boost::shared_ptr<MapElement> merged_config(new MapElement());
+ // Let's get the existing configuration.
+ ConstElementPtr full_config = server_->config_session_->getFullConfig();
+ // The full_config and merged_config should be always non-NULL
+ // but to provide some level of exception safety we check that they
+ // really are (in case we go out of memory).
+ if (full_config && merged_config) {
+ merged_config->setValue(full_config->mapValue());
+
+ // Merge an existing and new configuration.
+ isc::data::merge(merged_config, new_config);
+ LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND, DHCP4_CONFIG_UPDATE)
+ .arg(full_config->str());
+ }
+
+ // Configure the server.
+ return (configureDhcp4Server(*server_, merged_config));
}
ConstElementPtr
@@ -109,8 +152,15 @@ void ControlledDhcpv4Srv::establishSession() {
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_CCSESSION_STARTING)
.arg(specfile);
cc_session_ = new Session(io_service_.get_io_service());
+ // Create a session with the dummy configuration handler.
+ // Dumy configuration handler is internally invoked by the
+ // constructor and on success the constructor updates
+ // the current session with the configuration that had been
+ // commited in the previous session. If we did not install
+ // the dummy handler, the previous configuration would have
+ // been lost.
config_session_ = new ModuleCCSession(specfile, *cc_session_,
- NULL,
+ dhcp4StubConfigHandler,
dhcp4CommandHandler, false);
config_session_->start();
diff --git a/src/bin/dhcp4/ctrl_dhcp4_srv.h b/src/bin/dhcp4/ctrl_dhcp4_srv.h
index 9bd261c..ab4d45a 100644
--- a/src/bin/dhcp4/ctrl_dhcp4_srv.h
+++ b/src/bin/dhcp4/ctrl_dhcp4_srv.h
@@ -94,6 +94,27 @@ protected:
static isc::data::ConstElementPtr
dhcp4ConfigHandler(isc::data::ConstElementPtr new_config);
+ /// @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
+ /// 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
+ /// the previous session. The \ref dhcp4ConfigHandler can't be
+ /// used to parse the initial configuration because it needs the
+ /// full configuration to satisfy dependencies between the
+ /// various configuration values. Installing the dummy handler
+ /// that guarantees to return success causes initial configuration
+ /// to be stored for the session being created and that it can
+ /// be later accessed with \ref isc::ConfigData::getFullConfig.
+ ///
+ /// @param new_config new configuration.
+ ///
+ /// @return success configuration status.
+ static isc::data::ConstElementPtr
+ dhcp4StubConfigHandler(isc::data::ConstElementPtr new_config);
+
/// @brief A callback for handling incoming commands.
///
/// @param command textual representation of the command
diff --git a/src/bin/dhcp4/dhcp4.spec b/src/bin/dhcp4/dhcp4.spec
index c2b755c..59d727e 100644
--- a/src/bin/dhcp4/dhcp4.spec
+++ b/src/bin/dhcp4/dhcp4.spec
@@ -55,13 +55,13 @@
{ "item_name": "code",
"item_type": "integer",
"item_optional": false,
- "item_default": 0,
+ "item_default": 0
},
{ "item_name": "type",
"item_type": "string",
"item_optional": false,
- "item_default": "",
+ "item_default": ""
},
{ "item_name": "array",
@@ -70,16 +70,22 @@
"item_default": False
},
- { "item_name": "record_types",
+ { "item_name": "record-types",
"item_type": "string",
"item_optional": false,
- "item_default": "",
+ "item_default": ""
},
{ "item_name": "space",
"item_type": "string",
"item_optional": false,
"item_default": ""
+ },
+
+ { "item_name": "encapsulate",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
} ]
}
},
@@ -125,6 +131,44 @@
}
},
+ { "item_name": "lease-database",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {"type": "memfile"},
+ "map_item_spec": [
+ {
+ "item_name": "type",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ },
+ {
+ "item_name": "name",
+ "item_type": "string",
+ "item_optional": true,
+ "item_default": ""
+ },
+ {
+ "item_name": "user",
+ "item_type": "string",
+ "item_optional": true,
+ "item_default": ""
+ },
+ {
+ "item_name": "host",
+ "item_type": "string",
+ "item_optional": true,
+ "item_default": ""
+ },
+ {
+ "item_name": "password",
+ "item_type": "string",
+ "item_optional": true,
+ "item_default": ""
+ }
+ ]
+ },
+
{ "item_name": "subnet4",
"item_type": "list",
"item_optional": false,
diff --git a/src/bin/dhcp4/dhcp4_messages.mes b/src/bin/dhcp4/dhcp4_messages.mes
index fc47823..17f9967 100644
--- a/src/bin/dhcp4/dhcp4_messages.mes
+++ b/src/bin/dhcp4/dhcp4_messages.mes
@@ -115,6 +115,38 @@ This error is output if the server failed to assemble the data to be
returned to the client into a valid packet. The cause is most likely
to be a programming error: please raise a bug report.
+% DHCP4_PARSER_COMMIT_EXCEPTION parser failed to commit changes
+On receipt of message containing details to a change of the IPv4 DHCP
+server configuration, a set of parsers were successfully created, but one
+of them failed to commit its changes due to a low-level system exception
+being raised. Additional messages may be output indicating the reason.
+
+% DHCP4_PARSER_COMMIT_FAIL parser failed to commit changes: %1
+On receipt of message containing details to a change of the IPv4 DHCP
+server configuration, a set of parsers were successfully created, but
+one of them failed to commit its changes. The reason for the failure
+is given in the message.
+
+% DHCP4_PARSER_CREATED created parser for configuration element %1
+A debug message output during a configuration update of the IPv4 DHCP
+server, notifying that the parser for the specified configuration element
+has been successfully created.
+
+% DHCP4_PARSER_FAIL failed to create or run parser for configuration element %1: %2
+On receipt of message containing details to a change of its configuration,
+the IPv4 DHCP server failed to create a parser to decode the contents
+of the named configuration element, or the creation succeeded but the
+parsing actions and committal of changes failed. The reason for the
+failure is given in the message.
+
+% DHCP4_PARSER_EXCEPTION failed to create or run parser for configuration element %1
+On receipt of message containing details to a change of its configuration,
+the IPv4 DHCP server failed to create a parser to decode the contents of
+the named configuration element, or the creation succeeded but the parsing
+actions and committal of changes failed. The message has been output in
+response to a non-BIND 10 exception being raised. Additional messages
+may give further information.
+
% DHCP4_QUERY_DATA received packet type %1, data is <%2>
A debug message listing the data received from the client.
@@ -162,6 +194,26 @@ A debug message listing the data returned to the client.
The IPv4 DHCP server has encountered a fatal error and is terminating.
The reason for the failure is included in the message.
+% DHCP4_SERVERID_GENERATED server-id %1 has been generated and will be stored in %2
+This informational messages indicates that the server was not able to
+read its server identifier and has generated a new one. This server-id
+will be stored in a file and will be read (and used) whenever the server
+is restarted. This is normal behavior when the server is started for the
+first time. If this message is printed every time the server is started,
+please check that the server has sufficient permission to write its
+server-id file and that the file is not corrupt.
+
+% DHCP4_SERVERID_LOADED server-id %1 has been loaded from file %2
+This debug message indicates that the server loaded its server identifier.
+That value is sent in all server responses and clients use it to
+discriminate between servers. This is a part of normal startup or
+reconfiguration procedure.
+
+% DHCP4_SERVERID_WRITE_FAIL server was not able to write its ID to file %1
+This warning message indicates that server was not able to write its
+server identifier to a file. The most likely cause is is that the server
+does not have permissions to write the server id file.
+
% DHCP4_SESSION_FAIL failed to establish BIND 10 session (%1), running stand-alone
The server has failed to establish communication with the rest of BIND
10 and is running in stand-alone mode. (This behavior will change once
diff --git a/src/bin/dhcp4/dhcp4_srv.cc b/src/bin/dhcp4/dhcp4_srv.cc
index 88ccfea..20d8597 100644
--- a/src/bin/dhcp4/dhcp4_srv.cc
+++ b/src/bin/dhcp4/dhcp4_srv.cc
@@ -30,6 +30,11 @@
#include <dhcpsrv/utils.h>
#include <dhcpsrv/addr_utilities.h>
+#include <boost/algorithm/string/erase.hpp>
+
+#include <iomanip>
+#include <fstream>
+
using namespace isc;
using namespace isc::asiolink;
using namespace isc::dhcp;
@@ -41,7 +46,6 @@ using namespace std;
const std::string HARDCODED_GATEWAY = "192.0.2.1";
const std::string HARDCODED_DNS_SERVER = "192.0.2.2";
const std::string HARDCODED_DOMAIN_NAME = "isc.example.com";
-const std::string HARDCODED_SERVER_ID = "192.0.2.1";
Dhcpv4Srv::Dhcpv4Srv(uint16_t port, const char* dbconfig) {
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_OPEN_SOCKET).arg(port);
@@ -56,7 +60,22 @@ Dhcpv4Srv::Dhcpv4Srv(uint16_t port, const char* dbconfig) {
IfaceMgr::instance().openSockets4(port);
}
- setServerID();
+ string srvid_file = CfgMgr::instance().getDataDir() + "/" + string(SERVER_ID_FILE);
+ if (loadServerID(srvid_file)) {
+ LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_SERVERID_LOADED)
+ .arg(srvid_file);
+ } else {
+ generateServerID();
+ LOG_INFO(dhcp4_logger, DHCP4_SERVERID_GENERATED)
+ .arg(srvidToString(getServerID()))
+ .arg(srvid_file);
+
+ if (!writeServerID(srvid_file)) {
+ LOG_WARN(dhcp4_logger, DHCP4_SERVERID_WRITE_FAIL)
+ .arg(srvid_file);
+ }
+
+ }
// Instantiate LeaseMgr
LeaseMgrFactory::create(dbconfig);
@@ -176,19 +195,108 @@ Dhcpv4Srv::run() {
}
}
}
+ }
+
+ return (true);
+}
+
+bool Dhcpv4Srv::loadServerID(const std::string& file_name) {
+
+ // load content of the file into a string
+ fstream f(file_name.c_str(), ios::in);
+ if (!f.is_open()) {
+ return (false);
+ }
+
+ string hex_string;
+ f >> hex_string;
+ f.close();
+
+ // remove any spaces
+ boost::algorithm::erase_all(hex_string, " ");
+
+ try {
+ IOAddress addr(hex_string);
+
+ if (!addr.isV4()) {
+ return (false);
+ }
+
+ // Now create server-id option
+ serverid_.reset(new Option4AddrLst(DHO_DHCP_SERVER_IDENTIFIER, addr));
- // TODO add support for config session (see src/bin/auth/main.cc)
- // so this daemon can be controlled from bob
+ } catch(...) {
+ // any kind of malformed input (empty string, IPv6 address, complete
+ // garbate etc.)
+ return (false);
}
return (true);
}
-void
-Dhcpv4Srv::setServerID() {
- /// @todo: implement this for real (see ticket #2588)
- serverid_ = OptionPtr(new Option4AddrLst(DHO_DHCP_SERVER_IDENTIFIER,
- IOAddress(HARDCODED_SERVER_ID)));
+void Dhcpv4Srv::generateServerID() {
+
+ const IfaceMgr::IfaceCollection& ifaces = IfaceMgr::instance().getIfaces();
+
+ // Let's find suitable interface.
+ for (IfaceMgr::IfaceCollection::const_iterator iface = ifaces.begin();
+ iface != ifaces.end(); ++iface) {
+
+ // Let's don't use loopback.
+ if (iface->flag_loopback_) {
+ continue;
+ }
+
+ // Let's skip downed interfaces. It is better to use working ones.
+ if (!iface->flag_up_) {
+ continue;
+ }
+
+ const IfaceMgr::AddressCollection addrs = iface->getAddresses();
+
+ for (IfaceMgr::AddressCollection::const_iterator addr = addrs.begin();
+ addr != addrs.end(); ++addr) {
+ if (addr->getFamily() != AF_INET) {
+ continue;
+ }
+
+ serverid_ = OptionPtr(new Option4AddrLst(DHO_DHCP_SERVER_IDENTIFIER,
+ *addr));
+ return;
+ }
+
+
+ }
+
+ isc_throw(BadValue, "No suitable interfaces for server-identifier found");
+}
+
+bool Dhcpv4Srv::writeServerID(const std::string& file_name) {
+ fstream f(file_name.c_str(), ios::out | ios::trunc);
+ if (!f.good()) {
+ return (false);
+ }
+ f << srvidToString(getServerID());
+ f.close();
+}
+
+string Dhcpv4Srv::srvidToString(const OptionPtr& srvid) {
+ if (!srvid) {
+ isc_throw(BadValue, "NULL pointer passed to srvidToString()");
+ }
+ boost::shared_ptr<Option4AddrLst> generated =
+ boost::dynamic_pointer_cast<Option4AddrLst>(srvid);
+ if (!srvid) {
+ isc_throw(BadValue, "Pointer to invalid option passed to srvidToString()");
+ }
+
+ Option4AddrLst::AddressContainer addrs = generated->getAddresses();
+ if (addrs.size() != 1) {
+ isc_throw(BadValue, "Malformed option passed to srvidToString(). "
+ << "Expected to contain a single IPv4 address.");
+ }
+
+ return (addrs[0].toText());
}
void Dhcpv4Srv::copyDefaultFields(const Pkt4Ptr& question, Pkt4Ptr& answer) {
diff --git a/src/bin/dhcp4/dhcp4_srv.h b/src/bin/dhcp4/dhcp4_srv.h
index 53401c5..8d26e05 100644
--- a/src/bin/dhcp4/dhcp4_srv.h
+++ b/src/bin/dhcp4/dhcp4_srv.h
@@ -28,6 +28,15 @@
namespace isc {
namespace dhcp {
+/// @brief file name of a server-id file
+///
+/// Server must store its server identifier in persistent storage that must not
+/// change between restarts. This is name of the file that is created in dataDir
+/// (see isc::dhcp::CfgMgr::getDataDir()). It is a text file that uses
+/// regular IPv4 address, e.g. 192.0.2.1. Server will create it during
+/// first run and then use it afterwards.
+static const char* SERVER_ID_FILE = "b10-dhcp4-serverid";
+
/// @brief DHCPv4 server service.
///
/// This singleton class represents DHCPv4 server. It contains all
@@ -216,7 +225,32 @@ protected:
///
/// @throws isc::Unexpected Failed to obtain server identifier (i.e. no
// previously stored configuration and no network interfaces available)
- void setServerID();
+ void generateServerID();
+
+ /// @brief attempts to load server-id from a file
+ ///
+ /// Tries to load duid from a text file. If the load is successful,
+ /// it creates server-id option and stores it in serverid_ (to be used
+ /// later by getServerID()).
+ ///
+ /// @param file_name name of the server-id file to load
+ /// @return true if load was successful, false otherwise
+ bool loadServerID(const std::string& file_name);
+
+ /// @brief attempts to write server-id to a file
+ /// Tries to write server-id content (stored in serverid_) to a text file.
+ ///
+ /// @param file_name name of the server-id file to write
+ /// @return true if write was successful, false otherwise
+ bool writeServerID(const std::string& file_name);
+
+ /// @brief converts server-id to text
+ /// Converts content of server-id option to a text representation, e.g.
+ /// "192.0.2.1"
+ ///
+ /// @param opt option that contains server-id
+ /// @return string representation
+ static std::string srvidToString(const OptionPtr& opt);
/// @brief Selects a subnet for a given client's packet.
///
diff --git a/src/bin/dhcp4/tests/config_parser_unittest.cc b/src/bin/dhcp4/tests/config_parser_unittest.cc
index ba14edf..9b3be51 100644
--- a/src/bin/dhcp4/tests/config_parser_unittest.cc
+++ b/src/bin/dhcp4/tests/config_parser_unittest.cc
@@ -21,6 +21,8 @@
#include <dhcp4/dhcp4_srv.h>
#include <dhcp4/config_parser.h>
#include <dhcp/option4_addrlst.h>
+#include <dhcp/option_custom.h>
+#include <dhcp/option_int.h>
#include <dhcpsrv/subnet.h>
#include <dhcpsrv/cfgmgr.h>
#include <boost/foreach.hpp>
@@ -61,7 +63,7 @@ public:
EXPECT_EQ(expected_value, it->second);
}
- // Checks if config_result (result of DHCP server configuration) has
+ // Checks if the result of DHCP server configuration has
// expected code (0 for success, other for failures).
// Also stores result in rcode_ and comment_.
void checkResult(ConstElementPtr status, int expected_code) {
@@ -345,7 +347,6 @@ TEST_F(Dhcp4ParserTest, subnetGlobalDefaults) {
" \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
" \"subnet\": \"192.0.2.0/24\" } ],"
"\"valid-lifetime\": 4000 }";
- cout << config << endl;
ElementPtr json = Element::fromJSON(config);
@@ -379,7 +380,6 @@ TEST_F(Dhcp4ParserTest, subnetLocal) {
" \"valid-lifetime\": 4,"
" \"subnet\": \"192.0.2.0/24\" } ],"
"\"valid-lifetime\": 4000 }";
- cout << config << endl;
ElementPtr json = Element::fromJSON(config);
@@ -408,7 +408,6 @@ TEST_F(Dhcp4ParserTest, poolOutOfSubnet) {
" \"pool\": [ \"192.0.4.0/28\" ],"
" \"subnet\": \"192.0.2.0/24\" } ],"
"\"valid-lifetime\": 4000 }";
- cout << config << endl;
ElementPtr json = Element::fromJSON(config);
@@ -433,7 +432,6 @@ TEST_F(Dhcp4ParserTest, poolPrefixLen) {
" \"pool\": [ \"192.0.2.128/28\" ],"
" \"subnet\": \"192.0.2.0/24\" } ],"
"\"valid-lifetime\": 4000 }";
- cout << config << endl;
ElementPtr json = Element::fromJSON(config);
@@ -461,7 +459,8 @@ TEST_F(Dhcp4ParserTest, optionDefIpv4Address) {
" \"type\": \"ipv4-address\","
" \"array\": False,"
" \"record-types\": \"\","
- " \"space\": \"isc\""
+ " \"space\": \"isc\","
+ " \"encapsulate\": \"\""
" } ]"
"}";
ElementPtr json = Element::fromJSON(config);
@@ -474,6 +473,7 @@ TEST_F(Dhcp4ParserTest, optionDefIpv4Address) {
ConstElementPtr status;
EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
ASSERT_TRUE(status);
+ checkResult(status, 0);
// The option definition should now be available in the CfgMgr.
def = CfgMgr::instance().getOptionDef("isc", 100);
@@ -484,6 +484,7 @@ TEST_F(Dhcp4ParserTest, optionDefIpv4Address) {
EXPECT_EQ(100, def->getCode());
EXPECT_FALSE(def->getArrayType());
EXPECT_EQ(OPT_IPV4_ADDRESS_TYPE, def->getType());
+ EXPECT_TRUE(def->getEncapsulatedSpace().empty());
}
// The goal of this test is to check whether an option definiiton
@@ -499,7 +500,8 @@ TEST_F(Dhcp4ParserTest, optionDefRecord) {
" \"type\": \"record\","
" \"array\": False,"
" \"record-types\": \"uint16, ipv4-address, ipv6-address, string\","
- " \"space\": \"isc\""
+ " \"space\": \"isc\","
+ " \"encapsulate\": \"\""
" } ]"
"}";
ElementPtr json = Element::fromJSON(config);
@@ -523,6 +525,7 @@ TEST_F(Dhcp4ParserTest, optionDefRecord) {
EXPECT_EQ(100, def->getCode());
EXPECT_EQ(OPT_RECORD_TYPE, def->getType());
EXPECT_FALSE(def->getArrayType());
+ EXPECT_TRUE(def->getEncapsulatedSpace().empty());
// The option comprises the record of data fields. Verify that all
// fields are present and they are of the expected types.
@@ -546,7 +549,8 @@ TEST_F(Dhcp4ParserTest, optionDefMultiple) {
" \"type\": \"uint32\","
" \"array\": False,"
" \"record-types\": \"\","
- " \"space\": \"isc\""
+ " \"space\": \"isc\","
+ " \"encapsulate\": \"\""
" },"
" {"
" \"name\": \"foo-2\","
@@ -554,7 +558,8 @@ TEST_F(Dhcp4ParserTest, optionDefMultiple) {
" \"type\": \"ipv4-address\","
" \"array\": False,"
" \"record-types\": \"\","
- " \"space\": \"isc\""
+ " \"space\": \"isc\","
+ " \"encapsulate\": \"\""
" } ]"
"}";
ElementPtr json = Element::fromJSON(config);
@@ -578,6 +583,7 @@ TEST_F(Dhcp4ParserTest, optionDefMultiple) {
EXPECT_EQ(100, def1->getCode());
EXPECT_EQ(OPT_UINT32_TYPE, def1->getType());
EXPECT_FALSE(def1->getArrayType());
+ EXPECT_TRUE(def1->getEncapsulatedSpace().empty());
// Check the second option definition we have created.
OptionDefinitionPtr def2 = CfgMgr::instance().getOptionDef("isc", 101);
@@ -588,6 +594,7 @@ TEST_F(Dhcp4ParserTest, optionDefMultiple) {
EXPECT_EQ(101, def2->getCode());
EXPECT_EQ(OPT_IPV4_ADDRESS_TYPE, def2->getType());
EXPECT_FALSE(def2->getArrayType());
+ EXPECT_TRUE(def2->getEncapsulatedSpace().empty());
}
// The goal of this test is to verify that the duplicated option
@@ -604,7 +611,8 @@ TEST_F(Dhcp4ParserTest, optionDefDuplicate) {
" \"type\": \"uint32\","
" \"array\": False,"
" \"record-types\": \"\","
- " \"space\": \"isc\""
+ " \"space\": \"isc\","
+ " \"encapsulate\": \"\""
" },"
" {"
" \"name\": \"foo-2\","
@@ -612,7 +620,8 @@ TEST_F(Dhcp4ParserTest, optionDefDuplicate) {
" \"type\": \"ipv4-address\","
" \"array\": False,"
" \"record-types\": \"\","
- " \"space\": \"isc\""
+ " \"space\": \"isc\","
+ " \"encapsulate\": \"\""
" } ]"
"}";
ElementPtr json = Element::fromJSON(config);
@@ -640,7 +649,8 @@ TEST_F(Dhcp4ParserTest, optionDefArray) {
" \"type\": \"uint32\","
" \"array\": True,"
" \"record-types\": \"\","
- " \"space\": \"isc\""
+ " \"space\": \"isc\","
+ " \"encapsulate\": \"\""
" } ]"
"}";
ElementPtr json = Element::fromJSON(config);
@@ -664,6 +674,48 @@ TEST_F(Dhcp4ParserTest, optionDefArray) {
EXPECT_EQ(100, def->getCode());
EXPECT_EQ(OPT_UINT32_TYPE, def->getType());
EXPECT_TRUE(def->getArrayType());
+ EXPECT_TRUE(def->getEncapsulatedSpace().empty());
+}
+
+// The purpose of this test to verify that encapsulated option
+// space name may be specified.
+TEST_F(Dhcp4ParserTest, optionDefEncapsulate) {
+
+ // Configuration string. Included the encapsulated
+ // option space name.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"uint32\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\","
+ " \"encapsulate\": \"sub-opts-space\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Make sure that the particular option definition does not exist.
+ OptionDefinitionPtr def = CfgMgr::instance().getOptionDef("isc", 100);
+ ASSERT_FALSE(def);
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // The option definition should now be available in the CfgMgr.
+ def = CfgMgr::instance().getOptionDef("isc", 100);
+ ASSERT_TRUE(def);
+
+ // Check the option data.
+ EXPECT_EQ("foo", def->getName());
+ EXPECT_EQ(100, def->getCode());
+ EXPECT_EQ(OPT_UINT32_TYPE, def->getType());
+ EXPECT_FALSE(def->getArrayType());
+ EXPECT_EQ("sub-opts-space", def->getEncapsulatedSpace());
}
/// The purpose of this test is to verify that the option definition
@@ -678,7 +730,8 @@ TEST_F(Dhcp4ParserTest, optionDefInvalidName) {
" \"type\": \"string\","
" \"array\": False,"
" \"record-types\": \"\","
- " \"space\": \"isc\""
+ " \"space\": \"isc\","
+ " \"encapsulate\": \"\""
" } ]"
"}";
ElementPtr json = Element::fromJSON(config);
@@ -703,7 +756,8 @@ TEST_F(Dhcp4ParserTest, optionDefInvalidType) {
" \"type\": \"sting\","
" \"array\": False,"
" \"record-types\": \"\","
- " \"space\": \"isc\""
+ " \"space\": \"isc\","
+ " \"encapsulate\": \"\""
" } ]"
"}";
ElementPtr json = Element::fromJSON(config);
@@ -728,7 +782,62 @@ TEST_F(Dhcp4ParserTest, optionDefInvalidRecordType) {
" \"type\": \"record\","
" \"array\": False,"
" \"record-types\": \"uint32,uint8,sting\","
- " \"space\": \"isc\""
+ " \"space\": \"isc\","
+ " \"encapsulate\": \"\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ // Expecting parsing error (error code 1).
+ checkResult(status, 1);
+}
+
+/// The goal of this test is to verify that the invalid encapsulated
+/// option space name is not accepted.
+TEST_F(Dhcp4ParserTest, optionDefInvalidEncapsulatedSpace) {
+ // Configuration string. The encapsulated option space
+ // name is invalid (% character is not allowed).
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"uint32\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\","
+ " \"encapsulate\": \"invalid%space%name\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ // Expecting parsing error (error code 1).
+ checkResult(status, 1);
+}
+
+/// The goal of this test is to verify that the encapsulated
+/// option space name can't be specified for the option that
+/// comprises an array of data fields.
+TEST_F(Dhcp4ParserTest, optionDefEncapsulatedSpaceAndArray) {
+ // Configuration string. The encapsulated option space
+ // name is set to non-empty value and the array flag
+ // is set.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"uint32\","
+ " \"array\": True,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\","
+ " \"encapsulate\": \"valid-space-name\""
" } ]"
"}";
ElementPtr json = Element::fromJSON(config);
@@ -741,6 +850,31 @@ TEST_F(Dhcp4ParserTest, optionDefInvalidRecordType) {
checkResult(status, 1);
}
+/// The goal of this test is to verify that the option may not
+/// encapsulate option space it belongs to.
+TEST_F(Dhcp4ParserTest, optionDefEncapsulateOwnSpace) {
+ // Configuration string. Option is set to encapsulate
+ // option space it belongs to.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"uint32\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\","
+ " \"encapsulate\": \"isc\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ // Expecting parsing error (error code 1).
+ checkResult(status, 1);
+}
/// The purpose of this test is to verify that it is not allowed
/// to override the standard option (that belongs to dhcp4 option
@@ -759,7 +893,8 @@ TEST_F(Dhcp4ParserTest, optionStandardDefOverride) {
" \"type\": \"string\","
" \"array\": False,"
" \"record-types\": \"\","
- " \"space\": \"dhcp4\""
+ " \"space\": \"dhcp4\","
+ " \"encapsulate\": \"\""
" } ]"
"}";
ElementPtr json = Element::fromJSON(config);
@@ -794,7 +929,8 @@ TEST_F(Dhcp4ParserTest, optionStandardDefOverride) {
" \"type\": \"string\","
" \"array\": False,"
" \"record-types\": \"\","
- " \"space\": \"dhcp4\""
+ " \"space\": \"dhcp4\","
+ " \"encapsulate\": \"\""
" } ]"
"}";
json = Element::fromJSON(config);
@@ -907,7 +1043,8 @@ TEST_F(Dhcp4ParserTest, optionDataTwoSpaces) {
" \"type\": \"uint32\","
" \"array\": False,"
" \"record-types\": \"\","
- " \"space\": \"isc\""
+ " \"space\": \"isc\","
+ " \"encapsulate\": \"\""
" } ],"
"\"subnet4\": [ { "
" \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
@@ -940,6 +1077,166 @@ TEST_F(Dhcp4ParserTest, optionDataTwoSpaces) {
ASSERT_FALSE(desc3.option);
}
+// The goal of this test is to verify that it is possible to
+// encapsulate option space containing some options with
+// another option. In this test we create base option that
+// encapsulates option space 'isc' that comprises two other
+// options. Also, for all options their definitions are
+// created.
+TEST_F(Dhcp4ParserTest, optionDataEncapsulate) {
+
+ // @todo DHCP configurations has many dependencies between
+ // parameters. First of all, configuration for subnet is
+ // inherited from the global values. Thus subnet has to be
+ // configured when all global values have been configured.
+ // Also, an option can encapsulate another option only
+ // if the latter has been configured. For this reason in this
+ // test we created two-stage configuration where first we
+ // created options that belong to encapsulated option space.
+ // In the second stage we add the base option. Also, the Subnet
+ // object is configured in the second stage so it is created
+ // at the very end (when all other parameters are configured).
+
+ // Starting stage 1. Configure sub-options and their definitions.
+ string config = "{ \"interface\": [ \"all\" ],"
+ "\"rebind-timer\": 2000,"
+ "\"renew-timer\": 1000,"
+ "\"option-data\": [ {"
+ " \"name\": \"foo\","
+ " \"space\": \"isc\","
+ " \"code\": 1,"
+ " \"data\": \"1234\","
+ " \"csv-format\": True"
+ " },"
+ " {"
+ " \"name\": \"foo2\","
+ " \"space\": \"isc\","
+ " \"code\": 2,"
+ " \"data\": \"192.168.2.1\","
+ " \"csv-format\": True"
+ " } ],"
+ "\"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 1,"
+ " \"type\": \"uint32\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\","
+ " \"encapsulate\": \"\""
+ " },"
+ " {"
+ " \"name\": \"foo2\","
+ " \"code\": 2,"
+ " \"type\": \"ipv4-address\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\","
+ " \"encapsulate\": \"\""
+ " } ]"
+ "}";
+
+ ConstElementPtr status;
+
+ ElementPtr json = Element::fromJSON(config);
+
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // Stage 2. Configure base option and a subnet. Please note that
+ // the configuration from the stage 2 is repeated because BIND
+ // configuration manager sends whole configuration for the lists
+ // where at least one element is being modified or added.
+ config = "{ \"interface\": [ \"all\" ],"
+ "\"rebind-timer\": 2000,"
+ "\"renew-timer\": 1000,"
+ "\"option-data\": [ {"
+ " \"name\": \"base-option\","
+ " \"space\": \"dhcp4\","
+ " \"code\": 222,"
+ " \"data\": \"11\","
+ " \"csv-format\": True"
+ " },"
+ " {"
+ " \"name\": \"foo\","
+ " \"space\": \"isc\","
+ " \"code\": 1,"
+ " \"data\": \"1234\","
+ " \"csv-format\": True"
+ " },"
+ " {"
+ " \"name\": \"foo2\","
+ " \"space\": \"isc\","
+ " \"code\": 2,"
+ " \"data\": \"192.168.2.1\","
+ " \"csv-format\": True"
+ " } ],"
+ "\"option-def\": [ {"
+ " \"name\": \"base-option\","
+ " \"code\": 222,"
+ " \"type\": \"uint8\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"dhcp4\","
+ " \"encapsulate\": \"isc\""
+ "},"
+ "{"
+ " \"name\": \"foo\","
+ " \"code\": 1,"
+ " \"type\": \"uint32\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\","
+ " \"encapsulate\": \"\""
+ " },"
+ " {"
+ " \"name\": \"foo2\","
+ " \"code\": 2,"
+ " \"type\": \"ipv4-address\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\","
+ " \"encapsulate\": \"\""
+ " } ],"
+ "\"subnet4\": [ { "
+ " \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
+ " \"subnet\": \"192.0.2.0/24\""
+ " } ]"
+ "}";
+
+
+ json = Element::fromJSON(config);
+
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // Get the subnet.
+ Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.5"));
+ ASSERT_TRUE(subnet);
+
+ // We should have one option available.
+ Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp4");
+ ASSERT_TRUE(options);
+ ASSERT_EQ(1, options->size());
+
+ // Get the option.
+ Subnet::OptionDescriptor desc = subnet->getOptionDescriptor("dhcp4", 222);
+ EXPECT_TRUE(desc.option);
+ EXPECT_EQ(222, desc.option->getType());
+
+ // This opton should comprise two sub-options.
+ // One of them is 'foo' with code 1.
+ OptionPtr option_foo = desc.option->getOption(1);
+ ASSERT_TRUE(option_foo);
+ EXPECT_EQ(1, option_foo->getType());
+
+ // ...another one 'foo2' with code 2.
+ OptionPtr option_foo2 = desc.option->getOption(2);
+ ASSERT_TRUE(option_foo2);
+ EXPECT_EQ(2, option_foo2->getType());
+}
+
// Goal of this test is to verify options configuration
// for a single subnet. In particular this test checks
// that local options configuration overrides global
@@ -1290,4 +1587,165 @@ TEST_F(Dhcp4ParserTest, DISABLED_Uint32Parser) {
checkResult(status, 1);
}
+// The goal of this test is to verify that the standard option can
+// be configured to encapsulate multiple other options.
+TEST_F(Dhcp4ParserTest, stdOptionDataEncapsulate) {
+
+ // The configuration is two stage process in this test.
+ // In the first stahe we create definitions of suboptions
+ // that we will add to the base option.
+ // Let's create some dummy options: foo and foo2.
+ string config = "{ \"interface\": [ \"all\" ],"
+ "\"rebind-timer\": 2000,"
+ "\"renew-timer\": 1000,"
+ "\"option-data\": [ {"
+ " \"name\": \"foo\","
+ " \"space\": \"vendor-encapsulated-options-space\","
+ " \"code\": 1,"
+ " \"data\": \"1234\","
+ " \"csv-format\": True"
+ " },"
+ " {"
+ " \"name\": \"foo2\","
+ " \"space\": \"vendor-encapsulated-options-space\","
+ " \"code\": 2,"
+ " \"data\": \"192.168.2.1\","
+ " \"csv-format\": True"
+ " } ],"
+ "\"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 1,"
+ " \"type\": \"uint32\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"vendor-encapsulated-options-space\","
+ " \"encapsulate\": \"\""
+ " },"
+ " {"
+ " \"name\": \"foo2\","
+ " \"code\": 2,"
+ " \"type\": \"ipv4-address\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"vendor-encapsulated-options-space\","
+ " \"encapsulate\": \"\""
+ " } ]"
+ "}";
+
+ ConstElementPtr status;
+
+ ElementPtr json = Element::fromJSON(config);
+
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // Once the definitions have been added we can configure the
+ // standard option #17. This option comprises an enterprise
+ // number and sub options. By convention (introduced in
+ // std_option_defs.h) option named 'vendor-opts'
+ // encapsulates the option space named 'vendor-opts-space'.
+ // We add our dummy options to this option space and thus
+ // they should be included as sub-options in the 'vendor-opts'
+ // option.
+ config = "{ \"interface\": [ \"all\" ],"
+ "\"rebind-timer\": 2000,"
+ "\"renew-timer\": 1000,"
+ "\"option-data\": [ {"
+ " \"name\": \"vendor-encapsulated-options\","
+ " \"space\": \"dhcp4\","
+ " \"code\": 43,"
+ " \"data\": \"\","
+ " \"csv-format\": False"
+ " },"
+ " {"
+ " \"name\": \"foo\","
+ " \"space\": \"vendor-encapsulated-options-space\","
+ " \"code\": 1,"
+ " \"data\": \"1234\","
+ " \"csv-format\": True"
+ " },"
+ " {"
+ " \"name\": \"foo2\","
+ " \"space\": \"vendor-encapsulated-options-space\","
+ " \"code\": 2,"
+ " \"data\": \"192.168.2.1\","
+ " \"csv-format\": True"
+ " } ],"
+ "\"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 1,"
+ " \"type\": \"uint32\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"vendor-encapsulated-options-space\","
+ " \"encapsulate\": \"\""
+ " },"
+ " {"
+ " \"name\": \"foo2\","
+ " \"code\": 2,"
+ " \"type\": \"ipv4-address\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"vendor-encapsulated-options-space\","
+ " \"encapsulate\": \"\""
+ " } ],"
+ "\"subnet4\": [ { "
+ " \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
+ " \"subnet\": \"192.0.2.0/24\""
+ " } ]"
+ "}";
+
+
+ json = Element::fromJSON(config);
+
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // Get the subnet.
+ Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.5"));
+ ASSERT_TRUE(subnet);
+
+ // We should have one option available.
+ Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp4");
+ ASSERT_TRUE(options);
+ ASSERT_EQ(1, options->size());
+
+ // Get the option.
+ Subnet::OptionDescriptor desc =
+ subnet->getOptionDescriptor("dhcp4", DHO_VENDOR_ENCAPSULATED_OPTIONS);
+ EXPECT_TRUE(desc.option);
+ EXPECT_EQ(DHO_VENDOR_ENCAPSULATED_OPTIONS, desc.option->getType());
+
+ // Option with the code 1 should be added as a sub-option.
+ OptionPtr option_foo = desc.option->getOption(1);
+ ASSERT_TRUE(option_foo);
+ EXPECT_EQ(1, option_foo->getType());
+ // This option comprises a single uint32_t value thus it is
+ // represented by OptionInt<uint32_t> class. Let's get the
+ // object of this type.
+ boost::shared_ptr<OptionInt<uint32_t> > option_foo_uint32 =
+ boost::dynamic_pointer_cast<OptionInt<uint32_t> >(option_foo);
+ ASSERT_TRUE(option_foo_uint32);
+ // Validate the value according to the configuration.
+ EXPECT_EQ(1234, option_foo_uint32->getValue());
+
+ // Option with the code 2 should be added as a sub-option.
+ OptionPtr option_foo2 = desc.option->getOption(2);
+ ASSERT_TRUE(option_foo2);
+ EXPECT_EQ(2, option_foo2->getType());
+ // This option comprises the IPV4 address. Such option is
+ // represented by OptionCustom object.
+ OptionCustomPtr option_foo2_v4 =
+ boost::dynamic_pointer_cast<OptionCustom>(option_foo2);
+ ASSERT_TRUE(option_foo2_v4);
+ // Get the IP address carried by this option and validate it.
+ EXPECT_EQ("192.168.2.1", option_foo2_v4->readAddress().toText());
+
+ // Option with the code 3 should not be added.
+ EXPECT_FALSE(desc.option->getOption(3));
+}
+
+
};
diff --git a/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc b/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
index bc2246f..b9328ea 100644
--- a/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
+++ b/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
@@ -49,9 +49,15 @@ public:
using Dhcpv4Srv::processDecline;
using Dhcpv4Srv::processInform;
using Dhcpv4Srv::getServerID;
+ using Dhcpv4Srv::loadServerID;
+ using Dhcpv4Srv::generateServerID;
+ using Dhcpv4Srv::writeServerID;
using Dhcpv4Srv::sanityCheck;
+ using Dhcpv4Srv::srvidToString;
};
+static const char* SRVID_FILE = "server-id-test.txt";
+
class Dhcpv4SrvTest : public ::testing::Test {
public:
@@ -67,6 +73,9 @@ public:
CfgMgr::instance().deleteSubnets4();
CfgMgr::instance().addSubnet4(subnet_);
+
+ // it's ok if that fails. There should not be such a file anyway
+ unlink(SRVID_FILE);
}
/// @brief checks that the response matches request
@@ -245,6 +254,9 @@ public:
~Dhcpv4SrvTest() {
CfgMgr::instance().deleteSubnets4();
+
+ // Let's clean up if there is such a file.
+ unlink(SRVID_FILE);
};
/// @brief A subnet used in most tests
@@ -691,7 +703,7 @@ TEST_F(Dhcpv4SrvTest, ManyDiscovers) {
checkAddressParams(offer2, subnet_);
checkAddressParams(offer3, subnet_);
- // Check DUIDs
+ // Check server-ids
checkServerId(offer1, srv->getServerID());
checkServerId(offer2, srv->getServerID());
checkServerId(offer3, srv->getServerID());
@@ -878,7 +890,6 @@ TEST_F(Dhcpv4SrvTest, RenewBasic) {
// let's create a lease and put it in the LeaseMgr
uint8_t hwaddr2[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe};
- uint8_t clientid2[] = { 8, 7, 6, 5, 4, 3, 2, 1 };
Lease4Ptr used(new Lease4(IOAddress("192.0.2.106"), hwaddr2, sizeof(hwaddr2),
&client_id_->getDuid()[0], client_id_->getDuid().size(),
temp_valid, temp_t1, temp_t2, temp_timestamp,
@@ -1126,4 +1137,34 @@ TEST_F(Dhcpv4SrvTest, ReleaseReject) {
EXPECT_FALSE(l);
}
+// This test verifies if the server-id disk operations (read, write) are
+// working properly.
+TEST_F(Dhcpv4SrvTest, ServerID) {
+ NakedDhcpv4Srv srv(0);
+
+ string srvid_text = "192.0.2.100";
+ IOAddress srvid(srvid_text);
+
+ fstream file1(SRVID_FILE, ios::out | ios::trunc);
+ file1 << srvid_text;
+ file1.close();
+
+ // Test reading from a file
+ EXPECT_TRUE(srv.loadServerID(SRVID_FILE));
+ ASSERT_TRUE(srv.getServerID());
+ EXPECT_EQ(srvid_text, srv.srvidToString(srv.getServerID()));
+
+ // Now test writing to a file
+ EXPECT_EQ(0, unlink(SRVID_FILE));
+ EXPECT_NO_THROW(srv.writeServerID(SRVID_FILE));
+
+ fstream file2(SRVID_FILE, ios::in);
+ ASSERT_TRUE(file2.good());
+ string text;
+ file2 >> text;
+ file2.close();
+
+ EXPECT_EQ(srvid_text, text);
+}
+
} // end of anonymous namespace
diff --git a/src/bin/dhcp6/config_parser.cc b/src/bin/dhcp6/config_parser.cc
index 5c8675e..300d85b 100644
--- a/src/bin/dhcp6/config_parser.cc
+++ b/src/bin/dhcp6/config_parser.cc
@@ -18,7 +18,9 @@
#include <dhcp/libdhcp++.h>
#include <dhcp6/config_parser.h>
#include <dhcp6/dhcp6_log.h>
+#include <dhcp/iface_mgr.h>
#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/dbaccess_parser.h>
#include <dhcpsrv/dhcp_config_parser.h>
#include <dhcpsrv/pool.h>
#include <dhcpsrv/subnet.h>
@@ -58,9 +60,6 @@ typedef boost::shared_ptr<BooleanParser> BooleanParserPtr;
typedef boost::shared_ptr<StringParser> StringParserPtr;
typedef boost::shared_ptr<Uint32Parser> Uint32ParserPtr;
-/// @brief Auxiliary type used for storing an element name and its parser.
-typedef pair<string, ConstElementPtr> ConfigPair;
-
/// @brief Factory method that will create a parser for a given element name
typedef isc::dhcp::DhcpConfigParser* ParserFactory(const std::string& config_id);
@@ -103,7 +102,6 @@ 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,
@@ -778,27 +776,31 @@ private:
// 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, "Parser error: value of 'code' must not"
- << " be equal to zero. Option code '0' is reserved in"
- << " DHCPv6.");
+ 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, "Parser error: value of 'code' must not"
- << " exceed " << 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.
- // @todo possibly some more restrictions apply here?
std::string option_name = getParam<std::string>("name", string_values_);
if (option_name.empty()) {
- isc_throw(DhcpConfigError, "Parser error: option name must not be"
- << " 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, "Parser error: option name must not contain"
- << " spaces");
+ isc_throw(DhcpConfigError, "invalid option name '" << option_name
+ << "', space character is not allowed");
}
std::string option_space = getParam<std::string>("space", string_values_);
- /// @todo Validate option space once #2313 is merged.
+ if (!OptionSpace::validateName(option_space)) {
+ isc_throw(DhcpConfigError, "invalid option space name '"
+ << option_space << "' specified for option '"
+ << option_name << "' (code '" << option_code
+ << "')");
+ }
OptionDefinitionPtr def;
if (option_space == "dhcp6" &&
@@ -887,7 +889,7 @@ private:
// definition of option value makes sense.
if (def->getName() != option_name) {
isc_throw(DhcpConfigError, "specified option name '"
- << option_name << " does not match the "
+ << option_name << "' does not match the "
<< "option definition: '" << option_space
<< "." << def->getName() << "'");
}
@@ -1034,8 +1036,8 @@ public:
BOOST_FOREACH(ConfigPair param, option_def->mapValue()) {
std::string entry(param.first);
ParserPtr parser;
- if (entry == "name" || entry == "type" ||
- entry == "record-types" || entry == "space") {
+ if (entry == "name" || entry == "type" || entry == "record-types" ||
+ entry == "space" || entry == "encapsulate") {
StringParserPtr
str_parser(dynamic_cast<StringParser*>(StringParser::factory(entry)));
if (str_parser) {
@@ -1085,8 +1087,8 @@ public:
/// @brief Stores the parsed option definition in the data store.
void commit() {
- // @todo validate option space name once 2313 is merged.
- if (storage_ && option_definition_) {
+ if (storage_ && option_definition_ &&
+ OptionSpace::validateName(option_space_name_)) {
storage_->addItem(option_definition_, option_space_name_);
}
}
@@ -1108,11 +1110,10 @@ private:
void createOptionDef() {
// Get the option space name and validate it.
std::string space = getParam<std::string>("space", string_values_);
- // @todo uncomment the code below when the #2313 is merged.
- /* if (!OptionSpace::validateName()) {
+ if (!OptionSpace::validateName(space)) {
isc_throw(DhcpConfigError, "invalid option space name '"
<< space << "'");
- } */
+ }
// Get other parameters that are needed to create the
// option definition.
@@ -1120,9 +1121,36 @@ private:
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));
+
+ }
- OptionDefinitionPtr def(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",
@@ -1140,7 +1168,7 @@ private:
}
} catch (const Exception& ex) {
isc_throw(DhcpConfigError, "invalid record type values"
- << " specified for the option definition: "
+ << " specified for the option definition: "
<< ex.what());
}
}
@@ -1359,6 +1387,63 @@ private:
return (false);
}
+ /// @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 Create a new subnet using a data from child parsers.
///
/// @throw isc::dhcp::DhcpConfigError if subnet configuration parsing failed.
@@ -1400,6 +1485,15 @@ 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.
+
+ string iface;
+ StringStorage::const_iterator iface_iter = string_values_.find("interface");
+ if (iface_iter != string_values_.end()) {
+ iface = iface_iter->second;
+ }
+
/// @todo: Convert this to logger once the parser is working reliably
stringstream tmp;
tmp << addr.toText() << "/" << (int)len
@@ -1416,6 +1510,17 @@ private:
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
@@ -1439,6 +1544,8 @@ private:
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);
}
@@ -1466,6 +1573,9 @@ private:
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);
}
}
@@ -1489,6 +1599,7 @@ private:
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()) {
@@ -1641,6 +1752,7 @@ DhcpConfigParser* createGlobalDhcpConfigParser(const std::string& config_id) {
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()) {
@@ -1695,13 +1807,19 @@ configureDhcp6Server(Dhcpv6Srv&, ConstElementPtr config_set) {
// rollback informs whether error occured and original data
// have to be restored to global storages.
bool rollback = false;
+ // config_pair holds ther details of the current parser when iterating over
+ // the parsers. It is declared outside the loop so in case of error, the
+ // name of the failing parser can be retrieved within the "catch" clause.
+ ConfigPair config_pair;
try {
// Make parsers grouping.
const std::map<std::string, ConstElementPtr>& values_map =
config_set->mapValue();
- BOOST_FOREACH(ConfigPair config_pair, values_map) {
+ BOOST_FOREACH(config_pair, values_map) {
ParserPtr parser(createGlobalDhcpConfigParser(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;
@@ -1736,15 +1854,18 @@ configureDhcp6Server(Dhcpv6Srv&, ConstElementPtr config_set) {
}
} catch (const isc::Exception& ex) {
- answer =
- isc::config::createAnswer(1, string("Configuration parsing failed: ") + ex.what());
+ LOG_ERROR(dhcp6_logger, DHCP6_PARSER_FAIL)
+ .arg(config_pair.first).arg(ex.what());
+ answer = isc::config::createAnswer(1,
+ string("Configuration parsing failed: ") + ex.what());
// An error occured, so make sure that we restore original data.
rollback = true;
} catch (...) {
// for things like bad_cast in boost::lexical_cast
- answer =
- isc::config::createAnswer(1, string("Configuration parsing failed"));
+ LOG_ERROR(dhcp6_logger, DHCP6_PARSER_EXCEPTION).arg(config_pair.first);
+ answer = isc::config::createAnswer(1,
+ string("Configuration parsing failed"));
// An error occured, so make sure that we restore original data.
rollback = true;
}
@@ -1760,15 +1881,16 @@ configureDhcp6Server(Dhcpv6Srv&, ConstElementPtr config_set) {
}
}
catch (const isc::Exception& ex) {
- answer =
- isc::config::createAnswer(2, string("Configuration commit failed:")
- + ex.what());
+ LOG_ERROR(dhcp6_logger, DHCP6_PARSER_COMMIT_FAIL).arg(ex.what());
+ answer = isc::config::createAnswer(2,
+ string("Configuration commit failed:") + ex.what());
// An error occured, so make sure to restore the original data.
rollback = true;
} catch (...) {
// for things like bad_cast in boost::lexical_cast
- answer =
- isc::config::createAnswer(2, string("Configuration commit failed"));
+ LOG_ERROR(dhcp6_logger, DHCP6_PARSER_COMMIT_EXCEPTION);
+ answer = isc::config::createAnswer(2,
+ string("Configuration commit failed"));
// An error occured, so make sure to restore the original data.
rollback = true;
}
diff --git a/src/bin/dhcp6/ctrl_dhcp6_srv.cc b/src/bin/dhcp6/ctrl_dhcp6_srv.cc
index ba3e2c2..e4e17f1 100644
--- a/src/bin/dhcp6/ctrl_dhcp6_srv.cc
+++ b/src/bin/dhcp6/ctrl_dhcp6_srv.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
@@ -42,23 +42,65 @@ using namespace std;
namespace isc {
namespace dhcp {
-
ControlledDhcpv6Srv* ControlledDhcpv6Srv::server_ = NULL;
ConstElementPtr
+ControlledDhcpv6Srv::dhcp6StubConfigHandler(ConstElementPtr) {
+ // This configuration handler is intended to be used only
+ // when the initial configuration comes in. To receive this
+ // configuration a pointer to this handler must be passed
+ // using ModuleCCSession constructor. This constructor will
+ // invoke the handler and will store the configuration for
+ // the configuration session when the handler returns success.
+ // Since this configuration is partial we just pretend to
+ // parse it and always return success. The function that
+ // initiates the session must get the configuration on its
+ // own using getFullConfig.
+ return (isc::config::createAnswer(0, "Configuration accepted."));
+}
+
+ConstElementPtr
ControlledDhcpv6Srv::dhcp6ConfigHandler(ConstElementPtr new_config) {
- LOG_DEBUG(dhcp6_logger, DBG_DHCP6_COMMAND, DHCP6_CONFIG_UPDATE)
- .arg(new_config->str());
- if (server_) {
- return (configureDhcp6Server(*server_, new_config));
+ if (!server_ || !server_->config_session_) {
+ // That should never happen as we install config_handler
+ // after we instantiate the server.
+ ConstElementPtr answer =
+ isc::config::createAnswer(1, "Configuration rejected,"
+ " server is during startup/shutdown phase.");
+ return (answer);
}
- // That should never happen as we install config_handler after we instantiate
- // the server.
- ConstElementPtr answer = isc::config::createAnswer(1,
- "Configuration rejected, server is during startup/shutdown phase.");
- return (answer);
+ // The configuration passed to this handler function is partial.
+ // In other words, it just includes the values being modified.
+ // In the same time, there are dependencies between various
+ // DHCP configuration parsers. For example: the option value can
+ // be set if the definition of this option is set. If someone removes
+ // an existing option definition then the partial configuration that
+ // removes that definition is triggered while a relevant option value
+ // may remain configured. This eventually results in the DHCP server
+ // configuration being in the inconsistent state.
+ // In order to work around this problem we need to merge the new
+ // configuration with the existing (full) configuration.
+
+ // Let's create a new object that will hold the merged configuration.
+ boost::shared_ptr<MapElement> merged_config(new MapElement());
+ // Let's get the existing configuration.
+ ConstElementPtr full_config = server_->config_session_->getFullConfig();
+ // The full_config and merged_config should be always non-NULL
+ // but to provide some level of exception safety we check that they
+ // really are (in case we go out of memory).
+ if (full_config && merged_config) {
+ merged_config->setValue(full_config->mapValue());
+
+ // Merge an existing and new configuration.
+ isc::data::merge(merged_config, new_config);
+ LOG_DEBUG(dhcp6_logger, DBG_DHCP6_COMMAND, DHCP6_CONFIG_UPDATE)
+ .arg(merged_config->str());
+ }
+
+ // Configure the server.
+ return (configureDhcp6Server(*server_, merged_config));
}
ConstElementPtr
@@ -109,18 +151,26 @@ void ControlledDhcpv6Srv::establishSession() {
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_CCSESSION_STARTING)
.arg(specfile);
cc_session_ = new Session(io_service_.get_io_service());
+ // Create a session with the dummy configuration handler.
+ // Dumy configuration handler is internally invoked by the
+ // constructor and on success the constructor updates
+ // the current session with the configuration that had been
+ // commited in the previous session. If we did not install
+ // the dummy handler, the previous configuration would have
+ // been lost.
config_session_ = new ModuleCCSession(specfile, *cc_session_,
- NULL,
+ dhcp6StubConfigHandler,
dhcp6CommandHandler, false);
config_session_->start();
- // We initially create ModuleCCSession() without configHandler, as
- // the session module is too eager to send partial configuration.
- // We want to get the full configuration, so we explicitly call
- // getFullConfig() and then pass it to our configHandler.
+ // The constructor already pulled the configuration that had
+ // been created in the previous session thanks to the dummy
+ // handler. We can switch to the handler that will be
+ // parsing future changes to the configuration.
config_session_->setConfigHandler(dhcp6ConfigHandler);
try {
+ // Pull the full configuration out from the session.
configureDhcp6Server(*this, config_session_->getFullConfig());
} catch (const DhcpConfigError& ex) {
LOG_ERROR(dhcp6_logger, DHCP6_CONFIG_LOAD_FAIL).arg(ex.what());
@@ -150,8 +200,8 @@ void ControlledDhcpv6Srv::disconnectSession() {
IfaceMgr::instance().set_session_socket(IfaceMgr::INVALID_SOCKET, NULL);
}
-ControlledDhcpv6Srv::ControlledDhcpv6Srv(uint16_t port, const char* dbconfig)
- : Dhcpv6Srv(port, dbconfig), cc_session_(NULL), config_session_(NULL) {
+ControlledDhcpv6Srv::ControlledDhcpv6Srv(uint16_t port)
+ : Dhcpv6Srv(port), cc_session_(NULL), config_session_(NULL) {
server_ = this; // remember this instance for use in callback
}
diff --git a/src/bin/dhcp6/ctrl_dhcp6_srv.h b/src/bin/dhcp6/ctrl_dhcp6_srv.h
index ef1acab..e3452bb 100644
--- a/src/bin/dhcp6/ctrl_dhcp6_srv.h
+++ b/src/bin/dhcp6/ctrl_dhcp6_srv.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
@@ -41,9 +41,7 @@ public:
/// @brief Constructor
///
/// @param port UDP port to be opened for DHCP traffic
- /// @param dbconfig Lease manager database configuration string
- ControlledDhcpv6Srv(uint16_t port = DHCP6_SERVER_PORT,
- const char* dbconfig = "type=memfile");
+ ControlledDhcpv6Srv(uint16_t port = DHCP6_SERVER_PORT);
/// @brief Destructor.
~ControlledDhcpv6Srv();
@@ -94,6 +92,27 @@ protected:
static isc::data::ConstElementPtr
dhcp6ConfigHandler(isc::data::ConstElementPtr new_config);
+ /// @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
+ /// 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
+ /// the previous session. The \ref dhcp6ConfigHandler can't be
+ /// used to parse the initial configuration because it needs the
+ /// full configuration to satisfy dependencies between the
+ /// various configuration values. Installing the dummy handler
+ /// that guarantees to return success causes initial configuration
+ /// to be stored for the session being created and that it can
+ /// be later accessed with \ref isc::ConfigData::getFullConfig.
+ ///
+ /// @param new_config new configuration.
+ ///
+ /// @return success configuration status.
+ static isc::data::ConstElementPtr
+ dhcp6StubConfigHandler(isc::data::ConstElementPtr new_config);
+
/// @brief A callback for handling incoming commands.
///
/// @param command textual representation of the command
diff --git a/src/bin/dhcp6/dhcp6.spec b/src/bin/dhcp6/dhcp6.spec
index b719ebb..1129aec 100644
--- a/src/bin/dhcp6/dhcp6.spec
+++ b/src/bin/dhcp6/dhcp6.spec
@@ -61,13 +61,13 @@
{ "item_name": "code",
"item_type": "integer",
"item_optional": false,
- "item_default": 0,
+ "item_default": 0
},
{ "item_name": "type",
"item_type": "string",
"item_optional": false,
- "item_default": "",
+ "item_default": ""
},
{ "item_name": "array",
@@ -76,16 +76,22 @@
"item_default": False
},
- { "item_name": "record_types",
+ { "item_name": "record-types",
"item_type": "string",
"item_optional": false,
- "item_default": "",
+ "item_default": ""
},
{ "item_name": "space",
"item_type": "string",
"item_optional": false,
"item_default": ""
+ },
+
+ { "item_name": "encapsulate",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
} ]
}
},
@@ -131,6 +137,44 @@
}
},
+ { "item_name": "lease-database",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {"type": "memfile"},
+ "map_item_spec": [
+ {
+ "item_name": "type",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ },
+ {
+ "item_name": "name",
+ "item_type": "string",
+ "item_optional": true,
+ "item_default": ""
+ },
+ {
+ "item_name": "user",
+ "item_type": "string",
+ "item_optional": true,
+ "item_default": ""
+ },
+ {
+ "item_name": "host",
+ "item_type": "string",
+ "item_optional": true,
+ "item_default": ""
+ },
+ {
+ "item_name": "password",
+ "item_type": "string",
+ "item_optional": true,
+ "item_default": ""
+ }
+ ]
+ },
+
{ "item_name": "subnet6",
"item_type": "list",
"item_optional": false,
@@ -149,6 +193,12 @@
"item_default": ""
},
+ { "item_name": "interface",
+ "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_messages.mes b/src/bin/dhcp6/dhcp6_messages.mes
index 83f75d9..65ad74b 100644
--- a/src/bin/dhcp6/dhcp6_messages.mes
+++ b/src/bin/dhcp6/dhcp6_messages.mes
@@ -172,6 +172,43 @@ This error is output if the server failed to assemble the data to be
returned to the client into a valid packet. The reason is most likely
to be to a programming error: please raise a bug report.
+% DHCP6_PARSER_COMMIT_EXCEPTION parser failed to commit changes
+On receipt of message containing details to a change of the IPv6 DHCP
+server configuration, a set of parsers were successfully created, but one
+of them failed to commit its changes due to a low-level system exception
+being raised. Additional messages may be output indicating the reason.
+
+% DHCP6_PARSER_COMMIT_FAIL parser failed to commit changes: %1
+On receipt of message containing details to a change of the IPv6 DHCP
+server configuration, a set of parsers were successfully created, but
+one of them failed to commit its changes. The reason for the failure
+is given in the message.
+
+% DHCP6_PARSER_CREATED created parser for configuration element %1
+A debug message output during a configuration update of the IPv6 DHCP
+server, notifying that the parser for the specified configuration element
+has been successfully created.
+
+% DHCP6_PARSER_FAIL failed to create or run parser for configuration element %1: %2
+On receipt of message containing details to a change of its configuration,
+the IPv6 DHCP server failed to create a parser to decode the contents
+of the named configuration element, or the creation succeeded but the
+parsing actions and committal of changes failed. The reason for the
+failure is given in the message.
+
+% DHCP6_PARSER_EXCEPTION failed to create or run parser for configuration element %1
+On receipt of message containing details to a change of its configuration,
+the IPv6 DHCP server failed to create a parser to decode the contents of
+the named configuration element, or the creation succeeded but the parsing
+actions and committal of changes failed. The message has been output in
+response to a non-BIND 10 exception being raised. Additional messages
+may give further information.
+
+The most likely cause of this is that the specification file for the server
+(which details the allowable contents of the configuration) is not correct for
+this version of BIND 10. This former may be the result of an interrupted
+installation of an update to BIND 10.
+
% DHCP6_PROCESS_IA_NA_REQUEST server is processing IA_NA option (duid=%1, iaid=%2, hint=%3)
This is a debug message that indicates a processing of received IA_NA
option. It may optionally contain an address that may be used by the server
@@ -194,6 +231,34 @@ A debug message listing the data returned to the client.
The IPv6 DHCP server has encountered a fatal error and is terminating.
The reason for the failure is included in the message.
+% DHCP6_SERVERID_GENERATED server-id %1 has been generated and will be stored in %2
+This informational messages indicates that the server was not able to read
+its server identifier (DUID) and has generated a new one. This server-id
+will be stored in a file and will be read and used during next restart. It
+is normal behavior when the server is started for the first time. If
+this message is printed every start, please check that the server have
+sufficient permission to write its server-id file and that the file is not
+corrupt.
+
+Changing the server identifier in a production environment is not
+recommended as existing clients will not recognize the server and may go
+through a rebind phase. However, they should be able to recover without
+losing their leases.
+
+% DHCP6_SERVERID_LOADED server-id %1 has been loaded from file %2
+This debug message indicates that the server loaded its server identifier.
+That value is sent in all server responses and clients use it to
+discriminate between servers. This is a part of normal startup or
+reconfiguration procedure.
+
+% DHCP6_SERVERID_WRITE_FAIL server was not able to write its ID to file %1
+This warning message indicates that server was not able to write its
+server identifier (DUID) to a file. This likely indicates lack of write
+permission to a given file or directory. This is not cricital and the
+server will continue to operate, but server will generate different DUID
+during every start and clients will need to go through a rebind phase
+to recover.
+
% DHCP6_SESSION_FAIL failed to establish BIND 10 session (%1), running stand-alone
The server has failed to establish communication with the rest of BIND
10 and is running in stand-alone mode. (This behavior will change once
diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc
index 18acd76..0468213 100644
--- a/src/bin/dhcp6/dhcp6_srv.cc
+++ b/src/bin/dhcp6/dhcp6_srv.cc
@@ -36,11 +36,16 @@
#include <exceptions/exceptions.h>
#include <util/io_utilities.h>
#include <util/range_utilities.h>
+#include <util/encode/hex.h>
#include <boost/foreach.hpp>
+#include <boost/tokenizer.hpp>
+#include <boost/algorithm/string/erase.hpp>
#include <stdlib.h>
#include <time.h>
+#include <iomanip>
+#include <fstream>
using namespace isc;
using namespace isc::asiolink;
@@ -51,7 +56,7 @@ using namespace std;
namespace isc {
namespace dhcp {
-Dhcpv6Srv::Dhcpv6Srv(uint16_t port, const char* dbconfig)
+Dhcpv6Srv::Dhcpv6Srv(uint16_t port)
: alloc_engine_(), serverid_(), shutdown_(true) {
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_OPEN_SOCKET).arg(port);
@@ -70,13 +75,22 @@ Dhcpv6Srv::Dhcpv6Srv(uint16_t port, const char* dbconfig)
IfaceMgr::instance().openSockets6(port);
}
- setServerID();
+ string duid_file = CfgMgr::instance().getDataDir() + "/" + string(SERVER_DUID_FILE);
+ if (loadServerID(duid_file)) {
+ LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_SERVERID_LOADED)
+ .arg(duid_file);
+ } else {
+ generateServerID();
+ LOG_INFO(dhcp6_logger, DHCP6_SERVERID_GENERATED)
+ .arg(duidToString(getServerID()))
+ .arg(duid_file);
+
+ if (!writeServerID(duid_file)) {
+ LOG_WARN(dhcp6_logger, DHCP6_SERVERID_WRITE_FAIL)
+ .arg(duid_file);
+ }
- // Instantiate LeaseMgr
- LeaseMgrFactory::create(dbconfig);
- LOG_INFO(dhcp6_logger, DHCP6_DB_BACKEND_STARTED)
- .arg(LeaseMgrFactory::instance().getType())
- .arg(LeaseMgrFactory::instance().getName());
+ }
// Instantiate allocation engine
alloc_engine_.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100));
@@ -191,7 +205,7 @@ bool Dhcpv6Srv::run() {
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL_DATA,
DHCP6_RESPONSE_DATA)
- .arg(rsp->getType()).arg(rsp->toText());
+ .arg(static_cast<int>(rsp->getType())).arg(rsp->toText());
if (rsp->pack()) {
try {
@@ -209,10 +223,67 @@ bool Dhcpv6Srv::run() {
return (true);
}
-void Dhcpv6Srv::setServerID() {
+bool Dhcpv6Srv::loadServerID(const std::string& file_name) {
+
+ // load content of the file into a string
+ fstream f(file_name.c_str(), ios::in);
+ if (!f.is_open()) {
+ return (false);
+ }
+
+ string hex_string;
+ f >> hex_string;
+ f.close();
+
+ // remove any spaces
+ boost::algorithm::erase_all(hex_string, " ");
+
+ // now remove :
+ /// @todo: We should check first if the format is sane.
+ /// Otherwise 1:2:3:4 will be converted to 0x12, 0x34
+ boost::algorithm::erase_all(hex_string, ":");
+
+ std::vector<uint8_t> bin;
- /// @todo: DUID should be generated once and then stored, rather
- /// than generated each time
+ // Decode the hex string and store it in bin (which happens
+ // to be OptionBuffer format)
+ isc::util::encode::decodeHex(hex_string, bin);
+
+ // Now create server-id option
+ serverid_.reset(new Option(Option::V6, D6O_SERVERID, bin));
+
+ return (true);
+}
+
+std::string Dhcpv6Srv::duidToString(const OptionPtr& opt) {
+ stringstream tmp;
+
+ OptionBuffer data = opt->getData();
+
+ bool colon = false;
+ for (OptionBufferConstIter it = data.begin(); it != data.end(); ++it) {
+ if (colon) {
+ tmp << ":";
+ }
+ tmp << hex << setw(2) << setfill('0') << static_cast<uint16_t>(*it);
+ if (!colon) {
+ colon = true;
+ }
+ }
+
+ return tmp.str();
+}
+
+bool Dhcpv6Srv::writeServerID(const std::string& file_name) {
+ fstream f(file_name.c_str(), ios::out | ios::trunc);
+ if (!f.good()) {
+ return (false);
+ }
+ f << duidToString(getServerID());
+ f.close();
+}
+
+void Dhcpv6Srv::generateServerID() {
/// @todo: This code implements support for DUID-LLT (the recommended one).
/// We should eventually add support for other DUID types: DUID-LL, DUID-EN
@@ -340,14 +411,9 @@ void Dhcpv6Srv::appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer)
}
// Get the list of options that client requested.
const std::vector<uint16_t>& requested_opts = option_oro->getValues();
- // Get the list of options configured for a subnet.
- Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6");
- const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
- // Try to match requested options with those configured for a subnet.
- // If match is found, append configured option to the answer message.
BOOST_FOREACH(uint16_t opt, requested_opts) {
- const Subnet::OptionContainerTypeRange& range = idx.equal_range(opt);
- BOOST_FOREACH(Subnet::OptionDescriptor desc, range) {
+ Subnet::OptionDescriptor desc = subnet->getOptionDescriptor("dhcp6", opt);
+ if (desc.option) {
answer->addOption(desc.option);
}
}
@@ -428,7 +494,17 @@ void Dhcpv6Srv::sanityCheck(const Pkt6Ptr& pkt, RequirementLevel clientid,
}
Subnet6Ptr Dhcpv6Srv::selectSubnet(const Pkt6Ptr& question) {
- Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(question->getRemoteAddr());
+
+ /// @todo: pass interface information only if received direct (non-relayed) message
+
+ // 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 no subnet was found, try to find it based on remote address
+ subnet = CfgMgr::instance().getSubnet6(question->getRemoteAddr());
return (subnet);
}
diff --git a/src/bin/dhcp6/dhcp6_srv.h b/src/bin/dhcp6/dhcp6_srv.h
index 30abb50..7c6f77b 100644
--- a/src/bin/dhcp6/dhcp6_srv.h
+++ b/src/bin/dhcp6/dhcp6_srv.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
@@ -31,6 +31,16 @@
namespace isc {
namespace dhcp {
+/// @brief file name of a server-id file
+///
+/// Server must store its duid in persistent storage that must not change
+/// between restarts. This is name of the file that is created in dataDir
+/// (see isc::dhcp::CfgMgr::getDataDir()). It is a text file that uses
+/// double digit hex values separated by colons format, e.g.
+/// 01:ff:02:03:06:80:90:ab:cd:ef. Server will create it during first
+/// run and then use it afterwards.
+static const char* SERVER_DUID_FILE = "b10-dhcp6-serverid";
+
/// @brief DHCPv6 server service.
///
/// This class represents DHCPv6 server. It contains all
@@ -64,10 +74,7 @@ public:
/// old or create new DUID.
///
/// @param port port on will all sockets will listen
- /// @param dbconfig Lease manager configuration string. The default
- /// of the "memfile" manager is used for testing.
- Dhcpv6Srv(uint16_t port = DHCP6_SERVER_PORT,
- const char* dbconfig = "type=memfile");
+ Dhcpv6Srv(uint16_t port = DHCP6_SERVER_PORT);
/// @brief Destructor. Used during DHCPv6 service shutdown.
virtual ~Dhcpv6Srv();
@@ -290,15 +297,39 @@ protected:
/// @brief Sets server-identifier.
///
- /// This method attempts to set server-identifier DUID. It loads it
- /// from a file. If file load fails, it generates new DUID using
- /// interface link-layer addresses (EUI-64) + timestamp (DUID type
- /// duid-llt, see RFC3315, section 9.2). If there are no suitable
+ /// This method attempts to generate server-identifier DUID. It generates a
+ /// new DUID using interface link-layer addresses (EUI-64) + timestamp (DUID
+ /// type duid-llt, see RFC3315, section 9.2). If there are no suitable
/// interfaces present, exception it thrown
///
/// @throws isc::Unexpected Failed to read DUID file and no suitable
/// interfaces for new DUID generation are detected.
- void setServerID();
+ void generateServerID();
+
+ /// @brief attempts to load DUID from a file
+ ///
+ /// Tries to load duid from a text file. If the load is successful,
+ /// it creates server-id option and stores it in serverid_ (to be used
+ /// later by getServerID()).
+ ///
+ /// @param file_name name of the DUID file to load
+ /// @return true if load was successful, false otherwise
+ bool loadServerID(const std::string& file_name);
+
+ /// @brief attempts to write DUID to a file
+ /// Tries to write duid content (stored in serverid_) to a text file.
+ ///
+ /// @param file_name name of the DUID file to write
+ /// @return true if write was successful, false otherwise
+ bool writeServerID(const std::string& file_name);
+
+ /// @brief converts DUID to text
+ /// Converts content of DUID option to a text representation, e.g.
+ /// 01:ff:02:03:06:80:90:ab:cd:ef
+ ///
+ /// @param opt option that contains DUID
+ /// @return string representation
+ static std::string duidToString(const OptionPtr& opt);
private:
/// @brief Allocation Engine.
diff --git a/src/bin/dhcp6/main.cc b/src/bin/dhcp6/main.cc
index ced7422..269eae4 100644
--- a/src/bin/dhcp6/main.cc
+++ b/src/bin/dhcp6/main.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
@@ -36,17 +36,6 @@ using namespace std;
/// Dhcpv6Srv and other classes, see \ref dhcpv6Session.
namespace {
-// @todo: Replace the next line by extraction from configuration parameters
-// This is the "dbconfig" string for the MySQL database. It is likely
-// that a long-term solution will be to create the instance of the lease manager
-// somewhere other than the Dhcpv6Srv constructor, to give time to extract
-// the connection string from the configuration database.
-#ifdef HAVE_MYSQL
-const char* DBCONFIG = "type=mysql name=kea user=kea password=kea host=localhost";
-#else
-const char* DBCONFIG = "type=memfile";
-#endif
-
const char* const DHCP6_NAME = "b10-dhcp6";
void
@@ -115,7 +104,7 @@ main(int argc, char* argv[]) {
int ret = EXIT_SUCCESS;
try {
- ControlledDhcpv6Srv server(port_number, DBCONFIG);
+ ControlledDhcpv6Srv server(port_number);
if (!stand_alone) {
try {
server.establishSession();
diff --git a/src/bin/dhcp6/tests/config_parser_unittest.cc b/src/bin/dhcp6/tests/config_parser_unittest.cc
index b6ef97f..158ad62 100644
--- a/src/bin/dhcp6/tests/config_parser_unittest.cc
+++ b/src/bin/dhcp6/tests/config_parser_unittest.cc
@@ -17,6 +17,9 @@
#include <config/ccsession.h>
#include <dhcp/libdhcp++.h>
#include <dhcp/option6_ia.h>
+#include <dhcp/iface_mgr.h>
+#include <dhcp/option_custom.h>
+#include <dhcp/option_int.h>
#include <dhcp6/config_parser.h>
#include <dhcp6/dhcp6_srv.h>
#include <dhcpsrv/cfgmgr.h>
@@ -46,6 +49,23 @@ public:
// srv_(0) means to not open any sockets. We don't want to
// deal with sockets here, just check if configuration handling
// is sane.
+
+ const IfaceMgr::IfaceCollection& ifaces = IfaceMgr::instance().getIfaces();
+
+ // There must be some interface detected
+ if (ifaces.empty()) {
+ // We can't use ASSERT in constructor
+ ADD_FAILURE() << "No interfaces detected.";
+ }
+
+ valid_iface_ = ifaces.begin()->getName();
+ bogus_iface_ = "nonexisting0";
+
+ if (IfaceMgr::instance().getIface(bogus_iface_)) {
+ ADD_FAILURE() << "The '" << bogus_iface_ << "' exists on this system"
+ << " while the test assumes that it doesn't, to execute"
+ << " some negative scenarios. Can't continue this test.";
+ }
}
~Dhcp6ParserTest() {
@@ -261,6 +281,9 @@ public:
int rcode_;
ConstElementPtr comment_;
+
+ string valid_iface_;
+ string bogus_iface_;
};
// Goal of this test is a verification if a very simple config update
@@ -294,9 +317,8 @@ TEST_F(Dhcp6ParserTest, bogusCommand) {
EXPECT_EQ(1, rcode_);
}
-/// The goal of this test is to verify if wrongly defined subnet will
-/// be rejected. Properly defined subnet must include at least one
-/// pool definition.
+/// The goal of this test is to verify if configuration without any
+/// subnets defined can be accepted.
TEST_F(Dhcp6ParserTest, emptySubnet) {
ConstElementPtr status;
@@ -329,7 +351,6 @@ TEST_F(Dhcp6ParserTest, subnetGlobalDefaults) {
" \"pool\": [ \"2001:db8:1::1 - 2001:db8:1::ffff\" ],"
" \"subnet\": \"2001:db8:1::/64\" } ],"
"\"valid-lifetime\": 4000 }";
- cout << config << endl;
ElementPtr json = Element::fromJSON(config);
@@ -368,7 +389,6 @@ TEST_F(Dhcp6ParserTest, subnetLocal) {
" \"valid-lifetime\": 4,"
" \"subnet\": \"2001:db8:1::/64\" } ],"
"\"valid-lifetime\": 4000 }";
- cout << config << endl;
ElementPtr json = Element::fromJSON(config);
@@ -387,6 +407,99 @@ TEST_F(Dhcp6ParserTest, subnetLocal) {
EXPECT_EQ(4, subnet->getValid());
}
+// This test checks if it is possible to define a subnet with an
+// interface defined.
+TEST_F(Dhcp6ParserTest, subnetInterface) {
+
+ ConstElementPtr status;
+
+ // There should be at least one interface
+
+ string config = "{ \"interface\": [ \"all\" ],"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"pool\": [ \"2001:db8:1::1 - 2001:db8:1::ffff\" ],"
+ " \"interface\": \"" + valid_iface_ + "\","
+ " \"subnet\": \"2001:db8:1::/64\" } ],"
+ "\"valid-lifetime\": 4000 }";
+ cout << config << endl;
+
+ ElementPtr json = Element::fromJSON(config);
+
+ 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_);
+
+ Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+ ASSERT_TRUE(subnet);
+ EXPECT_EQ(valid_iface_, subnet->getIface());
+}
+
+// This test checks if invalid interface name will be rejected in
+// Subnet6 definition.
+TEST_F(Dhcp6ParserTest, subnetInterfaceBogus) {
+
+ ConstElementPtr status;
+
+ // There should be at least one interface
+
+ string config = "{ \"interface\": [ \"all\" ],"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"pool\": [ \"2001:db8:1::1 - 2001:db8:1::ffff\" ],"
+ " \"interface\": \"" + bogus_iface_ + "\","
+ " \"subnet\": \"2001:db8:1::/64\" } ],"
+ "\"valid-lifetime\": 4000 }";
+ cout << config << endl;
+
+ ElementPtr json = Element::fromJSON(config);
+
+ 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_);
+
+ Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+ EXPECT_FALSE(subnet);
+}
+
+
+// This test checks if it is not allowed to define global interface
+// parameter.
+TEST_F(Dhcp6ParserTest, interfaceGlobal) {
+
+ ConstElementPtr status;
+
+ string config = "{ \"interface\": [ \"all\" ],"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"interface\": \"" + valid_iface_ + "\"," // 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 }";
+ cout << config << endl;
+
+ ElementPtr json = Element::fromJSON(config);
+
+ 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_);
+}
+
// Test verifies that a subnet with pool values that do not belong to that
// pool are rejected.
TEST_F(Dhcp6ParserTest, poolOutOfSubnet) {
@@ -401,7 +514,7 @@ TEST_F(Dhcp6ParserTest, poolOutOfSubnet) {
" \"pool\": [ \"4001:db8:1::/80\" ],"
" \"subnet\": \"2001:db8:1::/64\" } ],"
"\"valid-lifetime\": 4000 }";
- cout << config << endl;
+
ElementPtr json = Element::fromJSON(config);
@@ -411,7 +524,6 @@ TEST_F(Dhcp6ParserTest, poolOutOfSubnet) {
// as the pool does not belong to that subnet
ASSERT_TRUE(status);
comment_ = parseAnswer(rcode_, status);
-
EXPECT_EQ(1, rcode_);
}
@@ -430,7 +542,6 @@ TEST_F(Dhcp6ParserTest, poolPrefixLen) {
" \"pool\": [ \"2001:db8:1::/80\" ],"
" \"subnet\": \"2001:db8:1::/64\" } ],"
"\"valid-lifetime\": 4000 }";
- cout << config << endl;
ElementPtr json = Element::fromJSON(config);
@@ -461,7 +572,8 @@ TEST_F(Dhcp6ParserTest, optionDefIpv6Address) {
" \"type\": \"ipv6-address\","
" \"array\": False,"
" \"record-types\": \"\","
- " \"space\": \"isc\""
+ " \"space\": \"isc\","
+ " \"encapsulate\": \"\""
" } ]"
"}";
ElementPtr json = Element::fromJSON(config);
@@ -499,7 +611,8 @@ TEST_F(Dhcp6ParserTest, optionDefRecord) {
" \"type\": \"record\","
" \"array\": False,"
" \"record-types\": \"uint16, ipv4-address, ipv6-address, string\","
- " \"space\": \"isc\""
+ " \"space\": \"isc\","
+ " \"encapsulate\": \"\""
" } ]"
"}";
ElementPtr json = Element::fromJSON(config);
@@ -546,7 +659,8 @@ TEST_F(Dhcp6ParserTest, optionDefMultiple) {
" \"type\": \"uint32\","
" \"array\": False,"
" \"record-types\": \"\","
- " \"space\": \"isc\""
+ " \"space\": \"isc\","
+ " \"encapsulate\": \"\""
" },"
" {"
" \"name\": \"foo-2\","
@@ -554,7 +668,8 @@ TEST_F(Dhcp6ParserTest, optionDefMultiple) {
" \"type\": \"ipv4-address\","
" \"array\": False,"
" \"record-types\": \"\","
- " \"space\": \"isc\""
+ " \"space\": \"isc\","
+ " \"encapsulate\": \"\""
" } ]"
"}";
ElementPtr json = Element::fromJSON(config);
@@ -604,7 +719,8 @@ TEST_F(Dhcp6ParserTest, optionDefDuplicate) {
" \"type\": \"uint32\","
" \"array\": False,"
" \"record-types\": \"\","
- " \"space\": \"isc\""
+ " \"space\": \"isc\","
+ " \"encapsulate\": \"\""
" },"
" {"
" \"name\": \"foo-2\","
@@ -612,7 +728,8 @@ TEST_F(Dhcp6ParserTest, optionDefDuplicate) {
" \"type\": \"ipv4-address\","
" \"array\": False,"
" \"record-types\": \"\","
- " \"space\": \"isc\""
+ " \"space\": \"isc\","
+ " \"encapsulate\": \"\""
" } ]"
"}";
ElementPtr json = Element::fromJSON(config);
@@ -640,7 +757,8 @@ TEST_F(Dhcp6ParserTest, optionDefArray) {
" \"type\": \"uint32\","
" \"array\": True,"
" \"record-types\": \"\","
- " \"space\": \"isc\""
+ " \"space\": \"isc\","
+ " \"encapsulate\": \"\""
" } ]"
"}";
ElementPtr json = Element::fromJSON(config);
@@ -666,6 +784,47 @@ TEST_F(Dhcp6ParserTest, optionDefArray) {
EXPECT_TRUE(def->getArrayType());
}
+// The purpose of this test to verify that encapsulated option
+// space name may be specified.
+TEST_F(Dhcp6ParserTest, optionDefEncapsulate) {
+
+ // Configuration string. Included the encapsulated
+ // option space name.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"uint32\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\","
+ " \"encapsulate\": \"sub-opts-space\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Make sure that the particular option definition does not exist.
+ OptionDefinitionPtr def = CfgMgr::instance().getOptionDef("isc", 100);
+ ASSERT_FALSE(def);
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // The option definition should now be available in the CfgMgr.
+ def = CfgMgr::instance().getOptionDef("isc", 100);
+ ASSERT_TRUE(def);
+
+ // Check the option data.
+ EXPECT_EQ("foo", def->getName());
+ EXPECT_EQ(100, def->getCode());
+ EXPECT_EQ(OPT_UINT32_TYPE, def->getType());
+ EXPECT_FALSE(def->getArrayType());
+ EXPECT_EQ("sub-opts-space", def->getEncapsulatedSpace());
+}
+
/// The purpose of this test is to verify that the option definition
/// with invalid name is not accepted.
TEST_F(Dhcp6ParserTest, optionDefInvalidName) {
@@ -678,7 +837,8 @@ TEST_F(Dhcp6ParserTest, optionDefInvalidName) {
" \"type\": \"string\","
" \"array\": False,"
" \"record-types\": \"\","
- " \"space\": \"isc\""
+ " \"space\": \"isc\","
+ " \"encapsulate\": \"\""
" } ]"
"}";
ElementPtr json = Element::fromJSON(config);
@@ -703,7 +863,8 @@ TEST_F(Dhcp6ParserTest, optionDefInvalidType) {
" \"type\": \"sting\","
" \"array\": False,"
" \"record-types\": \"\","
- " \"space\": \"isc\""
+ " \"space\": \"isc\","
+ " \"encapsulate\": \"\""
" } ]"
"}";
ElementPtr json = Element::fromJSON(config);
@@ -728,7 +889,8 @@ TEST_F(Dhcp6ParserTest, optionDefInvalidRecordType) {
" \"type\": \"record\","
" \"array\": False,"
" \"record-types\": \"uint32,uint8,sting\","
- " \"space\": \"isc\""
+ " \"space\": \"isc\","
+ " \"encapsulate\": \"\""
" } ]"
"}";
ElementPtr json = Element::fromJSON(config);
@@ -741,6 +903,85 @@ TEST_F(Dhcp6ParserTest, optionDefInvalidRecordType) {
checkResult(status, 1);
}
+/// The goal of this test is to verify that the invalid encapsulated
+/// option space name is not accepted.
+TEST_F(Dhcp6ParserTest, optionDefInvalidEncapsulatedSpace) {
+ // Configuration string. The encapsulated option space
+ // name is invalid (% character is not allowed).
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"uint32\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\","
+ " \"encapsulate\": \"invalid%space%name\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+ ASSERT_TRUE(status);
+ // Expecting parsing error (error code 1).
+ checkResult(status, 1);
+}
+
+/// The goal of this test is to verify that the encapsulated
+/// option space name can't be specified for the option that
+/// comprises an array of data fields.
+TEST_F(Dhcp6ParserTest, optionDefEncapsulatedSpaceAndArray) {
+ // Configuration string. The encapsulated option space
+ // name is set to non-empty value and the array flag
+ // is set.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"uint32\","
+ " \"array\": True,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\","
+ " \"encapsulate\": \"valid-space-name\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+ ASSERT_TRUE(status);
+ // Expecting parsing error (error code 1).
+ checkResult(status, 1);
+}
+
+/// The goal of this test is to verify that the option may not
+/// encapsulate option space it belongs to.
+TEST_F(Dhcp6ParserTest, optionDefEncapsulateOwnSpace) {
+ // Configuration string. Option is set to encapsulate
+ // option space it belongs to.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"uint32\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\","
+ " \"encapsulate\": \"isc\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+ ASSERT_TRUE(status);
+ // Expecting parsing error (error code 1).
+ checkResult(status, 1);
+}
/// The purpose of this test is to verify that it is not allowed
/// to override the standard option (that belongs to dhcp6 option
@@ -759,7 +1000,8 @@ TEST_F(Dhcp6ParserTest, optionStandardDefOverride) {
" \"type\": \"string\","
" \"array\": False,"
" \"record-types\": \"\","
- " \"space\": \"dhcp6\""
+ " \"space\": \"dhcp6\","
+ " \"encapsulate\": \"\""
" } ]"
"}";
ElementPtr json = Element::fromJSON(config);
@@ -794,7 +1036,8 @@ TEST_F(Dhcp6ParserTest, optionStandardDefOverride) {
" \"type\": \"string\","
" \"array\": False,"
" \"record-types\": \"\","
- " \"space\": \"dhcp6\""
+ " \"space\": \"dhcp6\","
+ " \"encapsulate\": \"\""
" } ]"
"}";
json = Element::fromJSON(config);
@@ -916,7 +1159,8 @@ TEST_F(Dhcp6ParserTest, optionDataTwoSpaces) {
" \"type\": \"uint32\","
" \"array\": False,"
" \"record-types\": \"\","
- " \"space\": \"isc\""
+ " \"space\": \"isc\","
+ " \"encapsulate\": \"\""
" } ],"
"\"subnet6\": [ { "
" \"pool\": [ \"2001:db8:1::/80\" ],"
@@ -949,81 +1193,164 @@ TEST_F(Dhcp6ParserTest, optionDataTwoSpaces) {
ASSERT_FALSE(desc3.option);
}
-// The goal of this test is to verify options configuration
-// for a single subnet. In particular this test checks
-// that local options configuration overrides global
-// option setting.
-TEST_F(Dhcp6ParserTest, optionDataInSingleSubnet) {
- ConstElementPtr x;
+// The goal of this test is to verify that it is possible to
+// encapsulate option space containing some options with
+// another option. In this test we create base option that
+// encapsulates option space 'isc' that comprises two other
+// options. Also, for all options their definitions are
+// created.
+TEST_F(Dhcp6ParserTest, optionDataEncapsulate) {
+
+ // @todo DHCP configurations has many dependencies between
+ // parameters. First of all, configuration for subnet is
+ // inherited from the global values. Thus subnet has to be
+ // configured when all global values have been configured.
+ // Also, an option can encapsulate another option only
+ // if the latter has been configured. For this reason in this
+ // test we created two-stage configuration where first we
+ // created options that belong to encapsulated option space.
+ // In the second stage we add the base option. Also, the Subnet
+ // object is configured in the second stage so it is created
+ // at the very end (when all other parameters are configured).
+
+ // Starting stage 1. Configure sub-options and their definitions.
string config = "{ \"interface\": [ \"all\" ],"
- "\"preferred-lifetime\": 3000,"
- "\"rebind-timer\": 2000, "
- "\"renew-timer\": 1000, "
+ "\"rebind-timer\": 2000,"
+ "\"renew-timer\": 1000,"
"\"option-data\": [ {"
- " \"name\": \"subscriber-id\","
- " \"space\": \"dhcp6\","
- " \"code\": 38,"
- " \"data\": \"AB\","
- " \"csv-format\": False"
+ " \"name\": \"foo\","
+ " \"space\": \"isc\","
+ " \"code\": 110,"
+ " \"data\": \"1234\","
+ " \"csv-format\": True"
+ " },"
+ " {"
+ " \"name\": \"foo2\","
+ " \"space\": \"isc\","
+ " \"code\": 111,"
+ " \"data\": \"192.168.2.1\","
+ " \"csv-format\": True"
+ " } ],"
+ "\"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 110,"
+ " \"type\": \"uint32\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\","
+ " \"encapsulate\": \"\""
+ " },"
+ " {"
+ " \"name\": \"foo2\","
+ " \"code\": 111,"
+ " \"type\": \"ipv4-address\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\","
+ " \"encapsulate\": \"\""
+ " } ]"
+ "}";
+
+ ConstElementPtr status;
+
+ ElementPtr json = Element::fromJSON(config);
+
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // Stage 2. Configure base option and a subnet. Please note that
+ // the configuration from the stage 2 is repeated because BIND
+ // configuration manager sends whole configuration for the lists
+ // where at least one element is being modified or added.
+ config = "{ \"interface\": [ \"all\" ],"
+ "\"rebind-timer\": 2000,"
+ "\"renew-timer\": 1000,"
+ "\"option-data\": [ {"
+ " \"name\": \"base-option\","
+ " \"space\": \"dhcp6\","
+ " \"code\": 100,"
+ " \"data\": \"11\","
+ " \"csv-format\": True"
+ " },"
+ " {"
+ " \"name\": \"foo\","
+ " \"space\": \"isc\","
+ " \"code\": 110,"
+ " \"data\": \"1234\","
+ " \"csv-format\": True"
+ " },"
+ " {"
+ " \"name\": \"foo2\","
+ " \"space\": \"isc\","
+ " \"code\": 111,"
+ " \"data\": \"192.168.2.1\","
+ " \"csv-format\": True"
+ " } ],"
+ "\"option-def\": [ {"
+ " \"name\": \"base-option\","
+ " \"code\": 100,"
+ " \"type\": \"uint8\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"dhcp6\","
+ " \"encapsulate\": \"isc\""
+ "},"
+ "{"
+ " \"name\": \"foo\","
+ " \"code\": 110,"
+ " \"type\": \"uint32\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\","
+ " \"encapsulate\": \"\""
+ " },"
+ " {"
+ " \"name\": \"foo2\","
+ " \"code\": 111,"
+ " \"type\": \"ipv4-address\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\","
+ " \"encapsulate\": \"\""
" } ],"
"\"subnet6\": [ { "
" \"pool\": [ \"2001:db8:1::/80\" ],"
- " \"subnet\": \"2001:db8:1::/64\", "
- " \"option-data\": [ {"
- " \"name\": \"subscriber-id\","
- " \"space\": \"dhcp6\","
- " \"code\": 38,"
- " \"data\": \"AB CDEF0105\","
- " \"csv-format\": False"
- " },"
- " {"
- " \"name\": \"preference\","
- " \"space\": \"dhcp6\","
- " \"code\": 7,"
- " \"data\": \"01\","
- " \"csv-format\": False"
- " } ]"
- " } ],"
- "\"valid-lifetime\": 4000 }";
+ " \"subnet\": \"2001:db8:1::/64\""
+ " } ]"
+ "}";
- ElementPtr json = Element::fromJSON(config);
- EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
- ASSERT_TRUE(x);
- comment_ = parseAnswer(rcode_, x);
- ASSERT_EQ(0, rcode_);
+ json = Element::fromJSON(config);
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // Get the subnet.
Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
ASSERT_TRUE(subnet);
- Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6");
- ASSERT_EQ(2, options->size());
- // Get the search index. Index #1 is to search using option code.
- const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
-
- // Get the options for specified index. Expecting one option to be
- // returned but in theory we may have multiple options with the same
- // code so we get the range.
- std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
- Subnet::OptionContainerTypeIndex::const_iterator> range =
- idx.equal_range(D6O_SUBSCRIBER_ID);
- // Expect single option with the code equal to 38.
- ASSERT_EQ(1, std::distance(range.first, range.second));
- const uint8_t subid_expected[] = {
- 0xAB, 0xCD, 0xEF, 0x01, 0x05
- };
- // Check if option is valid in terms of code and carried data.
- testOption(*range.first, D6O_SUBSCRIBER_ID, subid_expected,
- sizeof(subid_expected));
+ // We should have one option available.
+ Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6");
+ ASSERT_TRUE(options);
+ ASSERT_EQ(1, options->size());
- range = idx.equal_range(D6O_PREFERENCE);
- ASSERT_EQ(1, std::distance(range.first, range.second));
- // Do another round of testing with second option.
- const uint8_t pref_expected[] = {
- 0x01
- };
- testOption(*range.first, D6O_PREFERENCE, pref_expected,
- sizeof(pref_expected));
+ // Get the option.
+ Subnet::OptionDescriptor desc = subnet->getOptionDescriptor("dhcp6", 100);
+ EXPECT_TRUE(desc.option);
+ EXPECT_EQ(100, desc.option->getType());
+
+ // This opton should comprise two sub-options.
+ // Onf of them is 'foo' with code 110.
+ OptionPtr option_foo = desc.option->getOption(110);
+ ASSERT_TRUE(option_foo);
+ EXPECT_EQ(110, option_foo->getType());
+
+ // ...another one 'foo2' with code 111.
+ OptionPtr option_foo2 = desc.option->getOption(111);
+ ASSERT_TRUE(option_foo2);
+ EXPECT_EQ(111, option_foo2->getType());
}
// Goal of this test is to verify options configuration
@@ -1265,4 +1592,164 @@ TEST_F(Dhcp6ParserTest, stdOptionData) {
EXPECT_EQ(1516, optionIA->getT2());
}
+// The goal of this test is to verify that the standard option can
+// be configured to encapsulate multiple other options.
+TEST_F(Dhcp6ParserTest, stdOptionDataEncapsulate) {
+
+ // The configuration is two stage process in this test.
+ // In the first stahe we create definitions of suboptions
+ // that we will add to the base option.
+ // Let's create some dummy options: foo and foo2.
+ string config = "{ \"interface\": [ \"all\" ],"
+ "\"rebind-timer\": 2000,"
+ "\"renew-timer\": 1000,"
+ "\"option-data\": [ {"
+ " \"name\": \"foo\","
+ " \"space\": \"vendor-opts-space\","
+ " \"code\": 110,"
+ " \"data\": \"1234\","
+ " \"csv-format\": True"
+ " },"
+ " {"
+ " \"name\": \"foo2\","
+ " \"space\": \"vendor-opts-space\","
+ " \"code\": 111,"
+ " \"data\": \"192.168.2.1\","
+ " \"csv-format\": True"
+ " } ],"
+ "\"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 110,"
+ " \"type\": \"uint32\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"vendor-opts-space\","
+ " \"encapsulate\": \"\""
+ " },"
+ " {"
+ " \"name\": \"foo2\","
+ " \"code\": 111,"
+ " \"type\": \"ipv4-address\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"vendor-opts-space\","
+ " \"encapsulate\": \"\""
+ " } ]"
+ "}";
+
+ ConstElementPtr status;
+
+ ElementPtr json = Element::fromJSON(config);
+
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // Once the definitions have been added we can configure the
+ // standard option #17. This option comprises an enterprise
+ // number and sub options. By convention (introduced in
+ // std_option_defs.h) option named 'vendor-opts'
+ // encapsulates the option space named 'vendor-opts-space'.
+ // We add our dummy options to this option space and thus
+ // they should be included as sub-options in the 'vendor-opts'
+ // option.
+ config = "{ \"interface\": [ \"all\" ],"
+ "\"rebind-timer\": 2000,"
+ "\"renew-timer\": 1000,"
+ "\"option-data\": [ {"
+ " \"name\": \"vendor-opts\","
+ " \"space\": \"dhcp6\","
+ " \"code\": 17,"
+ " \"data\": \"1234\","
+ " \"csv-format\": True"
+ " },"
+ " {"
+ " \"name\": \"foo\","
+ " \"space\": \"vendor-opts-space\","
+ " \"code\": 110,"
+ " \"data\": \"1234\","
+ " \"csv-format\": True"
+ " },"
+ " {"
+ " \"name\": \"foo2\","
+ " \"space\": \"vendor-opts-space\","
+ " \"code\": 111,"
+ " \"data\": \"192.168.2.1\","
+ " \"csv-format\": True"
+ " } ],"
+ "\"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 110,"
+ " \"type\": \"uint32\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"vendor-opts-space\","
+ " \"encapsulate\": \"\""
+ " },"
+ " {"
+ " \"name\": \"foo2\","
+ " \"code\": 111,"
+ " \"type\": \"ipv4-address\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"vendor-opts-space\","
+ " \"encapsulate\": \"\""
+ " } ],"
+ "\"subnet6\": [ { "
+ " \"pool\": [ \"2001:db8:1::/80\" ],"
+ " \"subnet\": \"2001:db8:1::/64\""
+ " } ]"
+ "}";
+
+
+ json = Element::fromJSON(config);
+
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // Get the subnet.
+ Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+ ASSERT_TRUE(subnet);
+
+ // We should have one option available.
+ Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6");
+ ASSERT_TRUE(options);
+ ASSERT_EQ(1, options->size());
+
+ // Get the option.
+ Subnet::OptionDescriptor desc =
+ subnet->getOptionDescriptor("dhcp6", D6O_VENDOR_OPTS);
+ EXPECT_TRUE(desc.option);
+ EXPECT_EQ(D6O_VENDOR_OPTS, desc.option->getType());
+
+ // Option with the code 110 should be added as a sub-option.
+ OptionPtr option_foo = desc.option->getOption(110);
+ ASSERT_TRUE(option_foo);
+ EXPECT_EQ(110, option_foo->getType());
+ // This option comprises a single uint32_t value thus it is
+ // represented by OptionInt<uint32_t> class. Let's get the
+ // object of this type.
+ boost::shared_ptr<OptionInt<uint32_t> > option_foo_uint32 =
+ boost::dynamic_pointer_cast<OptionInt<uint32_t> >(option_foo);
+ ASSERT_TRUE(option_foo_uint32);
+ // Validate the value according to the configuration.
+ EXPECT_EQ(1234, option_foo_uint32->getValue());
+
+ // Option with the code 111 should be added as a sub-option.
+ OptionPtr option_foo2 = desc.option->getOption(111);
+ ASSERT_TRUE(option_foo2);
+ EXPECT_EQ(111, option_foo2->getType());
+ // This option comprises the IPV4 address. Such option is
+ // represented by OptionCustom object.
+ OptionCustomPtr option_foo2_v4 =
+ boost::dynamic_pointer_cast<OptionCustom>(option_foo2);
+ ASSERT_TRUE(option_foo2_v4);
+ // Get the IP address carried by this option and validate it.
+ EXPECT_EQ("192.168.2.1", option_foo2_v4->readAddress().toText());
+
+ // Option with the code 112 should not be added.
+ EXPECT_FALSE(desc.option->getOption(112));
+}
+
};
diff --git a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
index dee3de5..ef443e7 100644
--- a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
+++ b/src/bin/dhcp6/tests/dhcp6_srv_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 @@
#include <boost/scoped_ptr.hpp>
#include <gtest/gtest.h>
-
+#include <unistd.h>
#include <fstream>
#include <iostream>
#include <sstream>
@@ -55,7 +55,16 @@ namespace {
class NakedDhcpv6Srv: public Dhcpv6Srv {
// "naked" Interface Manager, exposes internal members
public:
- NakedDhcpv6Srv(uint16_t port):Dhcpv6Srv(port) { }
+ NakedDhcpv6Srv(uint16_t port) : Dhcpv6Srv(port) {
+ // Open the "memfile" database for leases
+ std::string memfile = "type=memfile";
+ LeaseMgrFactory::create(memfile);
+ }
+
+ virtual ~NakedDhcpv6Srv() {
+ // Close the lease database
+ LeaseMgrFactory::destroy();
+ }
using Dhcpv6Srv::processSolicit;
using Dhcpv6Srv::processRequest;
@@ -64,10 +73,16 @@ public:
using Dhcpv6Srv::createStatusCode;
using Dhcpv6Srv::selectSubnet;
using Dhcpv6Srv::sanityCheck;
+ using Dhcpv6Srv::loadServerID;
+ using Dhcpv6Srv::writeServerID;
};
+static const char* DUID_FILE = "server-id-test.txt";
+
class Dhcpv6SrvTest : 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,
@@ -77,6 +92,9 @@ public:
CfgMgr::instance().deleteSubnets6();
CfgMgr::instance().addSubnet6(subnet_);
+
+ // it's ok if that fails. There should not be such a file anyway
+ unlink(DUID_FILE);
}
// Generate IA_NA option with specified parameters
@@ -246,6 +264,9 @@ public:
~Dhcpv6SrvTest() {
CfgMgr::instance().deleteSubnets6();
+
+ // Let's clean up if there is such a file.
+ unlink(DUID_FILE);
};
// A subnet used in most tests
@@ -1296,6 +1317,151 @@ TEST_F(Dhcpv6SrvTest, sanityCheck) {
RFCViolation);
}
+// This test verifies if selectSubnet() selects proper subnet for a given
+// source address.
+TEST_F(Dhcpv6SrvTest, selectSubnetAddr) {
+ 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));
+
+ // CASE 1: We have only one subnet defined and we received local traffic.
+ // The only available subnet should be selected
+ CfgMgr::instance().deleteSubnets6();
+ CfgMgr::instance().addSubnet6(subnet1); // just a single subnet
+
+ Pkt6Ptr pkt = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+ pkt->setRemoteAddr(IOAddress("fe80::abcd"));
+
+ Subnet6Ptr selected = srv.selectSubnet(pkt);
+ EXPECT_EQ(selected, subnet1);
+
+ // CASE 2: We have only one subnet defined and we received relayed traffic.
+ // We should NOT select it.
+
+ // Identical steps as in case 1, but repeated for clarity
+ CfgMgr::instance().deleteSubnets6();
+ CfgMgr::instance().addSubnet6(subnet1); // just a single subnet
+ pkt->setRemoteAddr(IOAddress("2001:db8:abcd::2345"));
+ selected = srv.selectSubnet(pkt);
+ EXPECT_FALSE(selected);
+
+ // CASE 3: We have three subnets defined and we received local traffic.
+ // Nothing should be selected.
+ CfgMgr::instance().deleteSubnets6();
+ CfgMgr::instance().addSubnet6(subnet1);
+ CfgMgr::instance().addSubnet6(subnet2);
+ CfgMgr::instance().addSubnet6(subnet3);
+ pkt->setRemoteAddr(IOAddress("fe80::abcd"));
+ selected = srv.selectSubnet(pkt);
+ EXPECT_FALSE(selected);
+
+ // CASE 4: 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);
+ pkt->setRemoteAddr(IOAddress("2001:db8:2::baca"));
+ selected = srv.selectSubnet(pkt);
+ EXPECT_EQ(selected, subnet2);
+
+ // CASE 5: 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->setRemoteAddr(IOAddress("2001:db8:4::baca"));
+ selected = srv.selectSubnet(pkt);
+ EXPECT_FALSE(selected);
+
+}
+
+// This test verifies if selectSubnet() selects proper subnet for a given
+// network interface name.
+TEST_F(Dhcpv6SrvTest, selectSubnetIface) {
+ 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->setIface("eth0");
+ subnet3->setIface("wifi1");
+
+ // CASE 1: We have only one subnet defined and it is available via eth0.
+ // Packet came from eth0. The only available subnet should be selected
+ CfgMgr::instance().deleteSubnets6();
+ CfgMgr::instance().addSubnet6(subnet1); // just a single subnet
+
+ Pkt6Ptr pkt = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+ pkt->setIface("eth0");
+
+ Subnet6Ptr selected = srv.selectSubnet(pkt);
+ EXPECT_EQ(selected, subnet1);
+
+ // CASE 2: We have only one subnet defined and it is available via eth0.
+ // Packet came from eth1. We should not select it
+ CfgMgr::instance().deleteSubnets6();
+ CfgMgr::instance().addSubnet6(subnet1); // just a single subnet
+
+ pkt->setIface("eth1");
+
+ selected = srv.selectSubnet(pkt);
+ EXPECT_FALSE(selected);
+
+ // CASE 3: We have only 3 subnets defined, one over eth0, one remote and
+ // one over wifi1.
+ // Packet came from eth1. We should not select it
+ CfgMgr::instance().deleteSubnets6();
+ CfgMgr::instance().addSubnet6(subnet1);
+ CfgMgr::instance().addSubnet6(subnet2);
+ CfgMgr::instance().addSubnet6(subnet3);
+
+ pkt->setIface("eth0");
+ EXPECT_EQ(subnet1, srv.selectSubnet(pkt));
+
+ pkt->setIface("eth3"); // no such interface
+ EXPECT_EQ(Subnet6Ptr(), srv.selectSubnet(pkt)); // nothing selected
+
+ pkt->setIface("wifi1");
+ EXPECT_EQ(subnet3, srv.selectSubnet(pkt));
+}
+
+// This test verifies if the server-id disk operations (read, write) are
+// working properly.
+TEST_F(Dhcpv6SrvTest, ServerID) {
+ NakedDhcpv6Srv srv(0);
+
+ string duid1_text = "01:ff:02:03:06:80:90:ab:cd:ef";
+ uint8_t duid1[] = { 0x01, 0xff, 2, 3, 6, 0x80, 0x90, 0xab, 0xcd, 0xef };
+ OptionBuffer expected_duid1(duid1, duid1 + sizeof(duid1));
+
+ fstream file1(DUID_FILE, ios::out | ios::trunc);
+ file1 << duid1_text;
+ file1.close();
+
+ // Test reading from a file
+ EXPECT_TRUE(srv.loadServerID(DUID_FILE));
+ ASSERT_TRUE(srv.getServerID());
+ ASSERT_EQ(sizeof(duid1) + Option::OPTION6_HDR_LEN, srv.getServerID()->len());
+ ASSERT_TRUE(expected_duid1 == srv.getServerID()->getData());
+
+ // Now test writing to a file
+ EXPECT_EQ(0, unlink(DUID_FILE));
+ EXPECT_NO_THROW(srv.writeServerID(DUID_FILE));
+
+ fstream file2(DUID_FILE, ios::in);
+ ASSERT_TRUE(file2.good());
+ string text;
+ file2 >> text;
+ file2.close();
+
+ EXPECT_EQ(duid1_text, text);
+}
+
/// @todo: Add more negative tests for processX(), e.g. extend sanityCheck() test
/// to call processX() methods.
diff --git a/src/bin/loadzone/loadzone.py.in b/src/bin/loadzone/loadzone.py.in
index 3ed3b7d..949446b 100755
--- a/src/bin/loadzone/loadzone.py.in
+++ b/src/bin/loadzone/loadzone.py.in
@@ -25,6 +25,7 @@ from isc.datasrc import *
import isc.util.process
import isc.log
from isc.log_messages.loadzone_messages import *
+from datetime import timedelta
isc.util.process.rename()
@@ -87,7 +88,6 @@ class LoadZoneRunner:
'''
def __init__(self, command_args):
self.__command_args = command_args
- self.__loaded_rrs = 0
self.__interrupted = False # will be set to True on receiving signal
# system-wide log configuration. We need to configure logging this
@@ -103,8 +103,9 @@ class LoadZoneRunner:
[{"output": "stderr",
"destination": "console"}]}]}
- # These are essentially private, and defined as "protected" for the
+ # These are essentially private, but defined as "protected" for the
# convenience of tests inspecting them
+ self._loaded_rrs = 0
self._zone_class = None
self._zone_name = None
self._zone_file = None
@@ -113,6 +114,10 @@ class LoadZoneRunner:
self._log_severity = 'INFO'
self._log_debuglevel = 0
self._report_interval = LOAD_INTERVAL_DEFAULT
+ self._start_time = None
+ # This one will be used in (rare) cases where we want to allow tests to
+ # fake time.time()
+ self._get_time = time.time
self._config_log()
@@ -200,16 +205,37 @@ class LoadZoneRunner:
logger.info(LOADZONE_SQLITE3_USING_DEFAULT_CONFIG, default_db_file)
return '{"database_file": "' + default_db_file + '"}'
- def _report_progress(self, loaded_rrs):
+ def _report_progress(self, loaded_rrs, progress, dump=True):
'''Dump the current progress report to stdout.
This is essentially private, but defined as "protected" for tests.
+ Normally dump is True, but tests will set it False to get the
+ text to be reported. Tests may also fake self._get_time (which
+ is set to time.time() by default) and self._start_time for control
+ time related conditions.
'''
- elapsed = time.time() - self.__start_time
- sys.stdout.write("\r" + (80 * " "))
- sys.stdout.write("\r%d RRs loaded in %.2f seconds" %
- (loaded_rrs, elapsed))
+ elapsed = self._get_time() - self._start_time
+ speed = int(loaded_rrs / elapsed) if elapsed > 0 else 0
+ etc = None # calculate estimated time of completion
+ if progress != ZoneLoader.PROGRESS_UNKNOWN:
+ etc = (1 - progress) * (elapsed / progress)
+
+ # Build report text
+ report_txt = '\r%d RRs' % loaded_rrs
+ if progress != ZoneLoader.PROGRESS_UNKNOWN:
+ report_txt += ' (%.1f%%)' % (progress * 100)
+ report_txt += ' in %s, %d RRs/sec' % \
+ (str(timedelta(seconds=int(elapsed))), speed)
+ if etc is not None:
+ report_txt += ', %s ETC' % str(timedelta(seconds=int(etc)))
+
+ # Dump or return the report text.
+ if dump:
+ sys.stdout.write("\r" + (80 * " "))
+ sys.stdout.write(report_txt)
+ else:
+ return report_txt
def _do_load(self):
'''Main part of the load logic.
@@ -230,7 +256,7 @@ class LoadZoneRunner:
self._zone_class)
loader = ZoneLoader(datasrc_client, self._zone_name,
self._zone_file)
- self.__start_time = time.time()
+ self._start_time = time.time()
if self._report_interval > 0:
limit = self._report_interval
else:
@@ -239,17 +265,20 @@ class LoadZoneRunner:
limit = LOAD_INTERVAL_DEFAULT
while (not self.__interrupted and
not loader.load_incremental(limit)):
- self.__loaded_rrs += self._report_interval
+ self._loaded_rrs += self._report_interval
if self._report_interval > 0:
- self._report_progress(self.__loaded_rrs)
+ self._report_progress(self._loaded_rrs,
+ loader.get_progress())
if self.__interrupted:
raise LoadFailure('loading interrupted by signal')
# On successful completion, add final '\n' to the progress
# report output (on failure don't bother to make it prettier).
if (self._report_interval > 0 and
- self.__loaded_rrs >= self._report_interval):
+ self._loaded_rrs >= self._report_interval):
sys.stdout.write('\n')
+ # record the final count of the loaded RRs for logging
+ self._loaded_rrs = loader.get_rr_count()
except Exception as ex:
# release any remaining lock held in the loader
loader = None
@@ -273,8 +302,8 @@ class LoadZoneRunner:
self._set_signal_handlers()
self._parse_args()
self._do_load()
- total_elapsed_txt = "%.2f" % (time.time() - self.__start_time)
- logger.info(LOADZONE_DONE, self.__loaded_rrs, self._zone_name,
+ total_elapsed_txt = "%.2f" % (time.time() - self._start_time)
+ logger.info(LOADZONE_DONE, self._loaded_rrs, self._zone_name,
self._zone_class, total_elapsed_txt)
return 0
except BadArgument as ex:
diff --git a/src/bin/loadzone/loadzone_messages.mes b/src/bin/loadzone/loadzone_messages.mes
index ca241b3..744a1a4 100644
--- a/src/bin/loadzone/loadzone_messages.mes
+++ b/src/bin/loadzone/loadzone_messages.mes
@@ -27,16 +27,11 @@ LOADZONE_ZONE_CREATED), but the loading operation has subsequently
failed. The newly created zone has been removed from the data source,
so that the data source will go back to the original state.
-% LOADZONE_DONE Loaded (at least) %1 RRs into zone %2/%3 in %4 seconds
+% LOADZONE_DONE Loaded %1 RRs into zone %2/%3 in %4 seconds
b10-loadzone has successfully loaded the specified zone. If there was
an old version of the zone in the data source, it is now deleted.
-It also prints (a lower bound of) the number of RRs that have been loaded
-and the time spent for the loading. Due to a limitation of the
-current implementation of the underlying library however, it cannot show the
-exact number of the loaded RRs; it's counted for every N-th RR where N
-is the value of the -i command line option. So, for smaller zones that
-don't even contain N RRs, the reported value will be 0. This will be
-improved in a future version.
+It also prints the number of RRs that have been loaded
+and the time spent for the loading.
% LOADZONE_LOAD_ERROR Failed to load zone %1/%2: %3
Loading a zone by b10-loadzone fails for some reason in the middle of
diff --git a/src/bin/loadzone/tests/loadzone_test.py b/src/bin/loadzone/tests/loadzone_test.py
index d1ee131..fee8c2a 100755
--- a/src/bin/loadzone/tests/loadzone_test.py
+++ b/src/bin/loadzone/tests/loadzone_test.py
@@ -142,7 +142,7 @@ class TestLoadZoneRunner(unittest.TestCase):
self.__runner._datasrc_config = DATASRC_CONFIG
self.__runner._report_interval = 1
self.__reports = []
- self.__runner._report_progress = lambda x: self.__reports.append(x)
+ self.__runner._report_progress = lambda x, _: self.__reports.append(x)
def __check_zone_soa(self, soa_txt, zone_name=TEST_ZONE_NAME):
"""Check that the given SOA RR exists and matches the expected string
@@ -175,6 +175,7 @@ class TestLoadZoneRunner(unittest.TestCase):
# be 3 RRs
self.assertEqual([1, 2, 3], self.__reports)
self.__check_zone_soa(NEW_SOA_TXT)
+ self.assertEqual(3, self.__runner._loaded_rrs)
def test_load_update_skipped_report(self):
'''successful loading, with reports for every 2 RRs'''
@@ -182,6 +183,8 @@ class TestLoadZoneRunner(unittest.TestCase):
self.__runner._report_interval = 2
self.__runner._do_load()
self.assertEqual([2], self.__reports)
+ # total RRs should still be set the actual value
+ self.assertEqual(3, self.__runner._loaded_rrs)
def test_load_update_no_report(self):
'''successful loading, without progress reports'''
@@ -190,6 +193,36 @@ class TestLoadZoneRunner(unittest.TestCase):
self.__runner._do_load()
self.assertEqual([], self.__reports) # no report
self.__check_zone_soa(NEW_SOA_TXT) # but load is completed
+ self.assertEqual(3, self.__runner._loaded_rrs)
+
+ def test_report_progress(self):
+ '''Check the output format of report_progress.
+
+ For some simple scenario and minor corner cases. We tweak the
+ start and current times so the test results will be predicatble.
+
+ '''
+ # 10 RRs in 10 sec, which is 25% of the entire zone. it'll take
+ # 30 sec more
+ self.__runner._start_time = 10
+ self.__runner._get_time = lambda: 20
+ self.assertEqual('\r10 RRs (25.0%) in 0:00:10, 1 RRs/sec, ' +
+ '0:00:30 ETC',
+ self.__runner._report_progress(10, 0.25, False))
+
+ # start time == current time. unlikely to happen in practice, but
+ # it shouldn't cause disruption.
+ self.__runner._get_time = lambda: 10
+ self.assertEqual('\r10 RRs (25.0%) in 0:00:00, 0 RRs/sec, ' +
+ '0:00:00 ETC',
+ self.__runner._report_progress(10, 0.25, False))
+
+ # progress is unknown
+ self.__runner._get_time = lambda: 20
+ unknown_progress = isc.datasrc.ZoneLoader.PROGRESS_UNKNOWN
+ self.assertEqual('\r10 RRs in 0:00:10, 1 RRs/sec',
+ self.__runner._report_progress(10, unknown_progress,
+ False))
def test_create_and_load(self):
'''successful case to loading contents to a new zone (created).'''
@@ -275,7 +308,8 @@ class TestLoadZoneRunner(unittest.TestCase):
def test_load_interrupted(self):
'''Load attempt fails due to signal interruption'''
self.__common_load_setup()
- self.__runner._report_progress = lambda x: self.__interrupt_progress(x)
+ self.__runner._report_progress = \
+ lambda x, _: self.__interrupt_progress(x)
# The interrupting _report_progress() will terminate the loading
# in the middle. the number of reports is smaller, and the zone
# won't be changed.
@@ -290,7 +324,8 @@ class TestLoadZoneRunner(unittest.TestCase):
'''
self.__common_load_setup()
- self.__runner._report_progress = lambda x: self.__interrupt_progress(x)
+ self.__runner._report_progress = \
+ lambda x, _: self.__interrupt_progress(x)
self.__runner._zone_name = Name('example.com')
self.__runner._zone_file = ALT_NEW_ZONE_TXT_FILE
self.__check_zone_soa(None, zone_name=Name('example.com'))
diff --git a/src/bin/msgq/msgq.py.in b/src/bin/msgq/msgq.py.in
index edca400..68c18dc 100755
--- a/src/bin/msgq/msgq.py.in
+++ b/src/bin/msgq/msgq.py.in
@@ -322,7 +322,6 @@ class MsgQ:
logger.error(MSGQ_READ_UNKNOWN_FD, fd)
return
sock = self.sockets[fd]
-# sys.stderr.write("[b10-msgq] Got read on fd %d\n" %fd)
self.process_packet(fd, sock)
def kill_socket(self, fd, sock):
@@ -650,6 +649,18 @@ class MsgQ:
logger.debug(TRACE_START, MSGQ_SHUTDOWN)
self.listen_socket.close()
self.cleanup_signalsock()
+ # Close all the sockets too. In real life, there should be none now,
+ # as Msgq should be the last one. But some tests don't adhere to this
+ # and create a new Msgq for each test, which led to huge socket leaks.
+ # Some other threads put some other things in instead of sockets, so
+ # we catch whatever exceptions there we can. This should be safe,
+ # because in real operation, we will terminate now anyway, implicitly
+ # closing anything anyway.
+ for sock in self.sockets.values():
+ try:
+ sock.close()
+ except Exception:
+ pass
if os.path.exists(self.socket_file):
os.remove(self.socket_file)
diff --git a/src/bin/stats/tests/b10-stats-httpd_test.py b/src/bin/stats/tests/b10-stats-httpd_test.py
index 997ac41..fb0510a 100644
--- a/src/bin/stats/tests/b10-stats-httpd_test.py
+++ b/src/bin/stats/tests/b10-stats-httpd_test.py
@@ -42,6 +42,7 @@ except ImportError:
lxml_etree = None
import isc
+import isc.log
import stats_httpd
import stats
from test_utils import BaseModules, ThreadingServerManager, MyStats,\
@@ -1066,4 +1067,5 @@ class TestStatsHttpd(unittest.TestCase):
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
index 193f46c..d18abf1 100644
--- a/src/bin/stats/tests/b10-stats_test.py
+++ b/src/bin/stats/tests/b10-stats_test.py
@@ -29,6 +29,7 @@ import time
import imp
import stats
+import isc.log
import isc.cc.session
from test_utils import BaseModules, ThreadingServerManager, MyStats, SignalHandler, send_command, send_shutdown
from isc.testutils.ccsession_mock import MockModuleCCSession
@@ -1254,8 +1255,6 @@ class TestOSEnv(unittest.TestCase):
os.environ["B10_FROM_SOURCE"] = path
imp.reload(stats)
-def test_main():
- unittest.main()
-
if __name__ == "__main__":
- test_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 96f7046..5c1855a 100644
--- a/src/bin/stats/tests/test_utils.py
+++ b/src/bin/stats/tests/test_utils.py
@@ -103,20 +103,9 @@ class ThreadingServerManager:
else:
self.server._thread.join(0) # timeout is 0
-def do_nothing(*args, **kwargs): pass
-
-class dummy_sys:
- """Dummy for sys"""
- class dummy_io:
- write = do_nothing
- stdout = stderr = dummy_io()
-
class MockMsgq:
def __init__(self):
self._started = threading.Event()
- # suppress output to stdout and stderr
- msgq.sys = dummy_sys()
- msgq.print = do_nothing
self.msgq = msgq.MsgQ(verbose=False)
result = self.msgq.setup()
if result:
@@ -124,10 +113,15 @@ class MockMsgq:
def run(self):
self._started.set()
- self.msgq.run()
+ try:
+ self.msgq.run()
+ finally:
+ # Make sure all the sockets, etc, are removed once it stops.
+ self.msgq.shutdown()
def shutdown(self):
- self.msgq.shutdown()
+ # Ask it to terminate nicely
+ self.msgq.stop()
class MockCfgmgr:
def __init__(self):
@@ -554,15 +548,20 @@ class BaseModules:
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()
- self.auth.shutdown()
+ self.auth2.shutdown(True)
+ self.auth.shutdown(True)
# MockBoss
- self.boss.shutdown()
+ self.boss.shutdown(True)
# MockCfgmgr
- self.cfgmgr.shutdown()
- # MockMsgq
- self.msgq.shutdown()
+ self.cfgmgr.shutdown(True)
# remove the unused socket file
socket_file = self.msgq.server.msgq.socket_file
try:
diff --git a/src/bin/sysinfo/run_sysinfo.sh.in b/src/bin/sysinfo/run_sysinfo.sh.in
index 268b5a4..6459c2d 100755
--- a/src/bin/sysinfo/run_sysinfo.sh.in
+++ b/src/bin/sysinfo/run_sysinfo.sh.in
@@ -20,10 +20,11 @@ export PYTHON_EXEC
SYSINFO_PATH=@abs_top_builddir@/src/bin/sysinfo
-# Note: we shouldn't need log_messages except for the seemingly necessary
-# dependency due to the automatic import in the isc package (its __init__.py
-# imports some other modules)
-PYTHONPATH=@abs_top_builddir@/src/lib/python:@abs_top_srcdir@/src/lib/python:@abs_top_builddir@/src/lib/python/isc/log_messages
+# Note: we shouldn't need log_messages and lib/dns except for the seemingly
+# necessary dependency due to the automatic import in the isc package (its
+# __init__.py imports some other modules)
+# #2145 should eliminate the need for them.
+PYTHONPATH=@abs_top_builddir@/src/lib/python:@abs_top_srcdir@/src/lib/python:@abs_top_builddir@/src/lib/python/isc/log_messages:@abs_top_builddir@/src/lib/dns/python/.libs
export PYTHONPATH
# Likewise, we need only because isc.log requires some loadable modules.
diff --git a/src/lib/cc/data.cc b/src/lib/cc/data.cc
index 4a37087..f9c23db 100644
--- a/src/lib/cc/data.cc
+++ b/src/lib/cc/data.cc
@@ -16,6 +16,7 @@
#include <cc/data.h>
+#include <cstring>
#include <cassert>
#include <climits>
#include <map>
@@ -31,7 +32,7 @@
using namespace std;
namespace {
-const char* WHITESPACE = " \b\f\n\r\t";
+const char* const WHITESPACE = " \b\f\n\r\t";
} // end anonymous namespace
namespace isc {
@@ -183,7 +184,7 @@ throwJSONError(const std::string& error, const std::string& file, int line,
}
std::ostream&
-operator<<(std::ostream &out, const Element& e) {
+operator<<(std::ostream& out, const Element& e) {
return (out << e.str());
}
@@ -240,8 +241,9 @@ Element::createMap() {
//
namespace {
bool
-char_in(const char c, const char *chars) {
- for (size_t i = 0; i < strlen(chars); ++i) {
+charIn(const int c, const char* chars) {
+ const size_t chars_len = std::strlen(chars);
+ for (size_t i = 0; i < chars_len; ++i) {
if (chars[i] == c) {
return (true);
}
@@ -250,9 +252,9 @@ char_in(const char c, const char *chars) {
}
void
-skip_chars(std::istream &in, const char *chars, int& line, int& pos) {
- char c = in.peek();
- while (char_in(c, chars) && c != EOF) {
+skipChars(std::istream& in, const char* chars, int& line, int& pos) {
+ int c = in.peek();
+ while (charIn(c, chars) && c != EOF) {
if (c == '\n') {
++line;
pos = 1;
@@ -270,21 +272,21 @@ skip_chars(std::istream &in, const char *chars, int& line, int& pos) {
//
// the character found is left on the stream
void
-skip_to(std::istream &in, const std::string& file, int& line,
- int& pos, const char* chars, const char* may_skip="")
+skipTo(std::istream& in, const std::string& file, int& line,
+ int& pos, const char* chars, const char* may_skip="")
{
- char c = in.get();
+ int c = in.get();
++pos;
while (c != EOF) {
if (c == '\n') {
pos = 1;
++line;
}
- if (char_in(c, may_skip)) {
+ if (charIn(c, may_skip)) {
c = in.get();
++pos;
- } else if (char_in(c, chars)) {
- while(char_in(in.peek(), may_skip)) {
+ } else if (charIn(c, chars)) {
+ while (charIn(in.peek(), may_skip)) {
if (in.peek() == '\n') {
pos = 1;
++line;
@@ -296,7 +298,7 @@ skip_to(std::istream &in, const std::string& file, int& line,
--pos;
return;
} else {
- throwJSONError(std::string("'") + c + "' read, one of \"" + chars + "\" expected", file, line, pos);
+ throwJSONError(std::string("'") + std::string(1, c) + "' read, one of \"" + chars + "\" expected", file, line, pos);
}
}
throwJSONError(std::string("EOF read, one of \"") + chars + "\" expected", file, line, pos);
@@ -305,12 +307,11 @@ skip_to(std::istream &in, const std::string& file, int& line,
// TODO: Should we check for all other official escapes here (and
// error on the rest)?
std::string
-str_from_stringstream(std::istream &in, const std::string& file, const int line,
- int& pos) throw (JSONError)
+strFromStringstream(std::istream& in, const std::string& file,
+ const int line, int& pos) throw (JSONError)
{
- char c;
std::stringstream ss;
- c = in.get();
+ int c = in.get();
++pos;
if (c == '"') {
c = in.get();
@@ -354,7 +355,7 @@ str_from_stringstream(std::istream &in, const std::string& file, const int line,
in.get();
++pos;
}
- ss << c;
+ ss.put(c);
c = in.get();
++pos;
}
@@ -365,7 +366,7 @@ str_from_stringstream(std::istream &in, const std::string& file, const int line,
}
std::string
-word_from_stringstream(std::istream &in, int& pos) {
+wordFromStringstream(std::istream& in, int& pos) {
std::stringstream ss;
while (isalpha(in.peek())) {
ss << (char) in.get();
@@ -374,8 +375,8 @@ word_from_stringstream(std::istream &in, int& pos) {
return (ss.str());
}
-static std::string
-number_from_stringstream(std::istream &in, int& pos) {
+std::string
+numberFromStringstream(std::istream& in, int& pos) {
std::stringstream ss;
while (isdigit(in.peek()) || in.peek() == '+' || in.peek() == '-' ||
in.peek() == '.' || in.peek() == 'e' || in.peek() == 'E') {
@@ -389,13 +390,13 @@ number_from_stringstream(std::istream &in, int& pos) {
// that can also hold an e value? (and have specific getters if the
// value is larger than an int can handle)
ElementPtr
-from_stringstream_number(std::istream &in, int &pos) {
+fromStringstreamNumber(std::istream& in, int& pos) {
long int i;
double d = 0.0;
bool is_double = false;
- char *endptr;
+ char* endptr;
- std::string number = number_from_stringstream(in, pos);
+ std::string number = numberFromStringstream(in, pos);
i = strtol(number.c_str(), &endptr, 10);
if (*endptr != '\0') {
@@ -422,10 +423,10 @@ from_stringstream_number(std::istream &in, int &pos) {
}
ElementPtr
-from_stringstream_bool(std::istream &in, const std::string& file,
- const int line, int& pos)
+fromStringstreamBool(std::istream& in, const std::string& file,
+ const int line, int& pos)
{
- const std::string word = word_from_stringstream(in, pos);
+ const std::string word = wordFromStringstream(in, pos);
if (boost::iequals(word, "True")) {
return (Element::create(true));
} else if (boost::iequals(word, "False")) {
@@ -438,10 +439,10 @@ from_stringstream_bool(std::istream &in, const std::string& file,
}
ElementPtr
-from_stringstream_null(std::istream &in, const std::string& file,
- const int line, int& pos)
+fromStringstreamNull(std::istream& in, const std::string& file,
+ const int line, int& pos)
{
- const std::string word = word_from_stringstream(in, pos);
+ const std::string word = wordFromStringstream(in, pos);
if (boost::iequals(word, "null")) {
return (Element::create());
} else {
@@ -451,26 +452,26 @@ from_stringstream_null(std::istream &in, const std::string& file,
}
ElementPtr
-from_stringstream_string(std::istream& in, const std::string& file, int& line,
- int& pos)
+fromStringstreamString(std::istream& in, const std::string& file, int& line,
+ int& pos)
{
- return (Element::create(str_from_stringstream(in, file, line, pos)));
+ return (Element::create(strFromStringstream(in, file, line, pos)));
}
ElementPtr
-from_stringstream_list(std::istream &in, const std::string& file, int& line,
- int& pos)
+fromStringstreamList(std::istream& in, const std::string& file, int& line,
+ int& pos)
{
- char c = 0;
+ int c = 0;
ElementPtr list = Element::createList();
ConstElementPtr cur_list_element;
- skip_chars(in, WHITESPACE, line, pos);
+ skipChars(in, WHITESPACE, line, pos);
while (c != EOF && c != ']') {
if (in.peek() != ']') {
cur_list_element = Element::fromJSON(in, file, line, pos);
list->add(cur_list_element);
- skip_to(in, file, line, pos, ",]", WHITESPACE);
+ skipTo(in, file, line, pos, ",]", WHITESPACE);
}
c = in.get();
pos++;
@@ -479,12 +480,12 @@ from_stringstream_list(std::istream &in, const std::string& file, int& line,
}
ElementPtr
-from_stringstream_map(std::istream &in, const std::string& file, int& line,
- int& pos)
+fromStringstreamMap(std::istream& in, const std::string& file, int& line,
+ int& pos)
{
ElementPtr map = Element::createMap();
- skip_chars(in, WHITESPACE, line, pos);
- char c = in.peek();
+ skipChars(in, WHITESPACE, line, pos);
+ int c = in.peek();
if (c == EOF) {
throwJSONError(std::string("Unterminated map, <string> or } expected"), file, line, pos);
} else if (c == '}') {
@@ -492,9 +493,9 @@ from_stringstream_map(std::istream &in, const std::string& file, int& line,
c = in.get();
} else {
while (c != EOF && c != '}') {
- std::string key = str_from_stringstream(in, file, line, pos);
+ std::string key = strFromStringstream(in, file, line, pos);
- skip_to(in, file, line, pos, ":", WHITESPACE);
+ skipTo(in, file, line, pos, ":", WHITESPACE);
// skip the :
in.get();
pos++;
@@ -502,14 +503,14 @@ from_stringstream_map(std::istream &in, const std::string& file, int& line,
ConstElementPtr value = Element::fromJSON(in, file, line, pos);
map->set(key, value);
- skip_to(in, file, line, pos, ",}", WHITESPACE);
+ skipTo(in, file, line, pos, ",}", WHITESPACE);
c = in.get();
pos++;
}
}
return (map);
}
-}
+} // unnamed namespace
std::string
Element::typeToName(Element::types type) {
@@ -575,13 +576,13 @@ Element::fromJSON(std::istream& in, const std::string& file_name)
}
ElementPtr
-Element::fromJSON(std::istream &in, const std::string& file, int& line,
+Element::fromJSON(std::istream& in, const std::string& file, int& line,
int& pos) throw(JSONError)
{
- char c = 0;
+ int c = 0;
ElementPtr element;
bool el_read = false;
- skip_chars(in, WHITESPACE, line, pos);
+ skipChars(in, WHITESPACE, line, pos);
while (c != EOF && !el_read) {
c = in.get();
pos++;
@@ -600,7 +601,7 @@ Element::fromJSON(std::istream &in, const std::string& file, int& line,
case '+':
case '.':
in.putback(c);
- element = from_stringstream_number(in, pos);
+ element = fromStringstreamNumber(in, pos);
el_read = true;
break;
case 't':
@@ -608,32 +609,32 @@ Element::fromJSON(std::istream &in, const std::string& file, int& line,
case 'f':
case 'F':
in.putback(c);
- element = from_stringstream_bool(in, file, line, pos);
+ element = fromStringstreamBool(in, file, line, pos);
el_read = true;
break;
case 'n':
case 'N':
in.putback(c);
- element = from_stringstream_null(in, file, line, pos);
+ element = fromStringstreamNull(in, file, line, pos);
el_read = true;
break;
case '"':
in.putback('"');
- element = from_stringstream_string(in, file, line, pos);
+ element = fromStringstreamString(in, file, line, pos);
el_read = true;
break;
case '[':
- element = from_stringstream_list(in, file, line, pos);
+ element = fromStringstreamList(in, file, line, pos);
el_read = true;
break;
case '{':
- element = from_stringstream_map(in, file, line, pos);
+ element = fromStringstreamMap(in, file, line, pos);
el_read = true;
break;
case EOF:
break;
default:
- throwJSONError(std::string("error: unexpected character ") + c, file, line, pos);
+ throwJSONError(std::string("error: unexpected character ") + std::string(1, c), file, line, pos);
break;
}
}
@@ -645,12 +646,12 @@ Element::fromJSON(std::istream &in, const std::string& file, int& line,
}
ElementPtr
-Element::fromJSON(const std::string &in) {
+Element::fromJSON(const std::string& in) {
std::stringstream ss;
ss << in;
int line = 1, pos = 1;
ElementPtr result(fromJSON(ss, "<string>", line, pos));
- skip_chars(ss, WHITESPACE, line, pos);
+ skipChars(ss, WHITESPACE, line, pos);
// ss must now be at end
if (ss.peek() != EOF) {
throwJSONError("Extra data", "<string>", line, pos);
diff --git a/src/lib/cc/tests/data_unittests.cc b/src/lib/cc/tests/data_unittests.cc
index 1565418..9f015d2 100644
--- a/src/lib/cc/tests/data_unittests.cc
+++ b/src/lib/cc/tests/data_unittests.cc
@@ -91,6 +91,10 @@ TEST(Element, from_and_to_json) {
sv.push_back("-1");
sv.push_back("-1.234");
sv.push_back("-123.456");
+ // We should confirm that our string handling is 8-bit clean.
+ // At one point we were using char-length data and comparing to EOF,
+ // which means that character '\xFF' would not parse properly.
+ sv.push_back("\"\xFF\"");
BOOST_FOREACH(const std::string& s, sv) {
// test << operator, which uses Element::str()
diff --git a/src/lib/datasrc/memory/Makefile.am b/src/lib/datasrc/memory/Makefile.am
index 72b3273..c0ee688 100644
--- a/src/lib/datasrc/memory/Makefile.am
+++ b/src/lib/datasrc/memory/Makefile.am
@@ -15,6 +15,7 @@ libdatasrc_memory_la_SOURCES += rdataset.h rdataset.cc
libdatasrc_memory_la_SOURCES += treenode_rrset.h treenode_rrset.cc
libdatasrc_memory_la_SOURCES += rdata_serialization.h rdata_serialization.cc
libdatasrc_memory_la_SOURCES += zone_data.h zone_data.cc
+libdatasrc_memory_la_SOURCES += rrset_collection.h rrset_collection.cc
libdatasrc_memory_la_SOURCES += segment_object_holder.h
libdatasrc_memory_la_SOURCES += logger.h logger.cc
libdatasrc_memory_la_SOURCES += zone_table.h zone_table.cc
diff --git a/src/lib/datasrc/memory/memory_messages.mes b/src/lib/datasrc/memory/memory_messages.mes
index 1b67093..81ac7f6 100644
--- a/src/lib/datasrc/memory/memory_messages.mes
+++ b/src/lib/datasrc/memory/memory_messages.mes
@@ -88,3 +88,16 @@ The software refuses to load NS records into a wildcard domain. It isn't
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.
diff --git a/src/lib/datasrc/memory/rrset_collection.cc b/src/lib/datasrc/memory/rrset_collection.cc
new file mode 100644
index 0000000..92319f6
--- /dev/null
+++ b/src/lib/datasrc/memory/rrset_collection.cc
@@ -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.
+
+#include <datasrc/memory/rrset_collection.h>
+#include <datasrc/memory/treenode_rrset.h>
+
+#include <exceptions/exceptions.h>
+
+using namespace isc;
+using namespace isc::dns;
+
+namespace isc {
+namespace datasrc {
+namespace memory {
+
+ConstRRsetPtr
+RRsetCollection::find(const isc::dns::Name& name,
+ const isc::dns::RRClass& rrclass,
+ const isc::dns::RRType& rrtype) const
+{
+ if (rrclass != rrclass_) {
+ // We could throw an exception here, but RRsetCollection is
+ // expected to support an arbitrary collection of RRsets, and it
+ // can be queried just as arbitrarily. So we just return nothing
+ // here.
+ return (ConstRRsetPtr());
+ }
+
+ const ZoneTree& tree = zone_data_.getZoneTree();
+ const ZoneNode *node = NULL;
+ ZoneTree::Result result = tree.find(name, &node);
+ if (result != ZoneTree::EXACTMATCH) {
+ return (ConstRRsetPtr());
+ }
+
+ const RdataSet* rdataset = RdataSet::find(node->getData(), rrtype);
+ if (rdataset == NULL) {
+ return (ConstRRsetPtr());
+ }
+
+ return (ConstRRsetPtr(new TreeNodeRRset(rrclass_, node, rdataset, true)));
+}
+
+} // end of namespace memory
+} // end of namespace datasrc
+} // end of namespace isc
diff --git a/src/lib/datasrc/memory/rrset_collection.h b/src/lib/datasrc/memory/rrset_collection.h
new file mode 100644
index 0000000..e5edd64
--- /dev/null
+++ b/src/lib/datasrc/memory/rrset_collection.h
@@ -0,0 +1,85 @@
+// 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 RRSET_COLLECTION_DATASRC_MEMORY_H
+#define RRSET_COLLECTION_DATASRC_MEMORY_H 1
+
+#include <dns/rrset_collection_base.h>
+#include <dns/rrclass.h>
+
+#include <datasrc/memory/zone_data.h>
+
+namespace isc {
+namespace datasrc {
+namespace memory {
+
+/// \brief In-memory derivation of \c isc::dns::RRsetCollectionBase.
+class RRsetCollection : public isc::dns::RRsetCollectionBase {
+public:
+ /// \brief Constructor.
+ ///
+ /// No reference (count via \c shared_ptr) to the \c ZoneData is
+ /// acquired. The RRsetCollection must not be used after its
+ /// \c ZoneData has been destroyed.
+ ///
+ /// \param zone_data The ZoneData to wrap around.
+ /// \param rrclass The RRClass of the records in the zone.
+ RRsetCollection(ZoneData& zone_data, const isc::dns::RRClass& rrclass) :
+ zone_data_(zone_data),
+ rrclass_(rrclass)
+ {}
+
+ /// \brief Destructor
+ virtual ~RRsetCollection() {}
+
+ /// \brief Find a matching RRset in the collection.
+ ///
+ /// Returns the RRset in the collection that exactly matches the
+ /// given \c name, \c rrclass and \c rrtype. If no matching RRset
+ /// is found, \c NULL is returned.
+ ///
+ /// \throw isc::dns::RRsetCollectionError if \c find() results in
+ /// some underlying error.
+ ///
+ /// \param name The name of the RRset to search for.
+ /// \param rrclass The class of the RRset to search for.
+ /// \param rrtype The type of the RRset to search for.
+ /// \returns The RRset if found, \c NULL otherwise.
+ virtual isc::dns::ConstRRsetPtr find(const isc::dns::Name& name,
+ const isc::dns::RRClass& rrclass,
+ const isc::dns::RRType& rrtype) const;
+
+protected:
+ virtual RRsetCollectionBase::IterPtr getBeginning() {
+ isc_throw(NotImplemented, "This method is not implemented.");
+ }
+
+ virtual RRsetCollectionBase::IterPtr getEnd() {
+ isc_throw(NotImplemented, "This method is not implemented.");
+ }
+
+private:
+ ZoneData& zone_data_;
+ isc::dns::RRClass rrclass_;
+};
+
+} // end of namespace memory
+} // end of namespace datasrc
+} // end of namespace isc
+
+#endif // RRSET_COLLECTION_DATASRC_MEMORY_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/datasrc/memory/zone_data_loader.cc b/src/lib/datasrc/memory/zone_data_loader.cc
index e224224..e3c9c3f 100644
--- a/src/lib/datasrc/memory/zone_data_loader.cc
+++ b/src/lib/datasrc/memory/zone_data_loader.cc
@@ -18,11 +18,13 @@
#include <datasrc/memory/logger.h>
#include <datasrc/memory/segment_object_holder.h>
#include <datasrc/memory/util_internal.h>
+#include <datasrc/memory/rrset_collection.h>
#include <dns/master_loader.h>
#include <dns/rrcollator.h>
#include <dns/rdataclass.h>
#include <dns/rrset.h>
+#include <dns/zone_checker.h>
#include <boost/foreach.hpp>
#include <boost/bind.hpp>
@@ -148,6 +150,22 @@ ZoneDataLoader::getCurrentName() const {
return (node_rrsigsets_.begin()->second->getName());
}
+void
+logWarning(const dns::Name* zone_name, const dns::RRClass* rrclass,
+ const std::string& reason)
+{
+ LOG_WARN(logger, DATASRC_MEMORY_CHECK_WARNING).arg(*zone_name).
+ arg(*rrclass).arg(reason);
+}
+
+void
+logError(const dns::Name* zone_name, const dns::RRClass* rrclass,
+ const std::string& reason)
+{
+ LOG_ERROR(logger, DATASRC_MEMORY_CHECK_ERROR).arg(*zone_name).arg(*rrclass).
+ arg(reason);
+}
+
ZoneData*
loadZoneDataInternal(util::MemorySegment& mem_sgmt,
const isc::dns::RRClass& rrclass,
@@ -172,12 +190,14 @@ loadZoneDataInternal(util::MemorySegment& mem_sgmt,
}
}
- // 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.
- if (RdataSet::find(rdataset, RRType::SOA()) == NULL) {
- isc_throw(EmptyZone,
- "Won't create an empty zone for: " << zone_name);
+ RRsetCollection collection(*(holder.get()), rrclass);
+ const dns::ZoneCheckerCallbacks
+ callbacks(boost::bind(&logError, &zone_name, &rrclass, _1),
+ boost::bind(&logWarning, &zone_name, &rrclass, _1));
+ if (!dns::checkZone(zone_name, rrclass, collection, callbacks)) {
+ isc_throw(ZoneValidationError,
+ "Errors found when validating zone: "
+ << zone_name << "/" << rrclass);
}
return (holder.release());
diff --git a/src/lib/datasrc/memory/zone_data_loader.h b/src/lib/datasrc/memory/zone_data_loader.h
index 6b409ec..db7ac3b 100644
--- a/src/lib/datasrc/memory/zone_data_loader.h
+++ b/src/lib/datasrc/memory/zone_data_loader.h
@@ -26,12 +26,12 @@ namespace isc {
namespace datasrc {
namespace memory {
-/// \brief Zone is empty exception.
+/// \brief Zone is invalid exception.
///
-/// This is thrown if an empty zone would be created during
+/// This is thrown if an invalid zone would be created during
/// \c loadZoneData().
-struct EmptyZone : public ZoneLoaderException {
- EmptyZone(const char* file, size_t line, const char* what) :
+struct ZoneValidationError : public ZoneLoaderException {
+ ZoneValidationError(const char* file, size_t line, const char* what) :
ZoneLoaderException(file, line, what)
{}
};
diff --git a/src/lib/datasrc/sqlite3_accessor_link.cc b/src/lib/datasrc/sqlite3_accessor_link.cc
index c064e0f..56e0c2f 100644
--- a/src/lib/datasrc/sqlite3_accessor_link.cc
+++ b/src/lib/datasrc/sqlite3_accessor_link.cc
@@ -47,12 +47,12 @@ checkConfig(ConstElementPtr config, ElementPtr errors) {
bool result = true;
if (!config || config->getType() != Element::map) {
- addError(errors, "Base config for SQlite3 backend must be a map");
+ addError(errors, "Base config for SQLite3 backend must be a map");
result = false;
} else {
if (!config->contains(CONFIG_ITEM_DATABASE_FILE)) {
addError(errors,
- "Config for SQlite3 backend does not contain a '" +
+ "Config for SQLite3 backend does not contain a '" +
string(CONFIG_ITEM_DATABASE_FILE) +
"' value");
result = false;
@@ -89,11 +89,11 @@ createInstance(isc::data::ConstElementPtr config, std::string& error) {
new SQLite3Accessor(dbfile, "IN")); // XXX: avoid hardcode RR class
return (new DatabaseClient(isc::dns::RRClass::IN(), sqlite3_accessor));
} catch (const std::exception& exc) {
- error = std::string("Error creating sqlite3 datasource: ") +
+ error = std::string("Error creating SQLite3 datasource: ") +
exc.what();
return (NULL);
} catch (...) {
- error = std::string("Error creating sqlite3 datasource, "
+ error = std::string("Error creating SQLite3 datasource, "
"unknown exception");
return (NULL);
}
diff --git a/src/lib/datasrc/tests/client_list_unittest.cc b/src/lib/datasrc/tests/client_list_unittest.cc
index 6769e9b..29a0daa 100644
--- a/src/lib/datasrc/tests/client_list_unittest.cc
+++ b/src/lib/datasrc/tests/client_list_unittest.cc
@@ -77,7 +77,7 @@ public:
};
class Iterator : public ZoneIterator {
public:
- Iterator(const Name& origin, bool include_ns) :
+ Iterator(const Name& origin, bool include_a) :
origin_(origin),
soa_(new RRset(origin_, RRClass::IN(), RRType::SOA(),
RRTTL(3600)))
@@ -89,12 +89,21 @@ public:
0, 0, 0, 0, 0));
rrsets_.push_back(soa_);
- if (include_ns) {
- ns_.reset(new RRset(origin_, RRClass::IN(), RRType::NS(),
- RRTTL(3600)));
- ns_->addRdata(rdata::generic::NS(Name::ROOT_NAME()));
- rrsets_.push_back(ns_);
+ 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();
@@ -110,13 +119,12 @@ public:
private:
const Name origin_;
const RRsetPtr soa_;
- RRsetPtr ns_;
std::vector<ConstRRsetPtr> rrsets_;
std::vector<ConstRRsetPtr>::const_iterator it_;
};
// Constructor from a list of zones.
MockDataSourceClient(const char* zone_names[]) :
- have_ns_(true), use_baditerator_(true)
+ have_a_(true), use_baditerator_(true)
{
for (const char** zone(zone_names); *zone; ++zone) {
zones.insert(Name(*zone));
@@ -128,7 +136,7 @@ public:
const ConstElementPtr& configuration) :
type_(type),
configuration_(configuration),
- have_ns_(true), use_baditerator_(true)
+ 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";
@@ -176,19 +184,19 @@ public:
} else {
FindResult result(findZone(name));
if (result.code == isc::datasrc::result::SUCCESS) {
- return (ZoneIteratorPtr(new Iterator(name, have_ns_)));
+ return (ZoneIteratorPtr(new Iterator(name, have_a_)));
} else {
isc_throw(DataSourceError, "No such zone");
}
}
}
- void disableNS() { have_ns_ = false; }
+ void disableA() { have_a_ = false; }
void disableBadIterator() { use_baditerator_ = false; }
const string type_;
const ConstElementPtr configuration_;
private:
set<Name> zones;
- bool have_ns_; // control the iterator behavior wrt whether to include NS
+ bool have_a_; // control the iterator behavior whether to include A record
bool use_baditerator_; // whether to use bogus zone iterators for tests
};
@@ -285,7 +293,7 @@ public:
MockDataSourceClient mock_client(zones);
// Disable some default features of the mock to distinguish the
// temporary case from normal case.
- mock_client.disableNS();
+ mock_client.disableA();
mock_client.disableBadIterator();
// Create cache from the temporary data source, and push it to the
@@ -925,14 +933,19 @@ TYPED_TEST(ReloadTest, reloadSuccess) {
this->list_->configure(this->config_elem_zones_, true);
const Name name("example.org");
this->prepareCache(0, name);
- // The cache currently contains a tweaked version of zone, which doesn't
- // have apex NS. So the lookup should result in NXRRSET.
- EXPECT_EQ(ZoneFinder::NXRRSET,
- this->list_->find(name).finder_->find(name, RRType::NS())->code);
+ // 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_->
+ 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(ZoneFinder::SUCCESS,
- this->list_->find(name).finder_->find(name, RRType::NS())->code);
+ this->list_->find(name).finder_->
+ find(Name("tstzonedata").concatenate(name),
+ RRType::A())->code);
}
// The cache is not enabled. The load should be rejected.
@@ -941,14 +954,18 @@ TYPED_TEST(ReloadTest, reloadNotEnabled) {
const Name name("example.org");
// We put the cache in even when not enabled. This won't confuse the thing.
this->prepareCache(0, name);
- // See the reloadSuccess test. This should result in NXRRSET.
- EXPECT_EQ(ZoneFinder::NXRRSET,
- this->list_->find(name).finder_->find(name, RRType::NS())->code);
+ // See the reloadSuccess test. This should result in NXDOMAIN.
+ EXPECT_EQ(ZoneFinder::NXDOMAIN,
+ this->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));
// Nothing changed here
- EXPECT_EQ(ZoneFinder::NXRRSET,
- this->list_->find(name).finder_->find(name, RRType::NS())->code);
+ EXPECT_EQ(ZoneFinder::NXDOMAIN,
+ this->list_->find(name).finder_->
+ find(Name("tstzonedata").concatenate(name),
+ RRType::A())->code);
}
// Test several cases when the zone does not exist
@@ -974,10 +991,11 @@ TYPED_TEST(ReloadTest, reloadNoSuchZone) {
this->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_);
- // Not reloaded, so NS shouldn't be visible yet.
- EXPECT_EQ(ZoneFinder::NXRRSET,
+ // Not reloaded, so A record shouldn't be visible yet.
+ EXPECT_EQ(ZoneFinder::NXDOMAIN,
this->list_->find(Name("example.com")).finder_->
- find(Name("example.com"), RRType::NS())->code);
+ find(Name("tstzonedata.example.com"),
+ RRType::A())->code);
}
// Check we gracefuly throw an exception when a zone disappeared in
diff --git a/src/lib/datasrc/tests/memory/Makefile.am b/src/lib/datasrc/tests/memory/Makefile.am
index a5d73b4..f77d212 100644
--- a/src/lib/datasrc/tests/memory/Makefile.am
+++ b/src/lib/datasrc/tests/memory/Makefile.am
@@ -32,6 +32,7 @@ run_unittests_SOURCES += ../../tests/faked_nsec3.h ../../tests/faked_nsec3.cc
run_unittests_SOURCES += memory_segment_test.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
diff --git a/src/lib/datasrc/tests/memory/memory_client_unittest.cc b/src/lib/datasrc/tests/memory/memory_client_unittest.cc
index 0a03645..74e7917 100644
--- a/src/lib/datasrc/tests/memory/memory_client_unittest.cc
+++ b/src/lib/datasrc/tests/memory/memory_client_unittest.cc
@@ -59,6 +59,7 @@ namespace {
const char* rrset_data[] = {
"example.org. 3600 IN SOA ns1.example.org. bugs.x.w.example.org. "
"68 3600 300 3600000 3600",
+ "example.org. 3600 IN NS ns1.example.org.",
"a.example.org. 3600 IN A 192.168.0.1\n" // RRset containing 2 RRs
"a.example.org. 3600 IN A 192.168.0.2",
"a.example.org. 3600 IN RRSIG A 5 3 3600 20150420235959 20051021000000 "
@@ -225,7 +226,7 @@ TEST_F(MemoryClientTest, loadEmptyZoneFileThrows) {
EXPECT_THROW(client_->load(Name("."),
TEST_DATA_DIR "/empty.zone"),
- EmptyZone);
+ ZoneValidationError);
EXPECT_EQ(0, client_->getZoneCount());
@@ -255,6 +256,11 @@ TEST_F(MemoryClientTest, loadFromIterator) {
EXPECT_TRUE(rrset);
EXPECT_EQ(RRType::SOA(), rrset->getType());
+ // RRType::NS() RRset
+ rrset = iterator->getNextRRset();
+ EXPECT_TRUE(rrset);
+ EXPECT_EQ(RRType::NS(), rrset->getType());
+
// RRType::MX() RRset
rrset = iterator->getNextRRset();
EXPECT_TRUE(rrset);
@@ -377,6 +383,10 @@ TEST_F(MemoryClientTest, loadReloadZone) {
EXPECT_EQ(RRType::SOA(), set->type);
set = set->getNext();
+ EXPECT_NE(static_cast<const RdataSet*>(NULL), set);
+ EXPECT_EQ(RRType::NS(), set->type);
+
+ set = set->getNext();
EXPECT_EQ(static_cast<const RdataSet*>(NULL), set);
/* Check ns1.example.org */
@@ -402,6 +412,10 @@ TEST_F(MemoryClientTest, loadReloadZone) {
EXPECT_EQ(RRType::SOA(), set->type);
set = set->getNext();
+ EXPECT_NE(static_cast<const RdataSet*>(NULL), set);
+ EXPECT_EQ(RRType::NS(), set->type);
+
+ set = set->getNext();
EXPECT_EQ(static_cast<const RdataSet*>(NULL), set);
/* Check ns1.example.org */
@@ -636,9 +650,14 @@ TEST_F(MemoryClientTest, getIterator) {
ZoneIteratorPtr iterator(client_->getIterator(Name("example.org")));
// First we have the SOA
- ConstRRsetPtr rrset_soa(iterator->getNextRRset());
- EXPECT_TRUE(rrset_soa);
- EXPECT_EQ(RRType::SOA(), rrset_soa->getType());
+ ConstRRsetPtr rrset(iterator->getNextRRset());
+ EXPECT_TRUE(rrset);
+ EXPECT_EQ(RRType::SOA(), rrset->getType());
+
+ // Then the NS
+ rrset = iterator->getNextRRset();
+ EXPECT_TRUE(rrset);
+ EXPECT_EQ(RRType::NS(), rrset->getType());
// There's nothing else in this iterator
EXPECT_EQ(ConstRRsetPtr(), iterator->getNextRRset());
@@ -659,6 +678,11 @@ TEST_F(MemoryClientTest, getIteratorSeparateRRs) {
EXPECT_TRUE(rrset);
EXPECT_EQ(RRType::SOA(), rrset->getType());
+ // Then, the NS
+ rrset = iterator->getNextRRset();
+ EXPECT_TRUE(rrset);
+ EXPECT_EQ(RRType::NS(), rrset->getType());
+
// Only one RRType::A() RRset
rrset = iterator->getNextRRset();
EXPECT_TRUE(rrset);
@@ -667,7 +691,6 @@ TEST_F(MemoryClientTest, getIteratorSeparateRRs) {
// There's nothing else in this zone
EXPECT_EQ(ConstRRsetPtr(), iterator->getNextRRset());
-
// separate_rrs = true
ZoneIteratorPtr iterator2(client_->getIterator(Name("example.org"), true));
@@ -676,6 +699,11 @@ TEST_F(MemoryClientTest, getIteratorSeparateRRs) {
EXPECT_TRUE(rrset);
EXPECT_EQ(RRType::SOA(), rrset->getType());
+ // Then, the NS
+ rrset = iterator2->getNextRRset();
+ EXPECT_TRUE(rrset);
+ EXPECT_EQ(RRType::NS(), rrset->getType());
+
// First RRType::A() RRset
rrset = iterator2->getNextRRset();
EXPECT_TRUE(rrset);
@@ -751,6 +779,11 @@ TEST_F(MemoryClientTest, findZoneData) {
EXPECT_NE(static_cast<const RdataSet*>(NULL), set);
EXPECT_EQ(RRType::SOA(), set->type);
+ /* Check NS */
+ set = set->getNext();
+ EXPECT_NE(static_cast<const RdataSet*>(NULL), set);
+ EXPECT_EQ(RRType::NS(), set->type);
+
set = set->getNext();
EXPECT_EQ(static_cast<const RdataSet*>(NULL), set);
diff --git a/src/lib/datasrc/tests/memory/rrset_collection_unittest.cc b/src/lib/datasrc/tests/memory/rrset_collection_unittest.cc
new file mode 100644
index 0000000..04e285b
--- /dev/null
+++ b/src/lib/datasrc/tests/memory/rrset_collection_unittest.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/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 <gtest/gtest.h>
+
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+using namespace std;
+using namespace isc::datasrc;
+using namespace isc::datasrc::memory;
+using namespace isc::datasrc::memory::detail;
+
+namespace {
+
+// Note: This class uses loadZoneData() to construct a ZoneData object,
+// which internally uses an RRsetCollection for validation. We assume
+// that loadZoneData() works at this point and test the RRsetCollection
+// around the ZoneData returned.
+class RRsetCollectionTest : public ::testing::Test {
+public:
+ RRsetCollectionTest() :
+ 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)
+ {}
+
+ const RRClass rrclass;
+ const Name origin;
+ std::string zone_file;
+ test::MemorySegmentTest mem_sgmt;
+ SegmentObjectHolder<ZoneData, RRClass> zone_data_holder;
+ RRsetCollection collection;
+};
+
+TEST_F(RRsetCollectionTest, find) {
+ const RRsetCollection& ccln = collection;
+ ConstRRsetPtr rrset = ccln.find(Name("www.example.org"), rrclass,
+ RRType::A());
+ EXPECT_TRUE(rrset);
+ EXPECT_EQ(RRType::A(), rrset->getType());
+ EXPECT_EQ(RRTTL(3600), rrset->getTTL());
+ EXPECT_EQ(RRClass("IN"), rrset->getClass());
+ EXPECT_EQ(Name("www.example.org"), rrset->getName());
+
+ // foo.example.org doesn't exist
+ rrset = ccln.find(Name("foo.example.org"), rrclass, RRType::A());
+ EXPECT_FALSE(rrset);
+
+ // www.example.org exists, but not with MX
+ rrset = ccln.find(Name("www.example.org"), rrclass, RRType::MX());
+ EXPECT_FALSE(rrset);
+
+ // www.example.org exists, with AAAA
+ rrset = ccln.find(Name("www.example.org"), rrclass, RRType::AAAA());
+ EXPECT_TRUE(rrset);
+
+ // www.example.org with AAAA does not exist in RRClass::CH()
+ rrset = ccln.find(Name("www.example.org"), RRClass::CH(),
+ RRType::AAAA());
+ EXPECT_FALSE(rrset);
+}
+
+} // namespace
diff --git a/src/lib/datasrc/tests/memory/testdata/Makefile.am b/src/lib/datasrc/tests/memory/testdata/Makefile.am
index ef5f08b..b076837 100644
--- a/src/lib/datasrc/tests/memory/testdata/Makefile.am
+++ b/src/lib/datasrc/tests/memory/testdata/Makefile.am
@@ -29,6 +29,7 @@ EXTRA_DIST += example.org-rrsigs.zone
EXTRA_DIST += example.org-wildcard-dname.zone
EXTRA_DIST += example.org-wildcard-ns.zone
EXTRA_DIST += example.org-wildcard-nsec3.zone
+EXTRA_DIST += rrset-collection.zone
EXTRA_DIST += 2503-test.zone
EXTRA_DIST += 2504-test.zone
diff --git a/src/lib/datasrc/tests/memory/testdata/example.org-duplicate-type.zone b/src/lib/datasrc/tests/memory/testdata/example.org-duplicate-type.zone
index 6e5c1b8..799b54e 100644
--- a/src/lib/datasrc/tests/memory/testdata/example.org-duplicate-type.zone
+++ b/src/lib/datasrc/tests/memory/testdata/example.org-duplicate-type.zone
@@ -1,4 +1,5 @@
-example.org. 3600 IN SOA ns1.example.org. bugs.x.w.example.org. 76 3600 300 3600000 3600
+example.org. 3600 IN SOA ns1.example.org. bugs.x.w.example.org. 77 3600 300 3600000 3600
+example.org. 3600 IN NS ns1.example.org.
ns1.example.org. 3600 IN A 192.168.0.1
ns1.example.org. 3600 IN A 192.168.0.2
ns1.example.org. 3600 IN AAAA ::1
diff --git a/src/lib/datasrc/tests/memory/testdata/example.org-empty.zone b/src/lib/datasrc/tests/memory/testdata/example.org-empty.zone
index f11b9b8..899a412 100644
--- a/src/lib/datasrc/tests/memory/testdata/example.org-empty.zone
+++ b/src/lib/datasrc/tests/memory/testdata/example.org-empty.zone
@@ -1,2 +1,3 @@
;; empty example.org zone
-example.org. 3600 IN SOA ns1.example.org. bugs.x.w.example.org. 68 3600 300 3600000 3600
+example.org. 3600 IN SOA ns1.example.org. bugs.x.w.example.org. 71 3600 300 3600000 3600
+example.org. 3600 IN NS ns1.example.org.
diff --git a/src/lib/datasrc/tests/memory/testdata/example.org-multiple.zone b/src/lib/datasrc/tests/memory/testdata/example.org-multiple.zone
index f473ae6..b94d806 100644
--- a/src/lib/datasrc/tests/memory/testdata/example.org-multiple.zone
+++ b/src/lib/datasrc/tests/memory/testdata/example.org-multiple.zone
@@ -1,4 +1,5 @@
;; Multiple RDATA for testing separate RRs iterator
-example.org. 3600 IN SOA ns1.example.org. bugs.x.w.example.org. 78 3600 300 3600000 3600
+example.org. 3600 IN SOA ns1.example.org. bugs.x.w.example.org. 80 3600 300 3600000 3600
+example.org. 3600 IN NS ns1.example.org.
a.example.org. 3600 IN A 192.168.0.1
a.example.org. 3600 IN A 192.168.0.2
diff --git a/src/lib/datasrc/tests/memory/testdata/example.org-rrsig-follows-nothing.zone b/src/lib/datasrc/tests/memory/testdata/example.org-rrsig-follows-nothing.zone
index ef5f887..59dda88 100644
--- a/src/lib/datasrc/tests/memory/testdata/example.org-rrsig-follows-nothing.zone
+++ b/src/lib/datasrc/tests/memory/testdata/example.org-rrsig-follows-nothing.zone
@@ -1,5 +1,6 @@
;; test zone file used for ZoneFinderContext tests.
;; RRSIGs are (obviouslly) faked ones for testing.
-example.org. 3600 IN SOA ns1.example.org. bugs.x.w.example.org. 68 3600 300 3600000 3600
+example.org. 3600 IN SOA ns1.example.org. bugs.x.w.example.org. 69 3600 300 3600000 3600
+example.org. 3600 IN NS ns1.example.org.
ns1.example.org. 3600 IN RRSIG A 7 3 3600 20150420235959 20051021000000 40430 example.org. FAKEFAKE
diff --git a/src/lib/datasrc/tests/memory/testdata/example.org-rrsigs.zone b/src/lib/datasrc/tests/memory/testdata/example.org-rrsigs.zone
index 1c780b1..3ea070f 100644
--- a/src/lib/datasrc/tests/memory/testdata/example.org-rrsigs.zone
+++ b/src/lib/datasrc/tests/memory/testdata/example.org-rrsigs.zone
@@ -1,7 +1,8 @@
;; test zone file used for ZoneFinderContext tests.
;; RRSIGs are (obviouslly) faked ones for testing.
-example.org. 3600 IN SOA ns1.example.org. bugs.x.w.example.org. 74 3600 300 3600000 3600
+example.org. 3600 IN SOA ns1.example.org. bugs.x.w.example.org. 75 3600 300 3600000 3600
+example.org. 3600 IN NS ns1.example.org.
ns1.example.org. 3600 IN A 192.168.0.1
ns1.example.org. 3600 IN RRSIG A 7 3 3600 20150420235959 20051021000000 40430 example.org. FAKEFAKE
ns1.example.org. 3600 IN RRSIG A 7 2 3600 20150420235959 20051021000000 40430 example.org. FAKEFAKE
diff --git a/src/lib/datasrc/tests/memory/testdata/rrset-collection.zone b/src/lib/datasrc/tests/memory/testdata/rrset-collection.zone
new file mode 100644
index 0000000..332848f
--- /dev/null
+++ b/src/lib/datasrc/tests/memory/testdata/rrset-collection.zone
@@ -0,0 +1,4 @@
+example.org. 3600 IN SOA ns1.example.org. bugs.x.w.example.org. 72 3600 300 3600000 3600
+example.org. 3600 IN NS ns1.example.org.
+www.example.org. 3600 IN A 192.0.2.6
+www.example.org. 3600 IN AAAA 2001:db8::6
diff --git a/src/lib/datasrc/tests/zone_loader_unittest.cc b/src/lib/datasrc/tests/zone_loader_unittest.cc
index f0ad814..0f44074 100644
--- a/src/lib/datasrc/tests/zone_loader_unittest.cc
+++ b/src/lib/datasrc/tests/zone_loader_unittest.cc
@@ -21,6 +21,7 @@
#include <dns/rrclass.h>
#include <dns/name.h>
#include <dns/rrset.h>
+#include <dns/rdataclass.h>
#include <util/memory_segment_local.h>
#include <exceptions/exceptions.h>
@@ -32,15 +33,11 @@
#include <string>
#include <vector>
-using isc::dns::RRClass;
-using isc::dns::Name;
-using isc::dns::RRType;
-using isc::dns::ConstRRsetPtr;
-using isc::dns::RRsetPtr;
+using namespace isc::dns;
+using namespace isc::datasrc;
+using boost::shared_ptr;
using std::string;
using std::vector;
-using boost::shared_ptr;
-using namespace isc::datasrc;
namespace {
@@ -51,8 +48,97 @@ public:
missing_zone_(false),
rrclass_(RRClass::IN())
{}
- virtual FindResult findZone(const Name&) const {
- isc_throw(isc::NotImplemented, "Method not used in tests");
+ class Finder : public ZoneFinder {
+ public:
+ Finder(const Name& origin) :
+ origin_(origin)
+ {}
+ Name getOrigin() const {
+ return (origin_);
+ }
+ RRClass getClass() const {
+ return (RRClass::IN());
+ }
+ // The rest is not to be called, so they throw.
+ 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) :
+ 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_);
+
+ // There is no NS record on purpose here.
+
+ // Dummy A rrset. This is used for checking zone data after
+ // reload.
+ RRsetPtr rrset(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_;
+ };
+ virtual ZoneIteratorPtr getIterator(const isc::dns::Name& name,
+ bool) const
+ {
+ if (name != Name("example.org")) {
+ isc_throw(DataSourceError, "No such zone");
+ }
+ return (ZoneIteratorPtr(new Iterator(Name("example.org"))));
+ }
+ virtual FindResult findZone(const Name& name) const {
+ const Name origin("example.org");
+ const ZoneFinderPtr finder(new Finder(origin));
+ NameComparisonResult compar(origin.compare(name));
+ 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()));
+ }
};
virtual std::pair<ZoneJournalReader::Result, ZoneJournalReaderPtr>
getJournalReader(const Name&, uint32_t, uint32_t) const
@@ -214,13 +300,6 @@ protected:
source_client_(ztable_segment_, rrclass_)
{}
void prepareSource(const Name& zone, const char* filename) {
- // TODO:
- // Currently, source_client_ is of InMemoryClient and its load()
- // uses a different code than the ZoneLoader (so we can cross-check
- // the implementations). Currently, the load() doesn't perform any
- // post-load checks. It will change in #2499, at which point the
- // loading may start failing depending on details of the test data. We
- // should prepare the data by some different method then.
source_client_.load(zone, string(TEST_DATA_DIR) + "/" + filename);
}
private:
@@ -246,6 +325,11 @@ TEST_F(ZoneLoaderTest, copyUnsigned) {
// 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]);
+
+ // Counter is initialized to 0, progress is "unknown" in case of copy.
+ EXPECT_EQ(0, loader.getRRCount());
+ EXPECT_EQ(ZoneLoader::PROGRESS_UNKNOWN, loader.getProgress());
+
// Now load the whole zone
loader.load();
EXPECT_TRUE(destination_client_.commit_called_);
@@ -254,6 +338,12 @@ TEST_F(ZoneLoaderTest, copyUnsigned) {
// The count is 34 because we expect the RRs to be separated.
EXPECT_EQ(34, destination_client_.rrsets_.size());
+
+ // Check various counters. getRRCount should be identical of the RRs
+ // we've seen. Progress is still "unknown" in the copy operation.
+ EXPECT_EQ(destination_client_.rrsets_.size(), loader.getRRCount());
+ EXPECT_EQ(ZoneLoader::PROGRESS_UNKNOWN, loader.getProgress());
+
// Ensure known order.
std::sort(destination_client_.rrset_texts_.begin(),
destination_client_.rrset_texts_.end());
@@ -281,6 +371,11 @@ TEST_F(ZoneLoaderTest, copyUnsignedIncremental) {
// Not committed yet, we didn't complete the loading
EXPECT_FALSE(destination_client_.commit_called_);
+ // Check we can get intermediate counters. Progress is always "unknown"
+ // in case of copy.
+ EXPECT_EQ(destination_client_.rrsets_.size(), loader.getRRCount());
+ EXPECT_EQ(ZoneLoader::PROGRESS_UNKNOWN, loader.getProgress());
+
// This is unusual, but allowed. Check it doesn't do anything
loader.loadIncremental(0);
EXPECT_EQ(10, destination_client_.rrsets_.size());
@@ -349,6 +444,11 @@ TEST_F(ZoneLoaderTest, classMismatch) {
TEST_F(ZoneLoaderTest, loadUnsigned) {
ZoneLoader loader(destination_client_, Name::ROOT_NAME(),
TEST_DATA_DIR "/root.zone");
+
+ // Counter and progress are initialized to 0.
+ EXPECT_EQ(0, loader.getRRCount());
+ EXPECT_EQ(0, loader.getProgress());
+
// 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]);
@@ -360,6 +460,12 @@ TEST_F(ZoneLoaderTest, loadUnsigned) {
// The count is 34 because we expect the RRs to be separated.
EXPECT_EQ(34, destination_client_.rrsets_.size());
+
+ // getRRCount should be identical of the RRs we've seen. progress
+ // should reach 100% (= 1).
+ EXPECT_EQ(destination_client_.rrsets_.size(), loader.getRRCount());
+ EXPECT_EQ(1, loader.getProgress());
+
// Ensure known order.
std::sort(destination_client_.rrset_texts_.begin(),
destination_client_.rrset_texts_.end());
@@ -380,6 +486,10 @@ TEST_F(ZoneLoaderTest, loadUnsignedIncremental) {
ZoneLoader loader(destination_client_, Name::ROOT_NAME(),
TEST_DATA_DIR "/root.zone");
+ // Counters are initialized to 0.
+ EXPECT_EQ(0, loader.getRRCount());
+ EXPECT_EQ(0, loader.getProgress());
+
// Try loading few RRs first.
loader.loadIncremental(10);
// We should get the 10 we asked for
@@ -390,11 +500,26 @@ TEST_F(ZoneLoaderTest, loadUnsignedIncremental) {
EXPECT_EQ(10, destination_client_.rrsets_.size());
EXPECT_FALSE(destination_client_.commit_called_);
+ // Check we can get intermediate counters. Expected progress is calculated
+ // based on the size of the zone file and the offset to the end of 10th RR
+ // (subject to future changes to the file, but we assume it's a rare
+ // event.). The expected value should be the exact expression that
+ // getProgress() should do internally, so EXPECT_EQ() should work here,
+ // but floating-point comparison can be always tricky we use
+ // EXPECT_DOUBLE_EQ just in case.
+ EXPECT_EQ(destination_client_.rrsets_.size(), loader.getRRCount());
+ // file size = 1541, offset = 428 (27.77%).
+ EXPECT_DOUBLE_EQ(static_cast<double>(428) / 1541, loader.getProgress());
+
// We can finish the rest
loader.loadIncremental(30);
EXPECT_EQ(34, destination_client_.rrsets_.size());
EXPECT_TRUE(destination_client_.commit_called_);
+ // Counters are updated accordingly. Progress should reach 100%.
+ EXPECT_EQ(destination_client_.rrsets_.size(), loader.getRRCount());
+ EXPECT_EQ(1, loader.getProgress());
+
// No more loading now
EXPECT_THROW(loader.load(), isc::InvalidOperation);
EXPECT_THROW(loader.loadIncremental(1), isc::InvalidOperation);
@@ -461,17 +586,6 @@ TEST_F(ZoneLoaderTest, loadCheck) {
EXPECT_FALSE(destination_client_.commit_called_);
}
-// The same test, but for copying from other data source
-TEST_F(ZoneLoaderTest, copyCheck) {
- prepareSource(Name("example.org"), "novalidate.zone");
- ZoneLoader loader(destination_client_, Name("example.org"),
- source_client_);
-
- EXPECT_THROW(loader.loadIncremental(10), ZoneContentError);
- // The messages go to the log. We don't have an easy way to examine them.
- EXPECT_FALSE(destination_client_.commit_called_);
-}
-
// Check a warning doesn't disrupt the loading of the zone
TEST_F(ZoneLoaderTest, loadCheckWarn) {
ZoneLoader loader(destination_client_, Name("example.org"),
@@ -495,4 +609,18 @@ TEST_F(ZoneLoaderTest, copyCheckWarn) {
}
+// Test there's validation of the data in the zone loader when copying
+// from another data source.
+TEST_F(ZoneLoaderTest, copyCheck) {
+ // In this test, my_source_client provides a zone that does not
+ // validate (no NS).
+ MockClient my_source_client;
+ ZoneLoader loader(destination_client_, Name("example.org"),
+ my_source_client);
+
+ EXPECT_THROW(loader.loadIncremental(10), ZoneContentError);
+ // The messages go to the log. We don't have an easy way to examine them.
+ EXPECT_FALSE(destination_client_.commit_called_);
+}
+
}
diff --git a/src/lib/datasrc/zone_loader.cc b/src/lib/datasrc/zone_loader.cc
index 098b47a..9e9dd4a 100644
--- a/src/lib/datasrc/zone_loader.cc
+++ b/src/lib/datasrc/zone_loader.cc
@@ -34,17 +34,20 @@
using isc::dns::Name;
using isc::dns::ConstRRsetPtr;
using isc::dns::MasterLoader;
+using isc::dns::MasterLexer;
namespace isc {
namespace datasrc {
+const double ZoneLoader::PROGRESS_UNKNOWN = -1;
+
ZoneLoader::ZoneLoader(DataSourceClient& destination, const Name& zone_name,
DataSourceClient& source) :
// Separate the RRsets as that is possibly faster (the data source doesn't
// have to aggregate them) and also because our limit semantics.
iterator_(source.getIterator(zone_name, true)),
updater_(destination.getUpdater(zone_name, true, false)),
- complete_(false)
+ complete_(false), rr_count_(0)
{
// The getIterator should never return NULL. So we check it.
// Or should we throw instead?
@@ -65,11 +68,25 @@ ZoneLoader::ZoneLoader(DataSourceClient& destination, const Name& zone_name,
}
}
+namespace {
+// Unified callback to install RR and increment RR count at the same time.
+void
+addRR(ZoneUpdater* updater, size_t* rr_count,
+ const dns::Name& name, const dns::RRClass& rrclass,
+ const dns::RRType& type, const dns::RRTTL& ttl,
+ const dns::rdata::RdataPtr& data)
+{
+ isc::dns::BasicRRset rrset(name, rrclass, type, ttl);
+ rrset.addRdata(data);
+ updater->addRRset(rrset);
+ ++*rr_count;
+}
+}
+
ZoneLoader::ZoneLoader(DataSourceClient& destination, const Name& zone_name,
const char* filename) :
updater_(destination.getUpdater(zone_name, true, false)),
- complete_(false),
- loaded_ok_(true)
+ complete_(false), loaded_ok_(true), rr_count_(0)
{
if (updater_ == ZoneUpdaterPtr()) {
isc_throw(DataSourceError, "Zone " << zone_name << " not found in "
@@ -83,7 +100,9 @@ ZoneLoader::ZoneLoader(DataSourceClient& destination, const Name& zone_name,
createMasterLoaderCallbacks(zone_name,
updater_->getFinder().getClass(),
&loaded_ok_),
- createMasterLoaderAddCallback(*updater_)));
+ boost::bind(addRR,
+ updater_.get(), &rr_count_,
+ _1, _2, _3, _4, _5)));
}
}
@@ -92,7 +111,7 @@ namespace {
// Copy up to limit RRsets from source to destination
bool
copyRRsets(const ZoneUpdaterPtr& destination, const ZoneIteratorPtr& source,
- size_t limit)
+ size_t limit, size_t& rr_count_)
{
size_t loaded = 0;
while (loaded < limit) {
@@ -104,6 +123,7 @@ copyRRsets(const ZoneUpdaterPtr& destination, const ZoneIteratorPtr& source,
destination->addRRset(*rrset);
}
++loaded;
+ rr_count_ += rrset->getRdataCount();
}
return (false); // Not yet, there may be more
}
@@ -133,8 +153,8 @@ ZoneLoader::loadIncremental(size_t limit) {
"Loading has been completed previously");
}
- if (iterator_ == ZoneIteratorPtr()) {
- assert(loader_.get() != NULL);
+ if (!iterator_) {
+ assert(loader_);
try {
complete_ = loader_->loadIncremental(limit);
} catch (const isc::dns::MasterLoaderError& e) {
@@ -144,7 +164,7 @@ ZoneLoader::loadIncremental(size_t limit) {
isc_throw(MasterFileError, "Error while loading master file");
}
} else {
- complete_ = copyRRsets(updater_, iterator_, limit);
+ complete_ = copyRRsets(updater_, iterator_, limit, rr_count_);
}
if (complete_) {
@@ -166,5 +186,36 @@ ZoneLoader::loadIncremental(size_t limit) {
return (complete_);
}
+size_t
+ZoneLoader::getRRCount() const {
+ return (rr_count_);
+}
+
+double
+ZoneLoader::getProgress() const {
+ if (!loader_) {
+ return (PROGRESS_UNKNOWN);
+ }
+
+ const size_t pos = loader_->getPosition();
+ const size_t total_size = loader_->getSize();
+
+ // If the current position is 0, progress should definitely be 0; we
+ // don't bother to check the total size even if it's "unknown".
+ if (pos == 0) {
+ return (0);
+ }
+
+ // These cases shouldn't happen with our usage of MasterLoader. So, in
+ // theory, we could throw here; however, since this method is expected
+ // to be used for informational purposes only, that's probably too harsh.
+ // So we return "unknown" instead.
+ if (total_size == MasterLexer::SOURCE_SIZE_UNKNOWN || total_size == 0) {
+ return (PROGRESS_UNKNOWN);
+ }
+
+ return (static_cast<double>(pos) / total_size);
+}
+
} // end namespace datasrc
} // end namespace isc
diff --git a/src/lib/datasrc/zone_loader.h b/src/lib/datasrc/zone_loader.h
index c7e5ccd..2a4559e 100644
--- a/src/lib/datasrc/zone_loader.h
+++ b/src/lib/datasrc/zone_loader.h
@@ -154,10 +154,66 @@ public:
/// \throw ZoneContentError when the zone doesn't pass sanity check.
/// \note If the limit is exactly the number of RRs available to be loaded,
/// the method still returns false and true'll be returned on the next
- /// call (which will load 0 RRs). This is because the end of iterator or
- /// master file is detected when reading past the end, not when the last
- /// one is read.
+ /// call (which will load 0 RRs). This is because the end of iterator
+ /// or master file is detected when reading past the end, not when the
+ /// last one is read.
bool loadIncremental(size_t limit);
+
+ /// \brief Return the number of RRs loaded.
+ ///
+ /// This method returns the number of RRs loaded via this loader by the
+ /// time of the call. Before starting the load it will return 0.
+ /// It will return the total number of RRs of the zone on and after
+ /// completing the load.
+ ///
+ /// \throw None
+ size_t getRRCount() const;
+
+ /// \brief Return the current progress of the loader.
+ ///
+ /// This method returns the current estimated progress of loader as a
+ /// value between 0 and 1 (inclusive); it's 0 before starting the load,
+ /// and 1 at the completion, and a value between these (exclusive) in the
+ /// middle of loading. It's an implementation detail how to calculate
+ /// the progress, which may vary depending on how the loader is
+ /// constructed and may even be impossible to detect effectively.
+ ///
+ /// If the progress cannot be determined, this method returns a special
+ /// value of PROGRESS_UNKNOWN, which is not included in the range between
+ /// 0 and 1.
+ ///
+ /// As such, the application should use the return value only for
+ /// informational purposes such as logging. For example, it shouldn't
+ /// be used to determine whether loading is completed by comparing it
+ /// to 1. It should also expect the possibility of getting
+ /// \c PROGRESS_UNKNOWN at any call to this method; it shouldn't assume
+ /// the specific way of internal implementation as described below (which
+ /// is provided for informational purposes only).
+ ///
+ /// In this implementation, if the loader is constructed with a file
+ /// name, the progress value is measured by the number of characters
+ /// read from the zone file divided by the size of the zone file
+ /// (with taking into account any included files). Note that due to
+ /// the possibility of intermediate included files, the total file size
+ /// cannot be fully fixed until the completion of the load. And, due to
+ /// this possibility, return values from this method may not always
+ /// increase monotonically.
+ ///
+ /// If it's constructed with another data source client, this method
+ /// always returns \c PROGRESS_UNKNOWN; in future, however, it may become
+ /// possible to return something more useful, e.g, based on the result
+ /// of \c getRRCount() and the total number of RRs if the underlying data
+ /// source can provide the latter value efficiently.
+ ///
+ /// \throw None
+ double getProgress() const;
+
+ /// \brief A special value for \c getProgress, meaning the progress is
+ /// unknown.
+ ///
+ /// See the method description for details.
+ static const double PROGRESS_UNKNOWN;
+
private:
/// \brief The iterator used as source of data in case of the copy mode.
const ZoneIteratorPtr iterator_;
@@ -169,9 +225,14 @@ private:
bool complete_;
/// \brief Was the loading successful?
bool loaded_ok_;
+ size_t rr_count_;
};
}
}
-#endif
+#endif // DATASRC_ZONE_LOADER_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dhcp/Makefile.am b/src/lib/dhcp/Makefile.am
index 822c4e3..f169fe6 100644
--- a/src/lib/dhcp/Makefile.am
+++ b/src/lib/dhcp/Makefile.am
@@ -32,6 +32,7 @@ libb10_dhcp___la_SOURCES += option.cc option.h
libb10_dhcp___la_SOURCES += option_custom.cc option_custom.h
libb10_dhcp___la_SOURCES += option_data_types.cc option_data_types.h
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 += std_option_defs.h
diff --git a/src/lib/dhcp/libdhcp++.cc b/src/lib/dhcp/libdhcp++.cc
index a3921cc..78f511d 100644
--- a/src/lib/dhcp/libdhcp++.cc
+++ b/src/lib/dhcp/libdhcp++.cc
@@ -267,20 +267,12 @@ size_t LibDHCP::unpackOptions4(const OptionBuffer& buf,
return (offset);
}
-void LibDHCP::packOptions6(isc::util::OutputBuffer &buf,
- const isc::dhcp::Option::OptionCollection& options) {
- for (Option::OptionCollection::const_iterator it = options.begin();
- it != options.end(); ++it) {
- it->second->pack(buf);
- }
-}
-
void
LibDHCP::packOptions(isc::util::OutputBuffer& buf,
const Option::OptionCollection& options) {
for (Option::OptionCollection::const_iterator it = options.begin();
it != options.end(); ++it) {
- it->second->pack4(buf);
+ it->second->pack(buf);
}
}
@@ -329,10 +321,35 @@ LibDHCP::initStdOptionDefs4() {
// Now let's add all option definitions.
for (int i = 0; i < OPTION_DEF_PARAMS_SIZE4; ++i) {
- OptionDefinitionPtr definition(new OptionDefinition(OPTION_DEF_PARAMS4[i].name,
- OPTION_DEF_PARAMS4[i].code,
- OPTION_DEF_PARAMS4[i].type,
- OPTION_DEF_PARAMS4[i].array));
+ std::string encapsulates(OPTION_DEF_PARAMS4[i].encapsulates);
+ if (!encapsulates.empty() && OPTION_DEF_PARAMS4[i].array) {
+ isc_throw(isc::BadValue, "invalid standard option definition: "
+ << "option with code '" << OPTION_DEF_PARAMS4[i].code
+ << "' may not encapsulate option space '"
+ << encapsulates << "' because the definition"
+ << " indicates that this option comprises an array"
+ << " of values");
+ }
+
+ // Depending whether the option encapsulates an option space or not
+ // we pick different constructor to create an instance of the option
+ // definition.
+ OptionDefinitionPtr definition;
+ if (encapsulates.empty()) {
+ // Option does not encapsulate any option space.
+ definition.reset(new OptionDefinition(OPTION_DEF_PARAMS4[i].name,
+ OPTION_DEF_PARAMS4[i].code,
+ OPTION_DEF_PARAMS4[i].type,
+ OPTION_DEF_PARAMS4[i].array));
+
+ } else {
+ // Option does encapsulate an option space.
+ definition.reset(new OptionDefinition(OPTION_DEF_PARAMS4[i].name,
+ OPTION_DEF_PARAMS4[i].code,
+ OPTION_DEF_PARAMS4[i].type,
+ OPTION_DEF_PARAMS4[i].encapsulates));
+
+ }
for (int rec = 0; rec < OPTION_DEF_PARAMS4[i].records_size; ++rec) {
definition->addRecordField(OPTION_DEF_PARAMS4[i].records[rec]);
@@ -358,10 +375,34 @@ LibDHCP::initStdOptionDefs6() {
v6option_defs_.clear();
for (int i = 0; i < OPTION_DEF_PARAMS_SIZE6; ++i) {
- OptionDefinitionPtr definition(new OptionDefinition(OPTION_DEF_PARAMS6[i].name,
- OPTION_DEF_PARAMS6[i].code,
- OPTION_DEF_PARAMS6[i].type,
- OPTION_DEF_PARAMS6[i].array));
+ std::string encapsulates(OPTION_DEF_PARAMS6[i].encapsulates);
+ if (!encapsulates.empty() && OPTION_DEF_PARAMS6[i].array) {
+ isc_throw(isc::BadValue, "invalid standard option definition: "
+ << "option with code '" << OPTION_DEF_PARAMS6[i].code
+ << "' may not encapsulate option space '"
+ << encapsulates << "' because the definition"
+ << " indicates that this option comprises an array"
+ << " of values");
+ }
+
+ // Depending whether an option encapsulates an option space or not
+ // we pick different constructor to create an instance of the option
+ // definition.
+ OptionDefinitionPtr definition;
+ if (encapsulates.empty()) {
+ // Option does not encapsulate any option space.
+ definition.reset(new OptionDefinition(OPTION_DEF_PARAMS6[i].name,
+ OPTION_DEF_PARAMS6[i].code,
+ OPTION_DEF_PARAMS6[i].type,
+ OPTION_DEF_PARAMS6[i].array));
+ } else {
+ // Option does encapsulate an option space.
+ definition.reset(new OptionDefinition(OPTION_DEF_PARAMS6[i].name,
+ OPTION_DEF_PARAMS6[i].code,
+ OPTION_DEF_PARAMS6[i].type,
+ OPTION_DEF_PARAMS6[i].encapsulates));
+
+ }
for (int rec = 0; rec < OPTION_DEF_PARAMS6[i].records_size; ++rec) {
definition->addRecordField(OPTION_DEF_PARAMS6[i].records[rec]);
diff --git a/src/lib/dhcp/libdhcp++.h b/src/lib/dhcp/libdhcp++.h
index bc47405..c6594b9 100644
--- a/src/lib/dhcp/libdhcp++.h
+++ b/src/lib/dhcp/libdhcp++.h
@@ -88,16 +88,6 @@ public:
uint16_t type,
const OptionBuffer& buf);
- /// Builds collection of options.
- ///
- /// Builds raw (on-wire) data for provided collection of options.
- ///
- /// @param buf output buffer (assembled options will be stored here)
- /// @param options collection of options to store to
- static void packOptions6(isc::util::OutputBuffer& buf,
- const isc::dhcp::Option::OptionCollection& options);
-
-
/// @brief Stores options in a buffer.
///
/// Stores all options defined in options containers in a on-wire
diff --git a/src/lib/dhcp/option.cc b/src/lib/dhcp/option.cc
index dbdac0c..e06b163 100644
--- a/src/lib/dhcp/option.cc
+++ b/src/lib/dhcp/option.cc
@@ -84,51 +84,14 @@ Option::check() {
}
void Option::pack(isc::util::OutputBuffer& buf) {
- switch (universe_) {
- case V6:
- return (pack6(buf));
-
- case V4:
- return (pack4(buf));
-
- default:
- isc_throw(BadValue, "Failed to pack " << type_ << " option as the "
- << "universe type is unknown.");
+ // Write a header.
+ packHeader(buf);
+ // Write data.
+ if (!data_.empty()) {
+ buf.writeData(&data_[0], data_.size());
}
-}
-
-void
-Option::pack4(isc::util::OutputBuffer& buf) {
- if (universe_ == V4) {
- // Write a header.
- packHeader(buf);
- // Write data.
- if (!data_.empty()) {
- buf.writeData(&data_[0], data_.size());
- }
- // Write sub-options.
- packOptions(buf);
- } else {
- isc_throw(BadValue, "Invalid universe type " << universe_);
- }
-
- return;
-}
-
-void Option::pack6(isc::util::OutputBuffer& buf) {
- if (universe_ == V6) {
- // Write a header.
- packHeader(buf);
- // Write data.
- if (!data_.empty()) {
- buf.writeData(&data_[0], data_.size());
- }
- // Write sub-options.
- packOptions(buf);
- } else {
- isc_throw(BadValue, "Invalid universe type " << universe_);
- }
- return;
+ // Write sub-options.
+ packOptions(buf);
}
void
@@ -153,16 +116,7 @@ Option::packHeader(isc::util::OutputBuffer& buf) {
void
Option::packOptions(isc::util::OutputBuffer& buf) {
- switch (universe_) {
- case V4:
- LibDHCP::packOptions(buf, options_);
- return;
- case V6:
- LibDHCP::packOptions6(buf, options_);
- return;
- default:
- isc_throw(isc::BadValue, "Invalid universe type " << universe_);
- }
+ LibDHCP::packOptions(buf, options_);
}
void Option::unpack(OptionBufferConstIter begin,
diff --git a/src/lib/dhcp/option.h b/src/lib/dhcp/option.h
index e4105cc..553e825 100644
--- a/src/lib/dhcp/option.h
+++ b/src/lib/dhcp/option.h
@@ -158,28 +158,13 @@ public:
///
/// Writes option in wire-format to buffer, returns pointer to first unused
/// byte after stored option (that is useful for writing options one after
- /// another). Used in DHCPv6 options.
- ///
- /// @todo Migrate DHCPv6 code to pack(OutputBuffer& buf) version
+ /// another).
///
/// @param buf pointer to a buffer
///
/// @throw BadValue Universe of the option is neither V4 nor V6.
virtual void pack(isc::util::OutputBuffer& buf);
- /// @brief Writes option in a wire-format to a buffer.
- ///
- /// Method will throw if option storing fails for some reason.
- ///
- /// @todo Once old (DHCPv6) implementation is rewritten,
- /// unify pack4() and pack6() and rename them to just pack().
- ///
- /// @param buf output buffer (option will be stored there)
- ///
- /// @throw OutOfRange Option type is greater than 255.
- /// @throw BadValue Universe is not V4.
- virtual void pack4(isc::util::OutputBuffer& buf);
-
/// @brief Parses received buffer.
///
/// @param begin iterator to first byte of option data
@@ -317,13 +302,6 @@ public:
virtual bool equal(const OptionPtr& other) const;
protected:
- /// Builds raw (over-wire) buffer of this option, including all
- /// defined suboptions. Version for building DHCPv4 options.
- ///
- /// @param buf output buffer (built options will be stored here)
- ///
- /// @throw BadValue Universe is not V6.
- virtual void pack6(isc::util::OutputBuffer& buf);
/// @brief Store option's header in a buffer.
///
diff --git a/src/lib/dhcp/option4_addrlst.cc b/src/lib/dhcp/option4_addrlst.cc
index 86da9f6..436d07d 100644
--- a/src/lib/dhcp/option4_addrlst.cc
+++ b/src/lib/dhcp/option4_addrlst.cc
@@ -64,7 +64,7 @@ Option4AddrLst::Option4AddrLst(uint8_t type, const IOAddress& addr)
}
void
-Option4AddrLst::pack4(isc::util::OutputBuffer& buf) {
+Option4AddrLst::pack(isc::util::OutputBuffer& buf) {
if (addrs_.size() * V4ADDRESS_LEN > 255) {
isc_throw(OutOfRange, "DHCPv4 Option4AddrLst " << type_ << " is too big."
diff --git a/src/lib/dhcp/option4_addrlst.h b/src/lib/dhcp/option4_addrlst.h
index 927f75b..0240f3e 100644
--- a/src/lib/dhcp/option4_addrlst.h
+++ b/src/lib/dhcp/option4_addrlst.h
@@ -87,11 +87,8 @@ public:
///
/// Method will throw if option storing fails for some reason.
///
- /// TODO Once old (DHCPv6) implementation is rewritten,
- /// unify pack4() and pack6() and rename them to just pack().
- ///
/// @param buf output buffer (option will be stored there)
- virtual void pack4(isc::util::OutputBuffer& buf);
+ virtual void pack(isc::util::OutputBuffer& buf);
/// Returns string representation of the option.
///
diff --git a/src/lib/dhcp/option_custom.cc b/src/lib/dhcp/option_custom.cc
index 068e360..3d2a1a9 100644
--- a/src/lib/dhcp/option_custom.cc
+++ b/src/lib/dhcp/option_custom.cc
@@ -387,14 +387,10 @@ OptionCustom::dataFieldToText(const OptionDataType data_type,
}
void
-OptionCustom::pack4(isc::util::OutputBuffer& buf) {
- if (len() > 255) {
- isc_throw(OutOfRange, "DHCPv4 Option " << type_
- << " value is too high. At most 255 is supported.");
- }
+OptionCustom::pack(isc::util::OutputBuffer& buf) {
- buf.writeUint8(type_);
- buf.writeUint8(len() - getHeaderLen());
+ // Pack DHCP header (V4 or V6).
+ packHeader(buf);
// Write data from buffers.
for (std::vector<OptionBuffer>::const_iterator it = buffers_.begin();
@@ -411,21 +407,6 @@ OptionCustom::pack4(isc::util::OutputBuffer& buf) {
packOptions(buf);
}
-void
-OptionCustom::pack6(isc::util::OutputBuffer& buf) {
- buf.writeUint16(type_);
- buf.writeUint16(len() - getHeaderLen());
-
- // Write data from buffers.
- for (std::vector<OptionBuffer>::const_iterator it = buffers_.begin();
- it != buffers_.end(); ++it) {
- if (!it->empty()) {
- buf.writeData(&(*it)[0], it->size());
- }
- }
-
- packOptions(buf);
-}
asiolink::IOAddress
OptionCustom::readAddress(const uint32_t index) const {
diff --git a/src/lib/dhcp/option_custom.h b/src/lib/dhcp/option_custom.h
index 0ee4688..c25347b 100644
--- a/src/lib/dhcp/option_custom.h
+++ b/src/lib/dhcp/option_custom.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
@@ -249,6 +249,11 @@ public:
void writeString(const std::string& text,
const uint32_t index = 0);
+ /// @brief Writes DHCP option in a wire format to a buffer.
+ ///
+ /// @param buf output buffer (option will be stored there).
+ virtual void pack(isc::util::OutputBuffer& buf);
+
/// @brief Parses received buffer.
///
/// @param begin iterator to first byte of option data
@@ -278,18 +283,6 @@ public:
void setData(const OptionBufferConstIter first,
const OptionBufferConstIter last);
-protected:
-
- /// @brief Writes DHCPv4 option in a wire format to a buffer.
- ///
- /// @param buf output buffer (option will be stored there).
- virtual void pack4(isc::util::OutputBuffer& buf);
-
- /// @brief Writes DHCPv6 option in a wire format to a buffer.
- ///
- /// @param buf output buffer (built options will be stored here)
- virtual void pack6(isc::util::OutputBuffer& buf);
-
private:
/// @brief Verify that the option comprises an array of values.
@@ -352,6 +345,9 @@ private:
std::vector<OptionBuffer> buffers_;
};
+/// A pointer to the OptionCustom object.
+typedef boost::shared_ptr<OptionCustom> OptionCustomPtr;
+
} // namespace isc::dhcp
} // namespace isc
diff --git a/src/lib/dhcp/option_definition.cc b/src/lib/dhcp/option_definition.cc
index d2b5aae..59ff022 100644
--- a/src/lib/dhcp/option_definition.cc
+++ b/src/lib/dhcp/option_definition.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
@@ -21,6 +21,7 @@
#include <dhcp/option_definition.h>
#include <dhcp/option_int.h>
#include <dhcp/option_int_array.h>
+#include <dhcp/option_space.h>
#include <util/encode/hex.h>
#include <util/strutil.h>
#include <boost/algorithm/string/classification.hpp>
@@ -40,7 +41,8 @@ OptionDefinition::OptionDefinition(const std::string& name,
: name_(name),
code_(code),
type_(OPT_UNKNOWN_TYPE),
- array_type_(array_type) {
+ array_type_(array_type),
+ encapsulated_space_("") {
// Data type is held as enum value by this class.
// Use the provided option type string to get the
// corresponding enum value.
@@ -54,7 +56,33 @@ OptionDefinition::OptionDefinition(const std::string& name,
: name_(name),
code_(code),
type_(type),
- array_type_(array_type) {
+ array_type_(array_type),
+ encapsulated_space_("") {
+}
+
+OptionDefinition::OptionDefinition(const std::string& name,
+ const uint16_t code,
+ const std::string& type,
+ const char* encapsulated_space)
+ : name_(name),
+ code_(code),
+ // Data type is held as enum value by this class.
+ // Use the provided option type string to get the
+ // corresponding enum value.
+ type_(OptionDataTypeUtil::getDataType(type)),
+ array_type_(false),
+ encapsulated_space_(encapsulated_space) {
+}
+
+OptionDefinition::OptionDefinition(const std::string& name,
+ const uint16_t code,
+ const OptionDataType type,
+ const char* encapsulated_space)
+ : name_(name),
+ code_(code),
+ type_(type),
+ array_type_(false),
+ encapsulated_space_(encapsulated_space) {
}
void
@@ -188,8 +216,8 @@ OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
const RecordFieldsCollection& records = getRecordFields();
if (records.size() > values.size()) {
isc_throw(InvalidOptionValue, "number of data fields for the option"
- << " type " << type_ << " is greater than number of values"
- << " provided.");
+ << " type '" << getCode() << "' is greater than number"
+ << " of values provided.");
}
for (size_t i = 0; i < records.size(); ++i) {
writeToBuffer(util::str::trim(values[i]),
@@ -228,6 +256,11 @@ OptionDefinition::validate() const {
all(find_tail(name_, 1), boost::is_any_of(std::string("-_")))) {
err_str << "invalid option name '" << name_ << "'";
+ } else if (!encapsulated_space_.empty() &&
+ !OptionSpace::validateName(encapsulated_space_)) {
+ err_str << "invalid encapsulated option space name: '"
+ << encapsulated_space_ << "'";
+
} else if (type_ >= OPT_UNKNOWN_TYPE) {
// Option definition must be of a known type.
err_str << "option type value " << type_ << " is out of range.";
@@ -411,14 +444,8 @@ OptionDefinition::writeToBuffer(const std::string& value,
OptionDataTypeUtil::writeString(value, buf);
return;
case OPT_FQDN_TYPE:
- {
- // FQDN implementation is not terribly complicated but will require
- // creation of some additional logic (maybe object) that will parse
- // the fqdn into labels.
- isc_throw(isc::NotImplemented, "write of FQDN record into option buffer"
- " is not supported yet");
- return;
- }
+ OptionDataTypeUtil::writeFqdn(value, buf);
+ return;
default:
// We hit this point because invalid option data type has been specified
// This may be the case because 'empty' or 'record' data type has been
diff --git a/src/lib/dhcp/option_definition.h b/src/lib/dhcp/option_definition.h
index efcaba0..df5def7 100644
--- a/src/lib/dhcp/option_definition.h
+++ b/src/lib/dhcp/option_definition.h
@@ -146,10 +146,10 @@ public:
/// @param type option data type as string.
/// @param array_type array indicator, if true it indicates that the
/// option fields are the array.
- OptionDefinition(const std::string& name,
- const uint16_t code,
- const std::string& type,
- const bool array_type = false);
+ explicit OptionDefinition(const std::string& name,
+ const uint16_t code,
+ const std::string& type,
+ const bool array_type = false);
/// @brief Constructor.
///
@@ -158,10 +158,49 @@ public:
/// @param type option data type.
/// @param array_type array indicator, if true it indicates that the
/// option fields are the array.
- OptionDefinition(const std::string& name,
- const uint16_t code,
- const OptionDataType type,
- const bool array_type = false);
+ explicit OptionDefinition(const std::string& name,
+ const uint16_t code,
+ const OptionDataType type,
+ const bool array_type = false);
+
+ /// @brief Constructor.
+ ///
+ /// 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.
+ /// This constructor does not allow to set array indicator
+ /// because options comprising an array of data fields must
+ /// not be used with sub-options.
+ ///
+ /// @param name option name.
+ /// @param code option code.
+ /// @param type option data type given as string.
+ /// @param encapsulated_space name of the option space being
+ /// encapsulated by this option.
+ explicit OptionDefinition(const std::string& name,
+ const uint16_t code,
+ const std::string& type,
+ const char* encapsulated_space);
+
+ /// @brief Constructor.
+ ///
+ /// 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.
+ /// This constructor does not allow to set array indicator
+ /// because options comprising an array of data fields must
+ /// not be used with sub-options.
+ ///
+ /// @param name option name.
+ /// @param code option code.
+ /// @param type option data type.
+ /// @param encapsulated_space name of the option space being
+ /// encapsulated by this option.
+ explicit OptionDefinition(const std::string& name,
+ const uint16_t code,
+ const OptionDataType type,
+ const char* encapsulated_space);
+
/// @brief Adds data field to the record.
///
@@ -192,10 +231,17 @@ public:
/// @return option code.
uint16_t getCode() const { return (code_); }
+ /// @brief Return name of the encapsulated option space.
+ ///
+ /// @return name of the encapsulated option space.
+ std::string getEncapsulatedSpace() const {
+ return (encapsulated_space_);
+ }
+
/// @brief Return option name.
///
/// @return option name.
- const std::string& getName() const { return (name_); }
+ std::string getName() const { return (name_); }
/// @brief Return list of record fields.
///
@@ -456,6 +502,8 @@ private:
OptionDataType type_;
/// Indicates wheter option is a single value or array.
bool array_type_;
+ /// Name of the space being encapsulated by this option.
+ std::string encapsulated_space_;
/// Collection of data fields within the record.
RecordFieldsCollection record_fields_;
};
diff --git a/src/lib/dhcp/option_space.cc b/src/lib/dhcp/option_space.cc
new file mode 100644
index 0000000..f9f5bee
--- /dev/null
+++ b/src/lib/dhcp/option_space.cc
@@ -0,0 +1,71 @@
+// 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 <dhcp/option_space.h>
+#include <boost/algorithm/string/classification.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+
+namespace isc {
+namespace dhcp {
+
+OptionSpace::OptionSpace(const std::string& name, const bool vendor_space)
+ : name_(name), vendor_space_(vendor_space) {
+ // Check that provided option space name is valid.
+ if (!validateName(name_)) {
+ isc_throw(InvalidOptionSpace, "Invalid option space name "
+ << name_);
+ }
+}
+
+bool
+OptionSpace::validateName(const std::string& name) {
+
+ using namespace boost::algorithm;
+
+ // Allowed characters are: lower or upper case letters, digits,
+ // underscores and hyphens. Empty option spaces are not allowed.
+ if (all(name, boost::is_from_range('a', 'z') ||
+ boost::is_from_range('A', 'Z') ||
+ boost::is_digit() ||
+ boost::is_any_of(std::string("-_"))) &&
+ !name.empty() &&
+ // Hyphens and underscores are not allowed at the beginning
+ // and at the end of the option space name.
+ !all(find_head(name, 1), boost::is_any_of(std::string("-_"))) &&
+ !all(find_tail(name, 1), boost::is_any_of(std::string("-_")))) {
+ return (true);
+
+ }
+ return (false);
+}
+
+OptionSpace6::OptionSpace6(const std::string& name)
+ : OptionSpace(name),
+ enterprise_number_(0) {
+}
+
+OptionSpace6::OptionSpace6(const std::string& name,
+ const uint32_t enterprise_number)
+ : OptionSpace(name, true),
+ enterprise_number_(enterprise_number) {
+}
+
+void
+OptionSpace6::setVendorSpace(const uint32_t enterprise_number) {
+ enterprise_number_ = enterprise_number;
+ OptionSpace::setVendorSpace();
+}
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
diff --git a/src/lib/dhcp/option_space.h b/src/lib/dhcp/option_space.h
new file mode 100644
index 0000000..9eebd76
--- /dev/null
+++ b/src/lib/dhcp/option_space.h
@@ -0,0 +1,189 @@
+// 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.
+
+#ifndef OPTION_SPACE_H
+#define OPTION_SPACE_H
+
+#include <exceptions/exceptions.h>
+#include <boost/shared_ptr.hpp>
+#include <map>
+#include <stdint.h>
+#include <string>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Exception to be thrown when invalid option space
+/// is specified.
+class InvalidOptionSpace : public Exception {
+public:
+ InvalidOptionSpace(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// OptionSpace forward declaration.
+class OptionSpace;
+/// A pointer to OptionSpace object.
+typedef boost::shared_ptr<OptionSpace> OptionSpacePtr;
+/// A collection of option spaces.
+typedef std::map<std::string, OptionSpacePtr> OptionSpaceCollection;
+
+/// @brief DHCP option space.
+///
+/// This class represents single option space. The option spaces are used
+/// to group DHCP options having unique option codes. The special type
+/// of the option space is so called "vendor specific option space".
+/// It groups sub-options being sent within Vendor Encapsulated Options.
+/// For DHCPv4 it is the option with code 43. The option spaces are
+/// assigned to option instances represented by isc::dhcp::Option and
+/// other classes derived from it. Each particular option may belong to
+/// multiple option spaces.
+/// This class may be used to represent any DHCPv4 option space. If the
+/// option space is to group DHCPv4 Vendor Encapsulated Options then
+/// "vendor space" flag must be set using \ref OptionSpace::setVendorSpace
+/// or the argument passed to the constructor. In theory, this class can
+/// be also used to represent non-vendor specific DHCPv6 option space
+/// but this is discouraged. For DHCPv6 option spaces the OptionSpace6
+/// class should be used instead.
+///
+/// @note this class is intended to be used to represent DHCPv4 option
+/// spaces only. However, it hasn't been called OptionSpace4 (that would
+/// suggest that it is specific to DHCPv4) because it can be also
+/// used to represent some DHCPv6 option spaces and is a base class
+/// for \ref OptionSpace6. Thus, if one declared the container as follows:
+/// @code
+/// std::vector<OptionSpace4> container;
+/// @endcode
+/// it would suggest that the container holds DHCPv4 option spaces while
+/// it could hold both DHCPv4 and DHCPv6 option spaces as the OptionSpace6
+/// object could be upcast to OptionSpace4. This confusion does not appear
+/// when OptionSpace is used as a name for the base class.
+class OptionSpace {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param name option space name.
+ /// @param vendor_space boolean value that indicates that the object
+ /// describes the vendor specific option space.
+ ///
+ /// @throw isc::dhcp::InvalidOptionSpace if given option space name
+ /// contains invalid characters or is empty. This constructor uses
+ /// \ref validateName function to check that the specified name is
+ /// correct.
+ OptionSpace(const std::string& name, const bool vendor_space = false);
+
+ /// @brief Return option space name.
+ ///
+ /// @return option space name.
+ const std::string& getName() const { return (name_); }
+
+ /// @brief Mark option space as non-vendor space.
+ void clearVendorSpace() {
+ vendor_space_ = false;
+ }
+
+ /// @brief Check if option space is vendor specific.
+ ///
+ /// @return boolean value that indicates if the object describes
+ /// the vendor specific option space.
+ bool isVendorSpace() const { return (vendor_space_); }
+
+ /// @brief Mark option space as vendor specific.
+ void setVendorSpace() {
+ vendor_space_ = true;
+ }
+
+ /// @brief Checks that the provided option space name is valid.
+ ///
+ /// It is expected that option space name consists of upper or
+ /// lower case letters or digits. Also, it may contain underscores
+ /// or dashes. Other characters are prohibited. The empty option
+ /// space names are invalid.
+ ///
+ /// @param name option space name to be validated.
+ ///
+ /// @return true if the option space is valid, else it returns false.
+ static bool validateName(const std::string& name);
+
+private:
+ std::string name_; ///< Holds option space name.
+
+ bool vendor_space_; ///< Is this the vendor space?
+
+};
+
+/// @brief DHCPv6 option space with enterprise number assigned.
+///
+/// This class extends the base class with the support for enterprise numbers.
+/// The enterprise numbers are assigned by IANA to various organizations
+/// and they are carried as uint32_t integers in DHCPv6 Vendor Specific
+/// Information Options (VSIO). For more information refer to RFC3315.
+/// All option spaces that group VSIO options must have enterprise number
+/// set. It can be set using a constructor or \ref setVendorSpace function.
+/// The extra functionality of this class (enterprise numbers) allows to
+/// represent DHCPv6 vendor-specific option spaces but this class is also
+/// intended to be used for all other DHCPv6 option spaces. That way all
+/// DHCPv6 option spaces can be stored in the container holding OptionSpace6
+/// objects. Also, it is easy to mark vendor-specific option space as non-vendor
+/// specific option space (and the other way around) without a need to cast
+/// between OptionSpace and OptionSpace6 types.
+class OptionSpace6 : public OptionSpace {
+public:
+
+ /// @brief Constructor for non-vendor-specific options.
+ ///
+ /// This constructor marks option space as non-vendor specific.
+ ///
+ /// @param name option space name.
+ ///
+ /// @throw isc::dhcp::InvalidOptionSpace if given option space name
+ /// contains invalid characters or is empty. This constructor uses
+ /// \ref OptionSpace::validateName function to check that the specified
+ /// name is correct.
+ OptionSpace6(const std::string& name);
+
+ /// @brief Constructor for vendor-specific options.
+ ///
+ /// This constructor marks option space as vendor specific and sets
+ /// enterprise number to a given value.
+ ///
+ /// @param name option space name.
+ /// @param enterprise_number enterprise number.
+ ///
+ /// @throw isc::dhcp::InvalidOptionSpace if given option space name
+ /// contains invalid characters or is empty. This constructor uses
+ /// \ref OptionSpace::validateName function to check that the specified
+ /// name is correct.
+ OptionSpace6(const std::string& name, const uint32_t enterprise_number);
+
+ /// @brief Return enterprise number for the option space.
+ ///
+ /// @return enterprise number.
+ uint32_t getEnterpriseNumber() const { return (enterprise_number_); }
+
+ /// @brief Mark option space as vendor specific.
+ ///
+ /// @param enterprise_number enterprise number.
+ void setVendorSpace(const uint32_t enterprise_number);
+
+private:
+
+ uint32_t enterprise_number_; ///< IANA assigned enterprise number.
+};
+
+} // namespace isc::dhcp
+} // namespace isc
+
+#endif // OPTION_SPACE_H
diff --git a/src/lib/dhcp/pkt4.cc b/src/lib/dhcp/pkt4.cc
index d3b22de..0592807 100644
--- a/src/lib/dhcp/pkt4.cc
+++ b/src/lib/dhcp/pkt4.cc
@@ -219,7 +219,7 @@ void Pkt4::check() {
uint8_t msg_type = getType();
if (msg_type > DHCPLEASEACTIVE) {
isc_throw(BadValue, "Invalid DHCP message type received: "
- << msg_type);
+ << static_cast<int>(msg_type));
}
}
@@ -230,10 +230,10 @@ uint8_t Pkt4::getType() const {
}
// Check if Message Type is specified as OptionInt<uint8_t>
- boost::shared_ptr<OptionInt<uint8_t> > typeOpt =
+ boost::shared_ptr<OptionInt<uint8_t> > type_opt =
boost::dynamic_pointer_cast<OptionInt<uint8_t> >(generic);
- if (typeOpt) {
- return (typeOpt->getValue());
+ if (type_opt) {
+ return (type_opt->getValue());
}
// Try to use it as generic option
@@ -253,7 +253,6 @@ void Pkt4::setType(uint8_t dhcp_type) {
}
}
-
void Pkt4::repack() {
bufferOut_.writeData(&data_[0], data_.size());
}
@@ -263,7 +262,7 @@ Pkt4::toText() {
stringstream tmp;
tmp << "localAddr=" << local_addr_.toText() << ":" << local_port_
<< " remoteAddr=" << remote_addr_.toText()
- << ":" << remote_port_ << ", msgtype=" << getType()
+ << ":" << remote_port_ << ", msgtype=" << static_cast<int>(getType())
<< ", transid=0x" << hex << transid_ << dec << endl;
for (isc::dhcp::Option::OptionCollection::iterator opt=options_.begin();
diff --git a/src/lib/dhcp/pkt6.cc b/src/lib/dhcp/pkt6.cc
index 2c97b07..c3a98bf 100644
--- a/src/lib/dhcp/pkt6.cc
+++ b/src/lib/dhcp/pkt6.cc
@@ -90,7 +90,7 @@ Pkt6::packUDP() {
bufferOut_.writeUint8( (transid_) & 0xff );
// the rest are options
- LibDHCP::packOptions6(bufferOut_, options_);
+ LibDHCP::packOptions(bufferOut_, options_);
}
catch (const Exception& e) {
/// @todo: throw exception here once we turn this function to void.
@@ -155,8 +155,8 @@ Pkt6::toText() {
tmp << "localAddr=[" << local_addr_.toText() << "]:" << local_port_
<< " remoteAddr=[" << remote_addr_.toText()
<< "]:" << remote_port_ << endl;
- tmp << "msgtype=" << msg_type_ << ", transid=0x" << hex << transid_
- << dec << endl;
+ tmp << "msgtype=" << static_cast<int>(msg_type_) << ", transid=0x" <<
+ hex << transid_ << dec << endl;
for (isc::dhcp::Option::OptionCollection::iterator opt=options_.begin();
opt != options_.end();
++opt) {
diff --git a/src/lib/dhcp/std_option_defs.h b/src/lib/dhcp/std_option_defs.h
index 144df8b..839a5d9 100644
--- a/src/lib/dhcp/std_option_defs.h
+++ b/src/lib/dhcp/std_option_defs.h
@@ -50,6 +50,8 @@ struct OptionDefParams {
bool array; // is array
const OptionDataType* records; // record fields
size_t records_size; // number of fields in a record
+ const char* encapsulates; // option space encapsulated by
+ // the particular option.
};
// fqdn option record fields.
@@ -64,128 +66,128 @@ RECORD_DECL(FQDN_RECORDS, OPT_UINT8_TYPE, OPT_UINT8_TYPE, OPT_STRING_TYPE);
/// @brief Definitions of standard DHCPv4 options.
const OptionDefParams OPTION_DEF_PARAMS4[] = {
- { "subnet-mask", DHO_SUBNET_MASK, OPT_IPV4_ADDRESS_TYPE, false, NO_RECORD_DEF },
- { "time-offset", DHO_TIME_OFFSET, OPT_UINT32_TYPE, false, NO_RECORD_DEF },
- { "routers", DHO_ROUTERS, OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF },
- { "time-servers", DHO_TIME_SERVERS, OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF },
+ { "subnet-mask", DHO_SUBNET_MASK, OPT_IPV4_ADDRESS_TYPE, false, NO_RECORD_DEF, "" },
+ { "time-offset", DHO_TIME_OFFSET, OPT_UINT32_TYPE, false, NO_RECORD_DEF, "" },
+ { "routers", DHO_ROUTERS, OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
+ { "time-servers", DHO_TIME_SERVERS, OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
{ "name-servers", DHO_NAME_SERVERS, OPT_IPV4_ADDRESS_TYPE,
- false, NO_RECORD_DEF },
+ false, NO_RECORD_DEF, "" },
{ "domain-name-servers", DHO_DOMAIN_NAME_SERVERS,
- OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF },
- { "log-servers", DHO_LOG_SERVERS, OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF },
+ OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
+ { "log-servers", DHO_LOG_SERVERS, OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
{ "cookie-servers", DHO_COOKIE_SERVERS, OPT_IPV4_ADDRESS_TYPE,
- true, NO_RECORD_DEF },
- { "lpr-servers", DHO_LPR_SERVERS, OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF },
- { "impress-servers", DHO_IMPRESS_SERVERS, OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF },
+ true, NO_RECORD_DEF, "" },
+ { "lpr-servers", DHO_LPR_SERVERS, OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
+ { "impress-servers", DHO_IMPRESS_SERVERS, OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
{ "resource-location-servers", DHO_RESOURCE_LOCATION_SERVERS,
- OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF },
- { "host-name", DHO_HOST_NAME, OPT_STRING_TYPE, false, NO_RECORD_DEF },
- { "boot-size", DHO_BOOT_SIZE, OPT_UINT16_TYPE, false, NO_RECORD_DEF },
- { "merit-dump", DHO_MERIT_DUMP, OPT_STRING_TYPE, false, NO_RECORD_DEF },
- { "domain-name", DHO_DOMAIN_NAME, OPT_FQDN_TYPE, false, NO_RECORD_DEF },
- { "swap-server", DHO_SWAP_SERVER, OPT_IPV4_ADDRESS_TYPE, false, NO_RECORD_DEF },
- { "root-path", DHO_ROOT_PATH, OPT_STRING_TYPE, false, NO_RECORD_DEF },
+ OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
+ { "host-name", DHO_HOST_NAME, OPT_STRING_TYPE, false, NO_RECORD_DEF, "" },
+ { "boot-size", DHO_BOOT_SIZE, OPT_UINT16_TYPE, false, NO_RECORD_DEF, "" },
+ { "merit-dump", DHO_MERIT_DUMP, OPT_STRING_TYPE, false, NO_RECORD_DEF, "" },
+ { "domain-name", DHO_DOMAIN_NAME, OPT_FQDN_TYPE, false, NO_RECORD_DEF, "" },
+ { "swap-server", DHO_SWAP_SERVER, OPT_IPV4_ADDRESS_TYPE, false, NO_RECORD_DEF, "" },
+ { "root-path", DHO_ROOT_PATH, OPT_STRING_TYPE, false, NO_RECORD_DEF, "" },
{ "extensions-path", DHO_EXTENSIONS_PATH, OPT_STRING_TYPE,
- false, NO_RECORD_DEF },
- { "ip-forwarding", DHO_IP_FORWARDING, OPT_BOOLEAN_TYPE, false, NO_RECORD_DEF },
+ false, NO_RECORD_DEF, "" },
+ { "ip-forwarding", DHO_IP_FORWARDING, OPT_BOOLEAN_TYPE, false, NO_RECORD_DEF, "" },
{ "non-local-source-routing", DHO_NON_LOCAL_SOURCE_ROUTING,
- OPT_BOOLEAN_TYPE, false, NO_RECORD_DEF },
- { "policy-filter", DHO_POLICY_FILTER, OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF },
+ OPT_BOOLEAN_TYPE, false, NO_RECORD_DEF, "" },
+ { "policy-filter", DHO_POLICY_FILTER, OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
{ "max-dgram-reassembly", DHO_MAX_DGRAM_REASSEMBLY,
- OPT_UINT16_TYPE, false, NO_RECORD_DEF },
- { "default-ip-ttl", DHO_DEFAULT_IP_TTL, OPT_UINT8_TYPE, false, NO_RECORD_DEF },
+ OPT_UINT16_TYPE, false, NO_RECORD_DEF, "" },
+ { "default-ip-ttl", DHO_DEFAULT_IP_TTL, OPT_UINT8_TYPE, false, NO_RECORD_DEF, "" },
{ "path-mtu-aging-timeout", DHO_PATH_MTU_AGING_TIMEOUT,
- OPT_UINT32_TYPE, false, NO_RECORD_DEF },
+ OPT_UINT32_TYPE, false, NO_RECORD_DEF, "" },
{ "path-mtu-plateau-table", DHO_PATH_MTU_PLATEAU_TABLE,
- OPT_UINT16_TYPE, true, NO_RECORD_DEF },
- { "interface-mtu", DHO_INTERFACE_MTU, OPT_UINT16_TYPE, false, NO_RECORD_DEF },
+ OPT_UINT16_TYPE, true, NO_RECORD_DEF, "" },
+ { "interface-mtu", DHO_INTERFACE_MTU, OPT_UINT16_TYPE, false, NO_RECORD_DEF, "" },
{ "all-subnets-local", DHO_ALL_SUBNETS_LOCAL,
- OPT_BOOLEAN_TYPE, false, NO_RECORD_DEF },
+ OPT_BOOLEAN_TYPE, false, NO_RECORD_DEF, "" },
{ "broadcast-address", DHO_BROADCAST_ADDRESS,
- OPT_IPV4_ADDRESS_TYPE, false, NO_RECORD_DEF },
+ OPT_IPV4_ADDRESS_TYPE, false, NO_RECORD_DEF, "" },
{ "perform-mask-discovery", DHO_PERFORM_MASK_DISCOVERY,
- OPT_BOOLEAN_TYPE, false, NO_RECORD_DEF },
- { "mask-supplier", DHO_MASK_SUPPLIER, OPT_BOOLEAN_TYPE, false, NO_RECORD_DEF },
+ OPT_BOOLEAN_TYPE, false, NO_RECORD_DEF, "" },
+ { "mask-supplier", DHO_MASK_SUPPLIER, OPT_BOOLEAN_TYPE, false, NO_RECORD_DEF, "" },
{ "router-discovery", DHO_ROUTER_DISCOVERY,
- OPT_BOOLEAN_TYPE, false, NO_RECORD_DEF },
+ OPT_BOOLEAN_TYPE, false, NO_RECORD_DEF, "" },
{ "router-solicitation-address", DHO_ROUTER_SOLICITATION_ADDRESS,
- OPT_IPV4_ADDRESS_TYPE, false, NO_RECORD_DEF },
+ OPT_IPV4_ADDRESS_TYPE, false, NO_RECORD_DEF, "" },
{ "static-routes", DHO_STATIC_ROUTES,
- OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF },
+ OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
{ "trailer-encapsulation", DHO_TRAILER_ENCAPSULATION,
- OPT_BOOLEAN_TYPE, false, NO_RECORD_DEF },
+ OPT_BOOLEAN_TYPE, false, NO_RECORD_DEF, "" },
{ "arp-cache-timeout", DHO_ARP_CACHE_TIMEOUT,
- OPT_UINT32_TYPE, false, NO_RECORD_DEF },
+ OPT_UINT32_TYPE, false, NO_RECORD_DEF, "" },
{ "ieee802-3-encapsulation", DHO_IEEE802_3_ENCAPSULATION,
- OPT_BOOLEAN_TYPE, false, NO_RECORD_DEF },
- { "default-tcp-ttl", DHO_DEFAULT_TCP_TTL, OPT_UINT8_TYPE, false, NO_RECORD_DEF },
+ OPT_BOOLEAN_TYPE, false, NO_RECORD_DEF, "" },
+ { "default-tcp-ttl", DHO_DEFAULT_TCP_TTL, OPT_UINT8_TYPE, false, NO_RECORD_DEF, "" },
{ "tcp-keepalive-internal", DHO_TCP_KEEPALIVE_INTERVAL,
- OPT_UINT32_TYPE, false, NO_RECORD_DEF },
+ OPT_UINT32_TYPE, false, NO_RECORD_DEF, "" },
{ "tcp-keepalive-garbage", DHO_TCP_KEEPALIVE_GARBAGE,
- OPT_BOOLEAN_TYPE, false, NO_RECORD_DEF },
- { "nis-domain", DHO_NIS_DOMAIN, OPT_STRING_TYPE, false, NO_RECORD_DEF },
- { "nis-servers", DHO_NIS_SERVERS, OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF },
- { "ntp-servers", DHO_NTP_SERVERS, OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF },
+ OPT_BOOLEAN_TYPE, false, NO_RECORD_DEF, "" },
+ { "nis-domain", DHO_NIS_DOMAIN, OPT_STRING_TYPE, false, NO_RECORD_DEF, "" },
+ { "nis-servers", DHO_NIS_SERVERS, OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
+ { "ntp-servers", DHO_NTP_SERVERS, OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
{ "vendor-encapsulated-options", DHO_VENDOR_ENCAPSULATED_OPTIONS,
- OPT_BINARY_TYPE, false, NO_RECORD_DEF },
+ OPT_EMPTY_TYPE, false, NO_RECORD_DEF, "vendor-encapsulated-options-space" },
{ "netbios-name-servers", DHO_NETBIOS_NAME_SERVERS,
- OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF },
+ OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
{ "netbios-dd-server", DHO_NETBIOS_DD_SERVER,
- OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF },
+ OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
{ "netbios-node-type", DHO_NETBIOS_NODE_TYPE,
- OPT_UINT8_TYPE, false, NO_RECORD_DEF },
- { "netbios-scope", DHO_NETBIOS_SCOPE, OPT_STRING_TYPE, false, NO_RECORD_DEF },
- { "font-servers", DHO_FONT_SERVERS, OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF },
+ OPT_UINT8_TYPE, false, NO_RECORD_DEF, "" },
+ { "netbios-scope", DHO_NETBIOS_SCOPE, OPT_STRING_TYPE, false, NO_RECORD_DEF, "" },
+ { "font-servers", DHO_FONT_SERVERS, OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
{ "x-display-manager", DHO_X_DISPLAY_MANAGER,
- OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF },
+ OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
{ "dhcp-requested-address", DHO_DHCP_REQUESTED_ADDRESS,
- OPT_IPV4_ADDRESS_TYPE, false, NO_RECORD_DEF },
- { "dhcp-lease-time", DHO_DHCP_LEASE_TIME, OPT_UINT32_TYPE, false, NO_RECORD_DEF },
+ OPT_IPV4_ADDRESS_TYPE, false, NO_RECORD_DEF, "" },
+ { "dhcp-lease-time", DHO_DHCP_LEASE_TIME, OPT_UINT32_TYPE, false, NO_RECORD_DEF, "" },
{ "dhcp-option-overload", DHO_DHCP_OPTION_OVERLOAD,
- OPT_UINT8_TYPE, false, NO_RECORD_DEF },
- { "dhcp-message-type", DHO_DHCP_MESSAGE_TYPE, OPT_UINT8_TYPE, false, NO_RECORD_DEF },
+ OPT_UINT8_TYPE, false, NO_RECORD_DEF, "" },
+ { "dhcp-message-type", DHO_DHCP_MESSAGE_TYPE, OPT_UINT8_TYPE, false, NO_RECORD_DEF, "" },
{ "dhcp-server-identifier", DHO_DHCP_SERVER_IDENTIFIER,
- OPT_IPV4_ADDRESS_TYPE, false, NO_RECORD_DEF },
+ OPT_IPV4_ADDRESS_TYPE, false, NO_RECORD_DEF, "" },
{ "dhcp-parameter-request-list", DHO_DHCP_PARAMETER_REQUEST_LIST,
- OPT_UINT8_TYPE, true, NO_RECORD_DEF },
- { "dhcp-message", DHO_DHCP_MESSAGE, OPT_STRING_TYPE, false, NO_RECORD_DEF },
+ OPT_UINT8_TYPE, true, NO_RECORD_DEF, "" },
+ { "dhcp-message", DHO_DHCP_MESSAGE, OPT_STRING_TYPE, false, NO_RECORD_DEF, "" },
{ "dhcp-max-message-size", DHO_DHCP_MAX_MESSAGE_SIZE,
- OPT_UINT16_TYPE, false, NO_RECORD_DEF },
- { "dhcp-renewal-time", DHO_DHCP_RENEWAL_TIME, OPT_UINT32_TYPE, false, NO_RECORD_DEF },
+ OPT_UINT16_TYPE, false, NO_RECORD_DEF, "" },
+ { "dhcp-renewal-time", DHO_DHCP_RENEWAL_TIME, OPT_UINT32_TYPE, false, NO_RECORD_DEF, "" },
{ "dhcp-rebinding-time", DHO_DHCP_REBINDING_TIME,
- OPT_UINT32_TYPE, false, NO_RECORD_DEF },
+ OPT_UINT32_TYPE, false, NO_RECORD_DEF, "" },
{ "vendor-class-identifier", DHO_VENDOR_CLASS_IDENTIFIER,
- OPT_BINARY_TYPE, false, NO_RECORD_DEF },
+ OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" },
{ "dhcp-client-identifier", DHO_DHCP_CLIENT_IDENTIFIER,
- OPT_BINARY_TYPE, false, NO_RECORD_DEF },
- { "nwip-domain-name", DHO_NWIP_DOMAIN_NAME, OPT_STRING_TYPE, false, NO_RECORD_DEF },
- { "nwip-suboptions", DHO_NWIP_SUBOPTIONS, OPT_BINARY_TYPE, false, NO_RECORD_DEF },
- { "user-class", DHO_USER_CLASS, OPT_BINARY_TYPE, false, NO_RECORD_DEF },
- { "fqdn", DHO_FQDN, OPT_RECORD_TYPE, false, RECORD_DEF(FQDN_RECORDS) },
+ OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" },
+ { "nwip-domain-name", DHO_NWIP_DOMAIN_NAME, OPT_STRING_TYPE, false, NO_RECORD_DEF, "" },
+ { "nwip-suboptions", DHO_NWIP_SUBOPTIONS, OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" },
+ { "user-class", DHO_USER_CLASS, OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" },
+ { "fqdn", DHO_FQDN, OPT_RECORD_TYPE, false, RECORD_DEF(FQDN_RECORDS), "" },
{ "dhcp-agent-options", DHO_DHCP_AGENT_OPTIONS,
- OPT_BINARY_TYPE, false, NO_RECORD_DEF },
+ OPT_EMPTY_TYPE, false, NO_RECORD_DEF, "dhcp-agent-options-space" },
// Unfortunatelly the AUTHENTICATE option contains a 64-bit
// data field called 'replay-detection' that can't be added
// as a record field to a custom option. Also, there is no
// dedicated option class to handle it so we simply return
// binary option type for now.
// @todo implement a class to handle AUTH option.
- { "authenticate", DHO_AUTHENTICATE, OPT_BINARY_TYPE, false, NO_RECORD_DEF },
+ { "authenticate", DHO_AUTHENTICATE, OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" },
{ "client-last-transaction-time", DHO_CLIENT_LAST_TRANSACTION_TIME,
- OPT_UINT32_TYPE, false, NO_RECORD_DEF },
- { "associated-ip", DHO_ASSOCIATED_IP, OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF },
+ OPT_UINT32_TYPE, false, NO_RECORD_DEF, "" },
+ { "associated-ip", DHO_ASSOCIATED_IP, OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
{ "subnet-selection", DHO_SUBNET_SELECTION,
- OPT_IPV4_ADDRESS_TYPE, false, NO_RECORD_DEF },
+ OPT_IPV4_ADDRESS_TYPE, false, NO_RECORD_DEF, "" },
// The following options need a special encoding of data
// being carried by them. Therefore, there is no way they can
// be handled by OptionCustom. We may need to implement
// dedicated classes to handle them. Until that happens
// let's treat them as 'binary' options.
- { "domain-search", DHO_DOMAIN_SEARCH, OPT_BINARY_TYPE, false, NO_RECORD_DEF },
+ { "domain-search", DHO_DOMAIN_SEARCH, OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" },
{ "vivco-suboptions", DHO_VIVCO_SUBOPTIONS,
- OPT_BINARY_TYPE, false, NO_RECORD_DEF },
+ OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" },
{ "vivso-suboptions", DHO_VIVSO_SUBOPTIONS, OPT_BINARY_TYPE,
- false, NO_RECORD_DEF }
+ false, NO_RECORD_DEF, "" }
// @todo add definitions for all remaning options.
};
@@ -222,8 +224,6 @@ RECORD_DECL(REMOTE_ID_RECORDS, OPT_UINT32_TYPE, OPT_BINARY_TYPE);
RECORD_DECL(STATUS_CODE_RECORDS, OPT_UINT16_TYPE, OPT_STRING_TYPE);
// vendor-class
RECORD_DECL(VENDOR_CLASS_RECORDS, OPT_UINT32_TYPE, OPT_BINARY_TYPE);
-// vendor-opts
-RECORD_DECL(VENDOR_OPTS_RECORDS, OPT_UINT32_TYPE, OPT_BINARY_TYPE);
/// Standard DHCPv6 option definitions.
///
@@ -236,84 +236,84 @@ RECORD_DECL(VENDOR_OPTS_RECORDS, OPT_UINT32_TYPE, OPT_BINARY_TYPE);
/// warning about lack of initializers for some struct members
/// causing build to fail.
const OptionDefParams OPTION_DEF_PARAMS6[] = {
- { "clientid", D6O_CLIENTID, OPT_BINARY_TYPE, false, NO_RECORD_DEF },
- { "serverid", D6O_SERVERID, OPT_BINARY_TYPE, false, NO_RECORD_DEF },
- { "ia-na", D6O_IA_NA, OPT_RECORD_TYPE, false, RECORD_DEF(IA_NA_RECORDS) },
- { "ia-ta", D6O_IA_TA, OPT_UINT32_TYPE, false, NO_RECORD_DEF },
- { "iaaddr", D6O_IAADDR, OPT_RECORD_TYPE, false, RECORD_DEF(IAADDR_RECORDS) },
- { "oro", D6O_ORO, OPT_UINT16_TYPE, true, NO_RECORD_DEF },
- { "preference", D6O_PREFERENCE, OPT_UINT8_TYPE, false, NO_RECORD_DEF },
- { "elapsed-time", D6O_ELAPSED_TIME, OPT_UINT16_TYPE, false, NO_RECORD_DEF },
- { "relay-msg", D6O_RELAY_MSG, OPT_BINARY_TYPE, false, NO_RECORD_DEF },
+ { "clientid", D6O_CLIENTID, OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" },
+ { "serverid", D6O_SERVERID, OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" },
+ { "ia-na", D6O_IA_NA, OPT_RECORD_TYPE, false, RECORD_DEF(IA_NA_RECORDS), "" },
+ { "ia-ta", D6O_IA_TA, OPT_UINT32_TYPE, false, NO_RECORD_DEF, "" },
+ { "iaaddr", D6O_IAADDR, OPT_RECORD_TYPE, false, RECORD_DEF(IAADDR_RECORDS), "" },
+ { "oro", D6O_ORO, OPT_UINT16_TYPE, true, NO_RECORD_DEF, "" },
+ { "preference", D6O_PREFERENCE, OPT_UINT8_TYPE, false, NO_RECORD_DEF, "" },
+ { "elapsed-time", D6O_ELAPSED_TIME, OPT_UINT16_TYPE, false, NO_RECORD_DEF, "" },
+ { "relay-msg", D6O_RELAY_MSG, OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" },
// Unfortunatelly the AUTH option contains a 64-bit data field
// called 'replay-detection' that can't be added as a record
// field to a custom option. Also, there is no dedicated
// option class to handle it so we simply return binary
// option type for now.
// @todo implement a class to handle AUTH option.
- { "auth", D6O_AUTH, OPT_BINARY_TYPE, false, NO_RECORD_DEF },
- { "unicast", D6O_UNICAST, OPT_IPV6_ADDRESS_TYPE, false, NO_RECORD_DEF },
+ { "auth", D6O_AUTH, OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" },
+ { "unicast", D6O_UNICAST, OPT_IPV6_ADDRESS_TYPE, false, NO_RECORD_DEF, "" },
{ "status-code", D6O_STATUS_CODE, OPT_RECORD_TYPE, false,
- RECORD_DEF(STATUS_CODE_RECORDS) },
- { "rapid-commit", D6O_RAPID_COMMIT, OPT_EMPTY_TYPE, false, NO_RECORD_DEF },
- { "user-class", D6O_USER_CLASS, OPT_BINARY_TYPE, false, NO_RECORD_DEF },
+ RECORD_DEF(STATUS_CODE_RECORDS), "" },
+ { "rapid-commit", D6O_RAPID_COMMIT, OPT_EMPTY_TYPE, false, NO_RECORD_DEF, "" },
+ { "user-class", D6O_USER_CLASS, OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" },
{ "vendor-class", D6O_VENDOR_CLASS, OPT_RECORD_TYPE, false,
- RECORD_DEF(VENDOR_CLASS_RECORDS) },
- { "vendor-opts", D6O_VENDOR_OPTS, OPT_RECORD_TYPE, false,
- RECORD_DEF(VENDOR_OPTS_RECORDS) },
- { "interface-id", D6O_INTERFACE_ID, OPT_BINARY_TYPE, false, NO_RECORD_DEF },
- { "reconf-msg", D6O_RECONF_MSG, OPT_UINT8_TYPE, false, NO_RECORD_DEF },
+ RECORD_DEF(VENDOR_CLASS_RECORDS), "" },
+ { "vendor-opts", D6O_VENDOR_OPTS, OPT_UINT32_TYPE, false,
+ NO_RECORD_DEF, "vendor-opts-space" },
+ { "interface-id", D6O_INTERFACE_ID, OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" },
+ { "reconf-msg", D6O_RECONF_MSG, OPT_UINT8_TYPE, false, NO_RECORD_DEF, "" },
{ "reconf-accept", D6O_RECONF_ACCEPT, OPT_EMPTY_TYPE, false,
- NO_RECORD_DEF },
+ NO_RECORD_DEF, "" },
{ "sip-server-dns", D6O_SIP_SERVERS_DNS, OPT_FQDN_TYPE, true,
- NO_RECORD_DEF },
+ NO_RECORD_DEF, "" },
{ "sip-server-addr", D6O_SIP_SERVERS_ADDR, OPT_IPV6_ADDRESS_TYPE, true,
- NO_RECORD_DEF },
+ NO_RECORD_DEF, "" },
{ "dns-servers", D6O_NAME_SERVERS, OPT_IPV6_ADDRESS_TYPE, true,
- NO_RECORD_DEF },
- { "domain-search", D6O_DOMAIN_SEARCH, OPT_FQDN_TYPE, true, NO_RECORD_DEF },
- { "ia-pd", D6O_IA_PD, OPT_RECORD_TYPE, false, RECORD_DEF(IA_PD_RECORDS) },
+ NO_RECORD_DEF, "" },
+ { "domain-search", D6O_DOMAIN_SEARCH, OPT_FQDN_TYPE, true, NO_RECORD_DEF, "" },
+ { "ia-pd", D6O_IA_PD, OPT_RECORD_TYPE, false, RECORD_DEF(IA_PD_RECORDS), "" },
{ "iaprefix", D6O_IAPREFIX, OPT_RECORD_TYPE, false,
- RECORD_DEF(IA_PREFIX_RECORDS) },
+ RECORD_DEF(IA_PREFIX_RECORDS), "" },
{ "nis-servers", D6O_NIS_SERVERS, OPT_IPV6_ADDRESS_TYPE, true,
- NO_RECORD_DEF },
+ NO_RECORD_DEF, "" },
{ "nisp-servers", D6O_NISP_SERVERS, OPT_IPV6_ADDRESS_TYPE, true,
- NO_RECORD_DEF },
+ NO_RECORD_DEF, "" },
{ "nis-domain-name", D6O_NIS_DOMAIN_NAME, OPT_FQDN_TYPE, true,
- NO_RECORD_DEF },
+ NO_RECORD_DEF, "" },
{ "nisp-domain-name", D6O_NISP_DOMAIN_NAME, OPT_FQDN_TYPE, true,
- NO_RECORD_DEF },
+ NO_RECORD_DEF, "" },
{ "sntp-servers", D6O_SNTP_SERVERS, OPT_IPV6_ADDRESS_TYPE, true,
- NO_RECORD_DEF },
+ NO_RECORD_DEF, "" },
{ "information-refresh-time", D6O_INFORMATION_REFRESH_TIME,
- OPT_UINT32_TYPE, false, NO_RECORD_DEF },
+ OPT_UINT32_TYPE, false, NO_RECORD_DEF, "" },
{ "bcmcs-server-dns", D6O_BCMCS_SERVER_D, OPT_FQDN_TYPE, true,
- NO_RECORD_DEF },
+ NO_RECORD_DEF, "" },
{ "bcmcs-server-addr", D6O_BCMCS_SERVER_A, OPT_IPV6_ADDRESS_TYPE, true,
- NO_RECORD_DEF },
+ NO_RECORD_DEF, "" },
{ "geoconf-civic", D6O_GEOCONF_CIVIC, OPT_RECORD_TYPE, false,
- RECORD_DEF(GEOCONF_CIVIC_RECORDS) },
+ RECORD_DEF(GEOCONF_CIVIC_RECORDS), "" },
{ "remote-id", D6O_REMOTE_ID, OPT_RECORD_TYPE, false,
- RECORD_DEF(REMOTE_ID_RECORDS) },
+ RECORD_DEF(REMOTE_ID_RECORDS), "" },
{ "subscriber-id", D6O_SUBSCRIBER_ID, OPT_BINARY_TYPE, false,
- NO_RECORD_DEF },
+ NO_RECORD_DEF, "" },
{ "client-fqdn", D6O_CLIENT_FQDN, OPT_RECORD_TYPE, false,
- RECORD_DEF(CLIENT_FQDN_RECORDS) },
+ RECORD_DEF(CLIENT_FQDN_RECORDS), "" },
{ "pana-agent", D6O_PANA_AGENT, OPT_IPV6_ADDRESS_TYPE, true,
- NO_RECORD_DEF },
+ NO_RECORD_DEF, "" },
{ "new-posix-timezone", D6O_NEW_POSIX_TIMEZONE, OPT_STRING_TYPE, false,
- NO_RECORD_DEF },
+ NO_RECORD_DEF, "" },
{ "new-tzdb-timezone", D6O_NEW_TZDB_TIMEZONE, OPT_STRING_TYPE, false,
- NO_RECORD_DEF },
- { "ero", D6O_ERO, OPT_UINT16_TYPE, true, NO_RECORD_DEF },
+ NO_RECORD_DEF, "" },
+ { "ero", D6O_ERO, OPT_UINT16_TYPE, true, NO_RECORD_DEF, "" },
{ "lq-query", D6O_LQ_QUERY, OPT_RECORD_TYPE, false,
- RECORD_DEF(LQ_QUERY_RECORDS) },
- { "client-data", D6O_CLIENT_DATA, OPT_EMPTY_TYPE, false, NO_RECORD_DEF },
- { "clt-time", D6O_CLT_TIME, OPT_UINT32_TYPE, false, NO_RECORD_DEF },
+ RECORD_DEF(LQ_QUERY_RECORDS), "" },
+ { "client-data", D6O_CLIENT_DATA, OPT_EMPTY_TYPE, false, NO_RECORD_DEF, "" },
+ { "clt-time", D6O_CLT_TIME, OPT_UINT32_TYPE, false, NO_RECORD_DEF, "" },
{ "lq-relay-data", D6O_LQ_RELAY_DATA, OPT_RECORD_TYPE, false,
- RECORD_DEF(LQ_RELAY_DATA_RECORDS) },
+ RECORD_DEF(LQ_RELAY_DATA_RECORDS), "" },
{ "lq-client-link", D6O_LQ_CLIENT_LINK, OPT_IPV6_ADDRESS_TYPE, true,
- NO_RECORD_DEF }
+ NO_RECORD_DEF, "" }
// @todo There is still a bunch of options for which we have to provide
// definitions but we don't do it because they are not really
diff --git a/src/lib/dhcp/tests/Makefile.am b/src/lib/dhcp/tests/Makefile.am
index 4833fb5..c868553 100644
--- a/src/lib/dhcp/tests/Makefile.am
+++ b/src/lib/dhcp/tests/Makefile.am
@@ -40,6 +40,7 @@ libdhcp___unittests_SOURCES += option_data_types_unittest.cc
libdhcp___unittests_SOURCES += option_definition_unittest.cc
libdhcp___unittests_SOURCES += option_custom_unittest.cc
libdhcp___unittests_SOURCES += option_unittest.cc
+libdhcp___unittests_SOURCES += option_space_unittest.cc
libdhcp___unittests_SOURCES += pkt4_unittest.cc
libdhcp___unittests_SOURCES += pkt6_unittest.cc
libdhcp___unittests_SOURCES += duid_unittest.cc
diff --git a/src/lib/dhcp/tests/libdhcp++_unittest.cc b/src/lib/dhcp/tests/libdhcp++_unittest.cc
index a59da12..1a87b13 100644
--- a/src/lib/dhcp/tests/libdhcp++_unittest.cc
+++ b/src/lib/dhcp/tests/libdhcp++_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
@@ -68,12 +68,16 @@ public:
/// used to create option instance.
/// @param expected_type type of the option created by the
/// factory function returned by the option definition.
+ /// @param encapsulates name of the option space being encapsulated
+ /// by the option.
static void testStdOptionDefs4(const uint16_t code,
const OptionBufferConstIter begin,
const OptionBufferConstIter end,
- const std::type_info& expected_type) {
+ const std::type_info& expected_type,
+ const std::string& encapsulates = "") {
// Use V4 universe.
- testStdOptionDefs(Option::V4, code, begin, end, expected_type);
+ testStdOptionDefs(Option::V4, code, begin, end, expected_type,
+ encapsulates);
}
/// @brief Test DHCPv6 option definition.
@@ -88,12 +92,16 @@ public:
/// used to create option instance.
/// @param expected_type type of the option created by the
/// factory function returned by the option definition.
+ /// @param encapsulates name of the option space being encapsulated
+ /// by the option.
static void testStdOptionDefs6(const uint16_t code,
const OptionBufferConstIter begin,
const OptionBufferConstIter end,
- const std::type_info& expected_type) {
+ const std::type_info& expected_type,
+ const std::string& encapsulates = "") {
// Use V6 universe.
- testStdOptionDefs(Option::V6, code, begin, end, expected_type);
+ testStdOptionDefs(Option::V6, code, begin, end, expected_type,
+ encapsulates);
}
private:
@@ -109,11 +117,14 @@ private:
/// used to create option instance.
/// @param expected_type type of the option created by the
/// factory function returned by the option definition.
+ /// @param encapsulates name of the option space being encapsulated
+ /// by the option.
static void testStdOptionDefs(const Option::Universe u,
const uint16_t code,
const OptionBufferConstIter begin,
const OptionBufferConstIter end,
- const std::type_info& expected_type) {
+ const std::type_info& expected_type,
+ const std::string& encapsulates) {
// Get all option definitions, we will use them to extract
// the definition for a particular option code.
// We don't have to initialize option definitions here because they
@@ -141,6 +152,9 @@ private:
ASSERT_NO_THROW(def->validate())
<< "Option definition for the option code " << code
<< " is invalid";
+ // Check that the valid encapsulated option space name
+ // has been specified.
+ EXPECT_EQ(encapsulates, def->getEncapsulatedSpace());
OptionPtr option;
// Create the option.
ASSERT_NO_THROW(option = def->optionFactory(u, code, begin, end))
@@ -259,7 +273,7 @@ TEST_F(LibDhcpTest, packOptions6) {
OutputBuffer assembled(512);
- EXPECT_NO_THROW(LibDHCP::packOptions6(assembled, opts));
+ EXPECT_NO_THROW(LibDHCP::packOptions(assembled, opts));
EXPECT_EQ(sizeof(v6packed), assembled.getLength());
EXPECT_EQ(0, memcmp(assembled.getData(), v6packed, sizeof(v6packed)));
}
@@ -648,7 +662,8 @@ TEST_F(LibDhcpTest, stdOptionDefs4) {
typeid(Option4AddrLst));
LibDhcpTest::testStdOptionDefs4(DHO_VENDOR_ENCAPSULATED_OPTIONS, begin, end,
- typeid(Option));
+ typeid(Option),
+ "vendor-encapsulated-options-space");
LibDhcpTest::testStdOptionDefs4(DHO_NETBIOS_NAME_SERVERS, begin, end,
typeid(Option4AddrLst));
@@ -717,7 +732,7 @@ TEST_F(LibDhcpTest, stdOptionDefs4) {
typeid(OptionCustom));
LibDhcpTest::testStdOptionDefs4(DHO_DHCP_AGENT_OPTIONS, begin, end,
- typeid(Option));
+ typeid(Option), "dhcp-agent-options-space");
LibDhcpTest::testStdOptionDefs4(DHO_AUTHENTICATE, begin, end,
typeid(Option));
@@ -816,7 +831,8 @@ TEST_F(LibDhcpTest, stdOptionDefs6) {
typeid(OptionCustom));
LibDhcpTest::testStdOptionDefs6(D6O_VENDOR_OPTS, begin, end,
- typeid(OptionCustom));
+ typeid(OptionInt<uint32_t>),
+ "vendor-opts-space");
LibDhcpTest::testStdOptionDefs6(D6O_INTERFACE_ID, begin, end,
typeid(Option));
diff --git a/src/lib/dhcp/tests/option4_addrlst_unittest.cc b/src/lib/dhcp/tests/option4_addrlst_unittest.cc
index a8e60f6..0c1d9e6 100644
--- a/src/lib/dhcp/tests/option4_addrlst_unittest.cc
+++ b/src/lib/dhcp/tests/option4_addrlst_unittest.cc
@@ -155,7 +155,7 @@ TEST_F(Option4AddrLstTest, assembly1) {
OutputBuffer buf(100);
EXPECT_NO_THROW(
- opt->pack4(buf);
+ opt->pack(buf);
);
ASSERT_EQ(6, opt->len());
@@ -198,7 +198,7 @@ TEST_F(Option4AddrLstTest, assembly4) {
OutputBuffer buf(100);
EXPECT_NO_THROW(
- opt->pack4(buf);
+ opt->pack(buf);
);
ASSERT_EQ(18, opt->len()); // 2(header) + 4xsizeof(IPv4addr)
diff --git a/src/lib/dhcp/tests/option_definition_unittest.cc b/src/lib/dhcp/tests/option_definition_unittest.cc
index 310c7bf..174bafb 100644
--- a/src/lib/dhcp/tests/option_definition_unittest.cc
+++ b/src/lib/dhcp/tests/option_definition_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
@@ -53,38 +53,62 @@ public:
TEST_F(OptionDefinitionTest, constructor) {
// Specify the option data type as string. This should get converted
// to enum value returned by getType().
- OptionDefinition opt_def1("OPTION_CLIENTID", 1, "string");
+ OptionDefinition opt_def1("OPTION_CLIENTID", D6O_CLIENTID, "string");
EXPECT_EQ("OPTION_CLIENTID", opt_def1.getName());
EXPECT_EQ(1, opt_def1.getCode());
EXPECT_EQ(OPT_STRING_TYPE, opt_def1.getType());
EXPECT_FALSE(opt_def1.getArrayType());
+ EXPECT_TRUE(opt_def1.getEncapsulatedSpace().empty());
EXPECT_NO_THROW(opt_def1.validate());
// Specify the option data type as an enum value.
- OptionDefinition opt_def2("OPTION_RAPID_COMMIT", 14,
+ OptionDefinition opt_def2("OPTION_RAPID_COMMIT", D6O_RAPID_COMMIT,
OPT_EMPTY_TYPE);
EXPECT_EQ("OPTION_RAPID_COMMIT", opt_def2.getName());
EXPECT_EQ(14, opt_def2.getCode());
EXPECT_EQ(OPT_EMPTY_TYPE, opt_def2.getType());
EXPECT_FALSE(opt_def2.getArrayType());
- EXPECT_NO_THROW(opt_def1.validate());
+ EXPECT_TRUE(opt_def2.getEncapsulatedSpace().empty());
+ EXPECT_NO_THROW(opt_def2.validate());
+
+ // Specify encapsulated option space name and option data type
+ // as enum value.
+ OptionDefinition opt_def3("OPTION_VENDOR_OPTS", D6O_VENDOR_OPTS,
+ OPT_UINT32_TYPE, "isc");
+ EXPECT_EQ("OPTION_VENDOR_OPTS", opt_def3.getName());
+ EXPECT_EQ(D6O_VENDOR_OPTS, opt_def3.getCode());
+ EXPECT_EQ(OPT_UINT32_TYPE, opt_def3.getType());
+ EXPECT_FALSE(opt_def3.getArrayType());
+ EXPECT_EQ("isc", opt_def3.getEncapsulatedSpace());
+ EXPECT_NO_THROW(opt_def3.validate());
+
+ // Specify encapsulated option space name and option data type
+ // as string value.
+ OptionDefinition opt_def4("OPTION_VENDOR_OPTS", D6O_VENDOR_OPTS,
+ "uint32", "isc");
+ EXPECT_EQ("OPTION_VENDOR_OPTS", opt_def4.getName());
+ EXPECT_EQ(D6O_VENDOR_OPTS, opt_def4.getCode());
+ EXPECT_EQ(OPT_UINT32_TYPE, opt_def4.getType());
+ EXPECT_FALSE(opt_def4.getArrayType());
+ EXPECT_EQ("isc", opt_def4.getEncapsulatedSpace());
+ EXPECT_NO_THROW(opt_def4.validate());
// Check if it is possible to set that option is an array.
- OptionDefinition opt_def3("OPTION_NIS_SERVERS", 27,
+ OptionDefinition opt_def5("OPTION_NIS_SERVERS", 27,
OPT_IPV6_ADDRESS_TYPE,
true);
- EXPECT_EQ("OPTION_NIS_SERVERS", opt_def3.getName());
- EXPECT_EQ(27, opt_def3.getCode());
- EXPECT_EQ(OPT_IPV6_ADDRESS_TYPE, opt_def3.getType());
- EXPECT_TRUE(opt_def3.getArrayType());
- EXPECT_NO_THROW(opt_def3.validate());
+ EXPECT_EQ("OPTION_NIS_SERVERS", opt_def5.getName());
+ EXPECT_EQ(27, opt_def5.getCode());
+ EXPECT_EQ(OPT_IPV6_ADDRESS_TYPE, opt_def5.getType());
+ EXPECT_TRUE(opt_def5.getArrayType());
+ EXPECT_NO_THROW(opt_def5.validate());
// The created object is invalid if invalid data type is specified but
// constructor shouldn't throw exception. The object is validated after
// it has been created.
EXPECT_NO_THROW(
- OptionDefinition opt_def4("OPTION_SERVERID",
+ OptionDefinition opt_def6("OPTION_SERVERID",
OPT_UNKNOWN_TYPE + 10,
OPT_STRING_TYPE);
);
@@ -213,6 +237,11 @@ TEST_F(OptionDefinitionTest, validate) {
"record");
opt_def16.addRecordField("uint8");
opt_def16.addRecordField("string");
+
+ // Check invalid encapsulated option space name.
+ OptionDefinition opt_def17("OPTION_VENDOR_OPTS", D6O_VENDOR_OPTS,
+ "uint32", "invalid%space%name");
+ EXPECT_THROW(opt_def17.validate(), MalformedOptionDefinition);
}
diff --git a/src/lib/dhcp/tests/option_space_unittest.cc b/src/lib/dhcp/tests/option_space_unittest.cc
new file mode 100644
index 0000000..d3a6f53
--- /dev/null
+++ b/src/lib/dhcp/tests/option_space_unittest.cc
@@ -0,0 +1,150 @@
+// 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 <dhcp/option_space.h>
+
+#include <gtest/gtest.h>
+
+using namespace isc::dhcp;
+using namespace isc;
+
+namespace {
+
+// The purpose of this test is to verify that the constructor
+// creates an object with members initialized to correct values.
+TEST(OptionSpaceTest, constructor) {
+ // Create some option space.
+ OptionSpace space("isc", true);
+ EXPECT_EQ("isc", space.getName());
+ EXPECT_TRUE(space.isVendorSpace());
+
+ // Create another object with different values
+ // to check that the values will change.
+ OptionSpace space2("abc", false);
+ EXPECT_EQ("abc", space2.getName());
+ EXPECT_FALSE(space2.isVendorSpace());
+
+ // Verify that constructor throws exception if invalid
+ // option space name is provided.
+ EXPECT_THROW(OptionSpace("invalid%space.name"), InvalidOptionSpace);
+}
+
+// The purpose of this test is to verify that the vendor-space flag
+// can be overriden.
+TEST(OptionSpaceTest, setVendorSpace) {
+ OptionSpace space("isc", true);
+ EXPECT_EQ("isc", space.getName());
+ EXPECT_TRUE(space.isVendorSpace());
+
+ // Override the vendor space flag.
+ space.clearVendorSpace();
+ EXPECT_FALSE(space.isVendorSpace());
+}
+
+// The purpose of this test is to verify that the static function
+// to validate the option space name works correctly.
+TEST(OptionSpaceTest, validateName) {
+ // Positive test scenarios: letters, digits, dashes, underscores
+ // lower/upper case allowed.
+ EXPECT_TRUE(OptionSpace::validateName("abc"));
+ EXPECT_TRUE(OptionSpace::validateName("dash-allowed"));
+ EXPECT_TRUE(OptionSpace::validateName("two-dashes-allowed"));
+ EXPECT_TRUE(OptionSpace::validateName("underscore_allowed"));
+ EXPECT_TRUE(OptionSpace::validateName("underscore_three_times_allowed"));
+ EXPECT_TRUE(OptionSpace::validateName("digits0912"));
+ EXPECT_TRUE(OptionSpace::validateName("1234"));
+ EXPECT_TRUE(OptionSpace::validateName("UPPER_CASE_allowed"));
+
+ // Negative test scenarions: empty strings, dots, spaces are not
+ // allowed
+ EXPECT_FALSE(OptionSpace::validateName(""));
+ EXPECT_FALSE(OptionSpace::validateName(" "));
+ EXPECT_FALSE(OptionSpace::validateName(" isc "));
+ EXPECT_FALSE(OptionSpace::validateName("isc "));
+ EXPECT_FALSE(OptionSpace::validateName(" isc"));
+ EXPECT_FALSE(OptionSpace::validateName("isc with-space"));
+
+ // Hyphens and underscores are not allowed at the beginning
+ // and at the end of the option space name.
+ EXPECT_FALSE(OptionSpace::validateName("-isc"));
+ EXPECT_FALSE(OptionSpace::validateName("isc-"));
+ EXPECT_FALSE(OptionSpace::validateName("_isc"));
+ EXPECT_FALSE(OptionSpace::validateName("isc_"));
+
+ // Test other special characters
+ const char specials[] = { '!', '@', '#', '$', '%', '^', '&', '*', '(', ')',
+ '+', '=', '[', ']', '{', '}', ';', ':', '"', '\'',
+ '\\', '|', '<','>', ',', '.', '?', '~', '`' };
+ for (int i = 0; i < sizeof(specials); ++i) {
+ std::ostringstream stream;
+ // Concatenate valid option space name: "abc" with an invalid character.
+ // That way we get option space names like: "abc!", "abc$" etc. It is
+ // expected that the validating function fails form them.
+ stream << "abc" << specials[i];
+ EXPECT_FALSE(OptionSpace::validateName(stream.str()))
+ << "Test failed for special character '" << specials[i] << "'.";
+ }
+}
+
+// The purpose of this test is to verify that the constructors of the
+// OptionSpace6 class set the class members to correct values.
+TEST(OptionSpace6Test, constructor) {
+ // Create some option space and do not specify enterprise number.
+ // In such case the vendor space flag is expected to be
+ // set to false.
+ OptionSpace6 space1("abcd");
+ EXPECT_EQ("abcd", space1.getName());
+ EXPECT_FALSE(space1.isVendorSpace());
+
+ // Create an option space and specify an enterprise number. In this
+ // case the vendor space flag is expected to be set to true and the
+ // enterprise number should be set to a desired value.
+ OptionSpace6 space2("abcd", 2145);
+ EXPECT_EQ("abcd", space2.getName());
+ EXPECT_TRUE(space2.isVendorSpace());
+ EXPECT_EQ(2145, space2.getEnterpriseNumber());
+
+ // Verify that constructors throw an exception when invalid option
+ // space name has been specified.
+ EXPECT_THROW(OptionSpace6("isc dhcp"), InvalidOptionSpace);
+ EXPECT_THROW(OptionSpace6("isc%dhcp", 2145), InvalidOptionSpace);
+}
+
+// The purpose of this test is to verify an option space can be marked
+// vendor option space and enterprise number can be set.
+TEST(OptionSpace6Test, setVendorSpace) {
+ OptionSpace6 space("isc");
+ EXPECT_EQ("isc", space.getName());
+ EXPECT_FALSE(space.isVendorSpace());
+
+ // Mark it vendor option space and set enterprise id.
+ space.setVendorSpace(1234);
+ EXPECT_TRUE(space.isVendorSpace());
+ EXPECT_EQ(1234, space.getEnterpriseNumber());
+
+ // Override the enterprise number to make sure and make sure that
+ // the new number is returned by the object.
+ space.setVendorSpace(2345);
+ EXPECT_TRUE(space.isVendorSpace());
+ EXPECT_EQ(2345, space.getEnterpriseNumber());
+
+ // Clear the vendor option space flag.
+ space.clearVendorSpace();
+ EXPECT_FALSE(space.isVendorSpace());
+}
+
+
+}; // end of anonymous namespace
diff --git a/src/lib/dhcp/tests/option_unittest.cc b/src/lib/dhcp/tests/option_unittest.cc
index afa64d5..1fc49f1 100644
--- a/src/lib/dhcp/tests/option_unittest.cc
+++ b/src/lib/dhcp/tests/option_unittest.cc
@@ -116,7 +116,7 @@ TEST_F(OptionTest, v4_data1) {
// now store that option into a buffer
OutputBuffer buf(100);
EXPECT_NO_THROW(
- opt->pack4(buf);
+ opt->pack(buf);
);
// check content of that buffer
@@ -173,7 +173,7 @@ TEST_F(OptionTest, v4_data2) {
// now store that option into a buffer
OutputBuffer buf(100);
EXPECT_NO_THROW(
- opt->pack4(buf);
+ opt->pack(buf);
);
// check content of that buffer
@@ -471,7 +471,7 @@ TEST_F(OptionTest, setUintX) {
// verify setUint8
opt1->setUint8(255);
EXPECT_EQ(255, opt1->getUint8());
- opt1->pack4(outBuf_);
+ opt1->pack(outBuf_);
EXPECT_EQ(3, opt1->len());
EXPECT_EQ(3, outBuf_.getLength());
uint8_t exp1[] = {125, 1, 255};
@@ -480,7 +480,7 @@ TEST_F(OptionTest, setUintX) {
// verify getUint16
outBuf_.clear();
opt2->setUint16(12345);
- opt2->pack4(outBuf_);
+ opt2->pack(outBuf_);
EXPECT_EQ(12345, opt2->getUint16());
EXPECT_EQ(4, opt2->len());
EXPECT_EQ(4, outBuf_.getLength());
@@ -490,7 +490,7 @@ TEST_F(OptionTest, setUintX) {
// verify getUint32
outBuf_.clear();
opt4->setUint32(0x12345678);
- opt4->pack4(outBuf_);
+ opt4->pack(outBuf_);
EXPECT_EQ(0x12345678, opt4->getUint32());
EXPECT_EQ(6, opt4->len());
EXPECT_EQ(6, outBuf_.getLength());
@@ -505,7 +505,7 @@ TEST_F(OptionTest, setData) {
buf_.begin(), buf_.begin() + 10));
buf_.resize(20, 1);
opt1->setData(buf_.begin(), buf_.end());
- opt1->pack4(outBuf_);
+ opt1->pack(outBuf_);
ASSERT_EQ(outBuf_.getLength() - opt1->getHeaderLen(), buf_.size());
const uint8_t* test_data = static_cast<const uint8_t*>(outBuf_.getData());
EXPECT_TRUE(0 == memcmp(&buf_[0], test_data + opt1->getHeaderLen(),
@@ -518,7 +518,7 @@ TEST_F(OptionTest, setData) {
outBuf_.clear();
buf_.resize(5, 1);
opt2->setData(buf_.begin(), buf_.end());
- opt2->pack4(outBuf_);
+ opt2->pack(outBuf_);
ASSERT_EQ(outBuf_.getLength() - opt1->getHeaderLen(), buf_.size());
test_data = static_cast<const uint8_t*>(outBuf_.getData());
EXPECT_TRUE(0 == memcmp(&buf_[0], test_data + opt1->getHeaderLen(),
diff --git a/src/lib/dhcpsrv/.gitignore b/src/lib/dhcpsrv/.gitignore
new file mode 100644
index 0000000..0b02c01
--- /dev/null
+++ b/src/lib/dhcpsrv/.gitignore
@@ -0,0 +1,2 @@
+/dhcpsrv_messages.cc
+/dhcpsrv_messages.h
diff --git a/src/lib/dhcpsrv/Makefile.am b/src/lib/dhcpsrv/Makefile.am
index cc5daec..e721267 100644
--- a/src/lib/dhcpsrv/Makefile.am
+++ b/src/lib/dhcpsrv/Makefile.am
@@ -1,6 +1,6 @@
SUBDIRS = . tests
-AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib -DDHCP_DATA_DIR="\"$(localstatedir)\""
AM_CPPFLAGS += $(BOOST_INCLUDES)
if HAVE_MYSQL
AM_CPPFLAGS += $(MYSQL_CPPFLAGS)
@@ -33,6 +33,7 @@ lib_LTLIBRARIES = libb10-dhcpsrv.la
libb10_dhcpsrv_la_SOURCES =
libb10_dhcpsrv_la_SOURCES += addr_utilities.cc addr_utilities.h
libb10_dhcpsrv_la_SOURCES += alloc_engine.cc alloc_engine.h
+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
@@ -42,7 +43,6 @@ libb10_dhcpsrv_la_SOURCES += memfile_lease_mgr.cc memfile_lease_mgr.h
if HAVE_MYSQL
libb10_dhcpsrv_la_SOURCES += mysql_lease_mgr.cc mysql_lease_mgr.h
endif
-libb10_dhcpsrv_la_SOURCES += option_space.cc option_space.h
libb10_dhcpsrv_la_SOURCES += option_space_container.h
libb10_dhcpsrv_la_SOURCES += pool.cc pool.h
libb10_dhcpsrv_la_SOURCES += subnet.cc subnet.h
diff --git a/src/lib/dhcpsrv/alloc_engine.h b/src/lib/dhcpsrv/alloc_engine.h
index c6cbc35..7e3d136 100644
--- a/src/lib/dhcpsrv/alloc_engine.h
+++ b/src/lib/dhcpsrv/alloc_engine.h
@@ -214,8 +214,8 @@ protected:
/// @param clientid client identifier
/// @param hwaddr client's hardware address
/// @param lease lease to be renewed
- /// @param renewed lease (typically the same passed as lease parameter)
- /// or NULL if the lease cannot be renewed
+ /// @param fake_allocation is this real i.e. REQUEST (false) or just picking
+ /// an address for DISCOVER that is not really allocated (true)
Lease4Ptr
renewLease4(const SubnetPtr& subnet,
const ClientIdPtr& clientid,
diff --git a/src/lib/dhcpsrv/cfgmgr.cc b/src/lib/dhcpsrv/cfgmgr.cc
index ee40130..b5e83e3 100644
--- a/src/lib/dhcpsrv/cfgmgr.cc
+++ b/src/lib/dhcpsrv/cfgmgr.cc
@@ -123,6 +123,26 @@ CfgMgr::getOptionDef(const std::string& option_space,
}
Subnet6Ptr
+CfgMgr::getSubnet6(const std::string& iface) {
+
+ if (!iface.length()) {
+ return (Subnet6Ptr());
+ }
+
+ // If there is more than one, we need to choose the proper one
+ for (Subnet6Collection::iterator subnet = subnets6_.begin();
+ subnet != subnets6_.end(); ++subnet) {
+ if (iface == (*subnet)->getIface()) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+ DHCPSRV_CFGMGR_SUBNET6_IFACE)
+ .arg((*subnet)->toText()).arg(iface);
+ return (*subnet);
+ }
+ }
+ return (Subnet6Ptr());
+}
+
+Subnet6Ptr
CfgMgr::getSubnet6(const isc::asiolink::IOAddress& hint) {
// If there's only one subnet configured, let's just use it
@@ -143,6 +163,7 @@ CfgMgr::getSubnet6(const isc::asiolink::IOAddress& hint) {
// If there is more than one, we need to choose the proper one
for (Subnet6Collection::iterator subnet = subnets6_.begin();
subnet != subnets6_.end(); ++subnet) {
+
if ((*subnet)->inRange(hint)) {
LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
DHCPSRV_CFGMGR_SUBNET6)
@@ -227,7 +248,16 @@ void CfgMgr::deleteSubnets6() {
subnets6_.clear();
}
-CfgMgr::CfgMgr() {
+std::string CfgMgr::getDataDir() {
+ return (datadir_);
+}
+
+
+CfgMgr::CfgMgr()
+ :datadir_(DHCP_DATA_DIR) {
+ // DHCP_DATA_DIR must be set set with -DDHCP_DATA_DIR="..." in Makefile.am
+ // Note: the definition of DHCP_DATA_DIR needs to include quotation marks
+ // See AM_CPPFLAGS definition in Makefile.am
}
CfgMgr::~CfgMgr() {
diff --git a/src/lib/dhcpsrv/cfgmgr.h b/src/lib/dhcpsrv/cfgmgr.h
index 9cfde9d..f4eecb5 100644
--- a/src/lib/dhcpsrv/cfgmgr.h
+++ b/src/lib/dhcpsrv/cfgmgr.h
@@ -18,7 +18,7 @@
#include <asiolink/io_address.h>
#include <dhcp/option.h>
#include <dhcp/option_definition.h>
-#include <dhcpsrv/option_space.h>
+#include <dhcp/option_space.h>
#include <dhcpsrv/option_space_container.h>
#include <dhcpsrv/pool.h>
#include <dhcpsrv/subnet.h>
@@ -155,9 +155,18 @@ public:
///
/// @param hint an address that belongs to a searched subnet
///
- /// @return a subnet object
+ /// @return a subnet object (or NULL if no suitable match was fount)
Subnet6Ptr getSubnet6(const isc::asiolink::IOAddress& hint);
+ /// @brief get IPv6 subnet by interface name
+ ///
+ /// Finds a matching local subnet, based on interface name. This
+ /// is used for selecting subnets that were explicitly marked by the
+ /// user as reachable over specified network interface.
+ /// @param iface_name interface name
+ /// @return a subnet object (or NULL if no suitable match was fount)
+ Subnet6Ptr getSubnet6(const std::string& iface_name);
+
/// @brief get IPv6 subnet by interface-id
///
/// Another possibility to find a subnet is based on interface-id.
@@ -221,6 +230,14 @@ public:
/// completely new?
void deleteSubnets4();
+
+ /// @brief returns path do the data directory
+ ///
+ /// This method returns a path to writeable directory that DHCP servers
+ /// can store data in.
+ /// @return data directory
+ std::string getDataDir();
+
protected:
/// @brief Protected constructor.
@@ -265,6 +282,8 @@ private:
/// @brief Container for defined DHCPv4 option spaces.
OptionSpaceCollection spaces4_;
+ /// @brief directory where data files (e.g. server-id) are stored
+ std::string datadir_;
};
} // namespace isc::dhcp
diff --git a/src/lib/dhcpsrv/dbaccess_parser.cc b/src/lib/dhcpsrv/dbaccess_parser.cc
new file mode 100644
index 0000000..d29cf54
--- /dev/null
+++ b/src/lib/dhcpsrv/dbaccess_parser.cc
@@ -0,0 +1,116 @@
+// 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 <dhcpsrv/dbaccess_parser.h>
+#include <dhcpsrv/dhcpsrv_log.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+
+#include <boost/foreach.hpp>
+
+#include <map>
+#include <string>
+#include <utility>
+
+using namespace std;
+using namespace isc::data;
+
+namespace isc {
+namespace dhcp {
+
+
+// Factory function to build the parser
+DbAccessParser::DbAccessParser(const std::string& param_name) : values_()
+{
+ if (param_name != "lease-database") {
+ LOG_WARN(dhcpsrv_logger, DHCPSRV_UNEXPECTED_NAME).arg(param_name);
+ }
+}
+
+// Parse the configuration and check that the various keywords are consistent.
+void
+DbAccessParser::build(isc::data::ConstElementPtr config_value) {
+
+ // To cope with incremental updates, the strategy is:
+ // 1. Take a copy of the stored keyword/value pairs.
+ // 2. Update the copy with the passed keywords.
+ // 3. Perform validation checks on the updated keyword/value pairs.
+ // 4. If all is OK, update the stored keyword/value pairs.
+
+ // 1. Take a copy of the stored keyword/value pairs.
+ std::map<string, string> values_copy = values_;
+
+ // 2. Update the copy with the passed keywords.
+ BOOST_FOREACH(ConfigPair param, config_value->mapValue()) {
+ values_copy[param.first] = param.second->stringValue();
+ }
+
+ // 3. Perform validation checks on the updated set of keyword/values.
+ //
+ // a. Check if the "type" keyword exists and thrown an exception if not.
+ StringPairMap::const_iterator type_ptr = values_copy.find("type");
+ if (type_ptr == values_copy.end()) {
+ isc_throw(TypeKeywordMissing, "lease database access parameters must "
+ "include the keyword 'type' to determine type of database "
+ "to be accessed");
+ }
+
+ // b. Check if the 'type; keyword known and throw an exception if not.
+ string dbtype = type_ptr->second;
+ if ((dbtype != "memfile") && (dbtype != "mysql")) {
+ isc_throw(BadValue, "unknown backend database type: " << dbtype);
+ }
+
+ // 4. If all is OK, update the stored keyword/value pairs. We do this by
+ // swapping contents - values_copy is destroyed immediately after the
+ // operation (when the method exits), so we are not interested in its new
+ // value.
+ values_.swap(values_copy);
+}
+
+// Create the database access string
+std::string
+DbAccessParser::getDbAccessString() const {
+
+ // Construct the database access string from all keywords and values in the
+ // parameter map where the value is not null.
+ string dbaccess;
+ BOOST_FOREACH(StringPair keyval, values_) {
+ if (!keyval.second.empty()) {
+
+ // Separate keyword/value pair from predecessor (if there is one).
+ if (!dbaccess.empty()) {
+ dbaccess += std::string(" ");
+ }
+
+ // Add the keyword/value pair to the access string.
+ dbaccess += (keyval.first + std::string("=") + keyval.second);
+ }
+ }
+
+ return (dbaccess);
+}
+
+// Commit the changes - reopen the database with the new parameters
+void
+DbAccessParser::commit() {
+ // Close current lease manager database.
+ LeaseMgrFactory::destroy();
+
+ // ... and open the new database using the access string.
+ LeaseMgrFactory::create(getDbAccessString());
+}
+
+}; // namespace dhcp
+}; // namespace isc
+
diff --git a/src/lib/dhcpsrv/dbaccess_parser.h b/src/lib/dhcpsrv/dbaccess_parser.h
new file mode 100644
index 0000000..140f11d
--- /dev/null
+++ b/src/lib/dhcpsrv/dbaccess_parser.h
@@ -0,0 +1,133 @@
+// 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.
+
+#ifndef DBACCESS_PARSER_H
+#define DBACCESS_PARSER_H
+
+#include <cc/data.h>
+#include <dhcpsrv/dhcp_config_parser.h>
+#include <exceptions/exceptions.h>
+
+#include <string>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Exception thrown when 'type' keyword is missing from string
+///
+/// This condition is checked, but should never occur because 'type' is marked
+/// as mandatory in the .spec file for the server.
+class TypeKeywordMissing : public isc::Exception {
+public:
+ TypeKeywordMissing(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// @brief Parse Lease Database Parameters
+///
+/// This class is the parser for the lease database configuration. This is a
+/// map under the top-level "lease-database" element, and comprises a map of
+/// strings.
+///
+/// Only the "type" sub-element is mandatory: the remaining sub-elements
+/// depend on the datbase chosen.
+class DbAccessParser: public DhcpConfigParser {
+public:
+ /// @brief Keyword and associated value
+ typedef std::pair<std::string, std::string> StringPair;
+
+ /// @brief Keyword/value collection of database access parameters
+ typedef std::map<std::string, std::string> StringPairMap;
+
+ /// @brief Constructor
+ ///
+ /// @param param_name Name of the parameter under which the database
+ /// access details are held.
+ DbAccessParser(const std::string& param_name);
+
+ /// The destructor.
+ virtual ~DbAccessParser()
+ {}
+
+ /// @brief Prepare configuration value.
+ ///
+ /// Parses the set of strings forming the database access specification and
+ /// checks that all are OK. In particular it checks:
+ ///
+ /// - "type" is "memfile" or "mysql"
+ /// - If "type" is "memfile", checks that no other values are present: if
+ /// they are, logs a warning that they will be ignored.
+ ///
+ /// Once all has been validated, constructs the database access string
+ /// expected by the lease manager.
+ ///
+ /// @param config_value The configuration value for the "lease-database"
+ /// identifier.
+ ///
+ /// @throw isc::BadValue The 'type' keyword contains an unknown database
+ /// type.
+ /// @throw isc::dhcp::MissingTypeKeyword The 'type' keyword is missing from
+ /// the list of database access keywords.
+ virtual void build(isc::data::ConstElementPtr config_value);
+
+ /// @brief Apply the prepared configuration value to the server.
+ ///
+ /// With the string validated, this closes the currently open database (if
+ /// any), then opens a database corresponding to the stored string.
+ ///
+ /// This method is expected to be called after \c build(), and only once.
+ /// The result is undefined otherwise.
+ virtual void commit();
+
+ /// @brief Factory method to create parser
+ ///
+ /// Creates an instance of this parser.
+ ///
+ /// @param name Name of the parameter used to access the configuration.
+ ///
+ /// @return Pointer to a DbAccessParser. The caller is responsible for
+ /// destroying the parser after use.
+ static DhcpConfigParser* factory(const std::string& param_name) {
+ return (new DbAccessParser(param_name));
+ }
+
+protected:
+ /// @brief Get database access parameters
+ ///
+ /// Used in testing to check that the configuration information has been
+ /// parsed correctly.
+ ///
+ /// @return Reference to the internal map of keyword/value pairs
+ /// representing database access information. This is valid only
+ /// for so long as the the parser remains in existence.
+ const StringPairMap& getDbAccessParameters() const {
+ return (values_);
+ }
+
+ /// @brief Construct dbtabase access string
+ ///
+ /// Constructs the database access string from the stored parameters.
+ ///
+ /// @return Database access string
+ std::string getDbAccessString() const;
+
+private:
+ std::map<std::string, std::string> values_; ///< Stored parameter values
+};
+
+}; // namespace dhcp
+}; // namespace isc
+
+
+#endif // DBACCESS_PARSER_H
diff --git a/src/lib/dhcpsrv/dhcp_config_parser.h b/src/lib/dhcpsrv/dhcp_config_parser.h
index d3d05f6..44cf519 100644
--- a/src/lib/dhcpsrv/dhcp_config_parser.h
+++ b/src/lib/dhcpsrv/dhcp_config_parser.h
@@ -46,6 +46,9 @@ typedef boost::shared_ptr<DhcpConfigParser> ParserPtr;
/// This container is used to store pointer to parsers for a given scope.
typedef std::vector<ParserPtr> ParserCollection;
+/// @brief Combination of parameter name and configuration contents
+typedef std::pair<std::string, isc::data::ConstElementPtr> ConfigPair;
+
/// @brief Base abstract class for all DHCP parsers
///
/// Each instance of a class derived from this class parses one specific config
diff --git a/src/lib/dhcpsrv/dhcpsrv_messages.mes b/src/lib/dhcpsrv/dhcpsrv_messages.mes
index 27f12fc..bf4e1e1 100644
--- a/src/lib/dhcpsrv/dhcpsrv_messages.mes
+++ b/src/lib/dhcpsrv/dhcpsrv_messages.mes
@@ -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
@@ -60,6 +60,20 @@ This is a debug message reporting that the DHCP configuration manager has
returned the specified IPv6 subnet when given the address hint specified
as the address is within the subnet.
+% DHCPSRV_CFGMGR_SUBNET6_IFACE selected subnet %1 for packet received over interface %2
+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).
+
+% DHCPSRV_CLOSE_DB closing currently open %1 database
+This is a debug message, issued when the DHCP server closes the currently
+open lease database. It is issued at program shutdown and whenever
+the database access parameters are changed: in the latter case, the
+server closes the currently open database, and opens a database using
+the new parameters.
+
% DHCPSRV_INVALID_ACCESS invalid database access string: %1
This is logged when an attempt has been made to parse a database access string
and the attempt ended in error. The access string in question - which
@@ -143,6 +157,13 @@ lease from the memory file database for the specified address.
A debug message issued when the server is attempting to update IPv6
lease from the memory file database for the specified address.
+% DHCPSRV_MEMFILE_WARNING using early version of memfile lease database - leases will be lost after a restart
+This warning message is issued when the 'memfile' lease database is
+opened. The current version of memfile does not store anything
+to disk, so lease information will be lost in the event of a restart.
+Using this version of memfile in a production environment is NOT
+recommended.
+
% DHCPSRV_MYSQL_ADD_ADDR4 adding IPv4 lease with address %1
A debug message issued when the server is about to add an IPv4 lease
with the specified address to the MySQL backend database.
@@ -226,6 +247,13 @@ a database backend, but where no 'type' keyword has been included in
the access string. The access string (less any passwords) is included
in the message.
+% DHCPSRV_UNEXPECTED_NAME database access parameters passed through '%1', expected 'lease-database'
+The parameters for access the lease database were passed to the server through
+the named configuration parameter, but the code was expecting them to be
+passed via the parameter named "lease-database". If the database opens
+successfully, there is no impact on server operation. However, as this does
+indicate an error in the source code, please submit a bug report.
+
% DHCPSRV_UNKNOWN_DB unknown database type: %1
The database access string specified a database type (given in the
message) that is unknown to the software. This is a configuration error.
diff --git a/src/lib/dhcpsrv/lease_mgr.h b/src/lib/dhcpsrv/lease_mgr.h
index f781576..e6aaa51 100644
--- a/src/lib/dhcpsrv/lease_mgr.h
+++ b/src/lib/dhcpsrv/lease_mgr.h
@@ -114,9 +114,18 @@ public:
/// leases.
struct Lease {
+ /// @brief Constructor
+ ///
+ /// @param addr IP address
+ /// @param t1 renewal time
+ /// @param t2 rebinding time
+ /// @param valid_lft Lifetime of the lease
+ /// @param subnet_id Subnet identification
+ /// @param cltt Client last transmission time
Lease(const isc::asiolink::IOAddress& addr, uint32_t t1, uint32_t t2,
uint32_t valid_lft, SubnetID subnet_id, time_t cltt);
+ /// @brief Destructor
virtual ~Lease() {}
/// @brief IPv4 ot IPv6 address
@@ -226,13 +235,14 @@ struct Lease4 : public Lease {
/// @brief Constructor
///
- /// @param addr IPv4 address as unsigned 32-bit integer in network byte
- /// order.
+ /// @param addr IPv4 address.
/// @param hwaddr Hardware address buffer
/// @param hwaddr_len Length of hardware address buffer
/// @param clientid Client identification buffer
/// @param clientid_len Length of client identification buffer
/// @param valid_lft Lifetime of the lease
+ /// @param t1 renewal time
+ /// @param t2 rebinding time
/// @param cltt Client last transmission time
/// @param subnet_id Subnet identification
Lease4(const isc::asiolink::IOAddress& addr, const uint8_t* hwaddr, size_t hwaddr_len,
diff --git a/src/lib/dhcpsrv/lease_mgr_factory.cc b/src/lib/dhcpsrv/lease_mgr_factory.cc
index 9fd276d..ede0dba 100644
--- a/src/lib/dhcpsrv/lease_mgr_factory.cc
+++ b/src/lib/dhcpsrv/lease_mgr_factory.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
@@ -120,15 +120,13 @@ LeaseMgrFactory::create(const std::string& dbaccess) {
// Yes, check what it is.
#ifdef HAVE_MYSQL
if (parameters[type] == string("mysql")) {
- LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_MYSQL_DB)
- .arg(redacted);
+ LOG_INFO(dhcpsrv_logger, DHCPSRV_MYSQL_DB).arg(redacted);
getLeaseMgrPtr().reset(new MySqlLeaseMgr(parameters));
return;
}
#endif
if (parameters[type] == string("memfile")) {
- LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_MEMFILE_DB)
- .arg(redacted);
+ LOG_INFO(dhcpsrv_logger, DHCPSRV_MEMFILE_DB).arg(redacted);
getLeaseMgrPtr().reset(new Memfile_LeaseMgr(parameters));
return;
}
@@ -141,6 +139,12 @@ LeaseMgrFactory::create(const std::string& dbaccess) {
void
LeaseMgrFactory::destroy() {
+ // Destroy current lease manager. This is a no-op if no lease manager
+ // is available.
+ if (getLeaseMgrPtr()) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CLOSE_DB)
+ .arg(getLeaseMgrPtr()->getType());
+ }
getLeaseMgrPtr().reset();
}
diff --git a/src/lib/dhcpsrv/memfile_lease_mgr.cc b/src/lib/dhcpsrv/memfile_lease_mgr.cc
index 34f21e9..e5846eb 100644
--- a/src/lib/dhcpsrv/memfile_lease_mgr.cc
+++ b/src/lib/dhcpsrv/memfile_lease_mgr.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
@@ -22,8 +22,7 @@ using namespace isc::dhcp;
Memfile_LeaseMgr::Memfile_LeaseMgr(const ParameterMap& parameters)
: LeaseMgr(parameters) {
- std::cout << "Warning: Using memfile database backend. It is usable for limited"
- << " testing only. Leases will be lost after restart." << std::endl;
+ LOG_WARN(dhcpsrv_logger, DHCPSRV_MEMFILE_WARNING);
}
Memfile_LeaseMgr::~Memfile_LeaseMgr() {
diff --git a/src/lib/dhcpsrv/option_space.cc b/src/lib/dhcpsrv/option_space.cc
deleted file mode 100644
index 0e802a7..0000000
--- a/src/lib/dhcpsrv/option_space.cc
+++ /dev/null
@@ -1,71 +0,0 @@
-// 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 <dhcpsrv/option_space.h>
-#include <boost/algorithm/string/classification.hpp>
-#include <boost/algorithm/string/predicate.hpp>
-
-namespace isc {
-namespace dhcp {
-
-OptionSpace::OptionSpace(const std::string& name, const bool vendor_space)
- : name_(name), vendor_space_(vendor_space) {
- // Check that provided option space name is valid.
- if (!validateName(name_)) {
- isc_throw(InvalidOptionSpace, "Invalid option space name "
- << name_);
- }
-}
-
-bool
-OptionSpace::validateName(const std::string& name) {
-
- using namespace boost::algorithm;
-
- // Allowed characters are: lower or upper case letters, digits,
- // underscores and hyphens. Empty option spaces are not allowed.
- if (all(name, boost::is_from_range('a', 'z') ||
- boost::is_from_range('A', 'Z') ||
- boost::is_digit() ||
- boost::is_any_of(std::string("-_"))) &&
- !name.empty() &&
- // Hyphens and underscores are not allowed at the beginning
- // and at the end of the option space name.
- !all(find_head(name, 1), boost::is_any_of(std::string("-_"))) &&
- !all(find_tail(name, 1), boost::is_any_of(std::string("-_")))) {
- return (true);
-
- }
- return (false);
-}
-
-OptionSpace6::OptionSpace6(const std::string& name)
- : OptionSpace(name),
- enterprise_number_(0) {
-}
-
-OptionSpace6::OptionSpace6(const std::string& name,
- const uint32_t enterprise_number)
- : OptionSpace(name, true),
- enterprise_number_(enterprise_number) {
-}
-
-void
-OptionSpace6::setVendorSpace(const uint32_t enterprise_number) {
- enterprise_number_ = enterprise_number;
- OptionSpace::setVendorSpace();
-}
-
-} // end of isc::dhcp namespace
-} // end of isc namespace
diff --git a/src/lib/dhcpsrv/option_space.h b/src/lib/dhcpsrv/option_space.h
deleted file mode 100644
index 9eebd76..0000000
--- a/src/lib/dhcpsrv/option_space.h
+++ /dev/null
@@ -1,189 +0,0 @@
-// 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.
-
-#ifndef OPTION_SPACE_H
-#define OPTION_SPACE_H
-
-#include <exceptions/exceptions.h>
-#include <boost/shared_ptr.hpp>
-#include <map>
-#include <stdint.h>
-#include <string>
-
-namespace isc {
-namespace dhcp {
-
-/// @brief Exception to be thrown when invalid option space
-/// is specified.
-class InvalidOptionSpace : public Exception {
-public:
- InvalidOptionSpace(const char* file, size_t line, const char* what) :
- isc::Exception(file, line, what) { };
-};
-
-/// OptionSpace forward declaration.
-class OptionSpace;
-/// A pointer to OptionSpace object.
-typedef boost::shared_ptr<OptionSpace> OptionSpacePtr;
-/// A collection of option spaces.
-typedef std::map<std::string, OptionSpacePtr> OptionSpaceCollection;
-
-/// @brief DHCP option space.
-///
-/// This class represents single option space. The option spaces are used
-/// to group DHCP options having unique option codes. The special type
-/// of the option space is so called "vendor specific option space".
-/// It groups sub-options being sent within Vendor Encapsulated Options.
-/// For DHCPv4 it is the option with code 43. The option spaces are
-/// assigned to option instances represented by isc::dhcp::Option and
-/// other classes derived from it. Each particular option may belong to
-/// multiple option spaces.
-/// This class may be used to represent any DHCPv4 option space. If the
-/// option space is to group DHCPv4 Vendor Encapsulated Options then
-/// "vendor space" flag must be set using \ref OptionSpace::setVendorSpace
-/// or the argument passed to the constructor. In theory, this class can
-/// be also used to represent non-vendor specific DHCPv6 option space
-/// but this is discouraged. For DHCPv6 option spaces the OptionSpace6
-/// class should be used instead.
-///
-/// @note this class is intended to be used to represent DHCPv4 option
-/// spaces only. However, it hasn't been called OptionSpace4 (that would
-/// suggest that it is specific to DHCPv4) because it can be also
-/// used to represent some DHCPv6 option spaces and is a base class
-/// for \ref OptionSpace6. Thus, if one declared the container as follows:
-/// @code
-/// std::vector<OptionSpace4> container;
-/// @endcode
-/// it would suggest that the container holds DHCPv4 option spaces while
-/// it could hold both DHCPv4 and DHCPv6 option spaces as the OptionSpace6
-/// object could be upcast to OptionSpace4. This confusion does not appear
-/// when OptionSpace is used as a name for the base class.
-class OptionSpace {
-public:
-
- /// @brief Constructor.
- ///
- /// @param name option space name.
- /// @param vendor_space boolean value that indicates that the object
- /// describes the vendor specific option space.
- ///
- /// @throw isc::dhcp::InvalidOptionSpace if given option space name
- /// contains invalid characters or is empty. This constructor uses
- /// \ref validateName function to check that the specified name is
- /// correct.
- OptionSpace(const std::string& name, const bool vendor_space = false);
-
- /// @brief Return option space name.
- ///
- /// @return option space name.
- const std::string& getName() const { return (name_); }
-
- /// @brief Mark option space as non-vendor space.
- void clearVendorSpace() {
- vendor_space_ = false;
- }
-
- /// @brief Check if option space is vendor specific.
- ///
- /// @return boolean value that indicates if the object describes
- /// the vendor specific option space.
- bool isVendorSpace() const { return (vendor_space_); }
-
- /// @brief Mark option space as vendor specific.
- void setVendorSpace() {
- vendor_space_ = true;
- }
-
- /// @brief Checks that the provided option space name is valid.
- ///
- /// It is expected that option space name consists of upper or
- /// lower case letters or digits. Also, it may contain underscores
- /// or dashes. Other characters are prohibited. The empty option
- /// space names are invalid.
- ///
- /// @param name option space name to be validated.
- ///
- /// @return true if the option space is valid, else it returns false.
- static bool validateName(const std::string& name);
-
-private:
- std::string name_; ///< Holds option space name.
-
- bool vendor_space_; ///< Is this the vendor space?
-
-};
-
-/// @brief DHCPv6 option space with enterprise number assigned.
-///
-/// This class extends the base class with the support for enterprise numbers.
-/// The enterprise numbers are assigned by IANA to various organizations
-/// and they are carried as uint32_t integers in DHCPv6 Vendor Specific
-/// Information Options (VSIO). For more information refer to RFC3315.
-/// All option spaces that group VSIO options must have enterprise number
-/// set. It can be set using a constructor or \ref setVendorSpace function.
-/// The extra functionality of this class (enterprise numbers) allows to
-/// represent DHCPv6 vendor-specific option spaces but this class is also
-/// intended to be used for all other DHCPv6 option spaces. That way all
-/// DHCPv6 option spaces can be stored in the container holding OptionSpace6
-/// objects. Also, it is easy to mark vendor-specific option space as non-vendor
-/// specific option space (and the other way around) without a need to cast
-/// between OptionSpace and OptionSpace6 types.
-class OptionSpace6 : public OptionSpace {
-public:
-
- /// @brief Constructor for non-vendor-specific options.
- ///
- /// This constructor marks option space as non-vendor specific.
- ///
- /// @param name option space name.
- ///
- /// @throw isc::dhcp::InvalidOptionSpace if given option space name
- /// contains invalid characters or is empty. This constructor uses
- /// \ref OptionSpace::validateName function to check that the specified
- /// name is correct.
- OptionSpace6(const std::string& name);
-
- /// @brief Constructor for vendor-specific options.
- ///
- /// This constructor marks option space as vendor specific and sets
- /// enterprise number to a given value.
- ///
- /// @param name option space name.
- /// @param enterprise_number enterprise number.
- ///
- /// @throw isc::dhcp::InvalidOptionSpace if given option space name
- /// contains invalid characters or is empty. This constructor uses
- /// \ref OptionSpace::validateName function to check that the specified
- /// name is correct.
- OptionSpace6(const std::string& name, const uint32_t enterprise_number);
-
- /// @brief Return enterprise number for the option space.
- ///
- /// @return enterprise number.
- uint32_t getEnterpriseNumber() const { return (enterprise_number_); }
-
- /// @brief Mark option space as vendor specific.
- ///
- /// @param enterprise_number enterprise number.
- void setVendorSpace(const uint32_t enterprise_number);
-
-private:
-
- uint32_t enterprise_number_; ///< IANA assigned enterprise number.
-};
-
-} // namespace isc::dhcp
-} // namespace isc
-
-#endif // OPTION_SPACE_H
diff --git a/src/lib/dhcpsrv/option_space_container.h b/src/lib/dhcpsrv/option_space_container.h
index f90bedd..ba16fbb 100644
--- a/src/lib/dhcpsrv/option_space_container.h
+++ b/src/lib/dhcpsrv/option_space_container.h
@@ -41,7 +41,7 @@ public:
/// @brief Adds a new item to the option_space.
///
/// @param item reference to the item being added.
- /// @param name of the option space.
+ /// @param option_space name of the option space.
void addItem(const ItemType& item, const std::string& option_space) {
ItemsContainerPtr items = getItems(option_space);
items->push_back(item);
diff --git a/src/lib/dhcpsrv/subnet.cc b/src/lib/dhcpsrv/subnet.cc
index 6414f88..0443a33 100644
--- a/src/lib/dhcpsrv/subnet.cc
+++ b/src/lib/dhcpsrv/subnet.cc
@@ -184,5 +184,15 @@ Subnet6::validateOption(const OptionPtr& option) const {
}
}
+
+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 b864321..471fb03 100644
--- a/src/lib/dhcpsrv/subnet.h
+++ b/src/lib/dhcpsrv/subnet.h
@@ -416,6 +416,9 @@ protected:
/// fully trusted.
isc::asiolink::IOAddress last_allocated_;
+ /// @brief Name of the network interface (if connected directly)
+ std::string iface_;
+
private:
/// A collection of option spaces grouping option descriptors.
@@ -496,6 +499,18 @@ public:
return (preferred_);
}
+ /// @brief sets name of the network interface for directly attached networks
+ ///
+ /// 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);
+
+ /// @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;
+
protected:
/// @brief Check if option is valid and can be added to a subnet.
diff --git a/src/lib/dhcpsrv/tests/Makefile.am b/src/lib/dhcpsrv/tests/Makefile.am
index dfe3e78..e19fd87 100644
--- a/src/lib/dhcpsrv/tests/Makefile.am
+++ b/src/lib/dhcpsrv/tests/Makefile.am
@@ -30,13 +30,13 @@ libdhcpsrv_unittests_SOURCES = run_unittests.cc
libdhcpsrv_unittests_SOURCES += addr_utilities_unittest.cc
libdhcpsrv_unittests_SOURCES += alloc_engine_unittest.cc
libdhcpsrv_unittests_SOURCES += cfgmgr_unittest.cc
+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
if HAVE_MYSQL
libdhcpsrv_unittests_SOURCES += mysql_lease_mgr_unittest.cc
endif
-libdhcpsrv_unittests_SOURCES += option_space_unittest.cc
libdhcpsrv_unittests_SOURCES += pool_unittest.cc
libdhcpsrv_unittests_SOURCES += schema_copy.h
libdhcpsrv_unittests_SOURCES += subnet_unittest.cc
@@ -63,6 +63,8 @@ endif
libdhcpsrv_unittests_LDADD = $(top_builddir)/src/lib/dhcpsrv/libb10-dhcpsrv.la
libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
+libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
+libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
diff --git a/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc b/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc
index d023a24..9b3d61b 100644
--- a/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc
+++ b/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc
@@ -351,4 +351,8 @@ TEST_F(CfgMgrTest, optionSpace6) {
// @todo decide if a duplicate vendor space is allowed.
}
+// No specific tests for getSubnet6. That method (2 overloaded versions) is tested
+// in Dhcpv6SrvTest.selectSubnetAddr and Dhcpv6SrvTest.selectSubnetIface
+// (see src/bin/dhcp6/tests/dhcp6_srv_unittest.cc)
+
} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/dbaccess_parser_unittest.cc b/src/lib/dhcpsrv/tests/dbaccess_parser_unittest.cc
new file mode 100644
index 0000000..24cfb1a
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/dbaccess_parser_unittest.cc
@@ -0,0 +1,434 @@
+// 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 <dhcpsrv/dbaccess_parser.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <config/ccsession.h>
+#include <gtest/gtest.h>
+
+#include <map>
+#include <string>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::data;
+using namespace isc::config;
+
+namespace {
+
+/// @brief Database Access Parser test fixture class
+class DbAccessParserTest : public ::testing::Test {
+public:
+ /// @brief Constructor
+ ///
+ /// Just make sure that the lease database is closed before every test
+ /// (the first in particular).
+ DbAccessParserTest() {
+ LeaseMgrFactory::destroy();
+ }
+ /// @brief Destructor
+ ///
+ /// Just make sure that the lease database is closed after every test
+ /// (the last in particular).
+ ~DbAccessParserTest() {
+ LeaseMgrFactory::destroy();
+ }
+
+ /// @brief Build JSON String
+ ///
+ /// Given a array of "const char*" strings representing in order, keyword,
+ /// value, keyword, value, ... and terminated by a NULL, return a string
+ /// that represents the JSON map for the keywords and values.
+ ///
+ /// E.g. given the array of strings: alpha, one, beta, two, NULL, it would
+ /// return the string '{ "alpha": "one", "beta": "two" }'
+ ///
+ /// @param keyval Array of "const char*" strings in the order keyword,
+ /// value, keyword, value ... A NULL entry terminates the list.
+ ///
+ /// @return JSON map for the keyword value array.
+ std::string toJson(const char* keyval[]) {
+ const std::string quote = "\"";
+ const std::string colon = ":";
+ const std::string space = " ";
+
+ string result = "{ ";
+
+ for (size_t i = 0; keyval[i] != NULL; i+= 2) {
+ // Get the value. This should not be NULL. As ASSERT_NE will
+ // cause a return - which gives compilation problems as a return
+ // statement is expected to return a string - use EXPECT_NE and
+ // explicitly return if the expected array is incorrect.
+ EXPECT_NE(static_cast<const char*>(NULL), keyval[i + 1]) <<
+ "Supplied reference keyword/value list does not contain values "
+ "for all keywords";
+ if (keyval[i + 1] == NULL) {
+ return (std::string(""));
+ }
+
+ // Add the separating comma if not the first.
+ if (i != 0) {
+ result += ", ";
+ }
+
+ // Add the keyword and value - make sure that they are quoted.
+ result += quote + keyval[i] + quote + colon + space +
+ quote + keyval[i + 1] + quote;
+ }
+
+ // Add the terminating brace
+ result += " }";
+
+ return (result);
+ }
+
+ /// @brief Check for Keywords
+ ///
+ /// Takes a database access string and checks it against a list of keywords
+ /// and values. It checks that:
+ ///
+ /// a. Every keyword in the string appears once and only once in the
+ /// list.
+ /// b. Every keyword in the list appears in the string.
+ /// c. Every keyword's value is the same as that in the string.
+ ///
+ /// To parse the access string, we use the parsing function in the
+ /// DHCP lease manager.
+ ///
+ /// @param trace_string String that will be used to set the value of a
+ /// SCOPED_TRACE for this call.
+ /// @param dbaccess set of database access parameters to check
+ /// @param keyval Array of "const char*" strings in the order keyword,
+ /// value, keyword, value ... A NULL entry terminates the list.
+ void checkAccessString(const char* trace_string,
+ const DbAccessParser::StringPairMap& parameters,
+ const char* keyval[]) {
+ SCOPED_TRACE(trace_string);
+
+ // Construct a map of keyword value pairs.
+ std::map<string, string> expected;
+ size_t expected_count = 0;
+ for (size_t i = 0; keyval[i] != NULL; i += 2) {
+ // Get the value. This should not be NULL
+ ASSERT_NE(static_cast<const char*>(NULL), keyval[i + 1]) <<
+ "Supplied reference keyword/value list does not contain values "
+ "for all keywords";
+ expected[keyval[i]] = keyval[i + 1];
+
+ // One more keyword processed
+ ++expected_count;
+ }
+
+ // Check no duplicates in the test set of reference keywords.
+ ASSERT_EQ(expected_count, expected.size()) <<
+ "Supplied reference keyword/value list contains duplicate keywords";
+
+ // The passed parameter map should have the same number of entries as
+ // the reference set of keywords.
+ EXPECT_EQ(expected_count, parameters.size());
+
+ // Check that the keywords and keyword values are the same: loop
+ // through the keywords in the database access string.
+ for (LeaseMgr::ParameterMap::const_iterator actual = parameters.begin();
+ actual != parameters.end(); ++actual) {
+
+ // Does the keyword exist in the set of expected keywords?
+ std::map<string, string>::iterator corresponding =
+ expected.find(actual->first);
+ ASSERT_TRUE(corresponding != expected.end());
+
+ // Keyword exists, is the value the same?
+ EXPECT_EQ(corresponding->second, actual->second);
+ }
+ }
+};
+
+
+/// @brief Version of parser with protected methods public
+///
+/// Some of the methods in DbAccessParser are not required to be public in
+/// BIND 10. Instead of being declared "private", they are declared "protected"
+/// so that they can be accessed through a derived class in the unit tests.
+class TestDbAccessParser : public DbAccessParser {
+public:
+
+ /// @brief Constructor
+ ///
+ /// @brief Keyword/value collection of ddatabase access parameters
+ TestDbAccessParser(const std::string& param_name)
+ : DbAccessParser(param_name)
+ {}
+
+ /// @brief Destructor
+ virtual ~TestDbAccessParser()
+ {}
+
+ /// Allow use of superclass's protected functions.
+ using DbAccessParser::getDbAccessParameters;
+ using DbAccessParser::getDbAccessString;
+
+ /// @brief Get database access parameters
+ ///
+ /// Used in testing to check that the configuration information has been
+ /// parsed corrected.
+ ///
+ /// @return Map of keyword/value pairs representing database access
+ /// information.
+ const StringPairMap& getDbAccessParameters() const {
+ return (DbAccessParser::getDbAccessParameters());
+ }
+
+ /// @brief Construct database access string
+ ///
+ /// Constructs the database access string from the stored parameters.
+ ///
+ /// @return Database access string
+ std::string getDbAccessString() const {
+ return (DbAccessParser::getDbAccessString());
+ }
+};
+
+// Check that the parser works with a simple configuration.
+TEST_F(DbAccessParserTest, validTypeMemfile) {
+ const char* config[] = {"type", "memfile",
+ NULL};
+
+ string json_config = toJson(config);
+ ConstElementPtr json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
+ TestDbAccessParser parser("lease-database");
+ EXPECT_NO_THROW(parser.build(json_elements));
+ checkAccessString("Valid memfile", parser.getDbAccessParameters(), config);
+}
+
+// Check that the parser works with a simple configuration that
+// includes empty elements.
+TEST_F(DbAccessParserTest, emptyKeyword) {
+ const char* config[] = {"type", "memfile",
+ "name", "",
+ NULL};
+
+ string json_config = toJson(config);
+ ConstElementPtr json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
+ TestDbAccessParser parser("lease-database");
+ EXPECT_NO_THROW(parser.build(json_elements));
+ checkAccessString("Valid memfile", parser.getDbAccessParameters(), config);
+}
+
+// Check that the parser works with a valid MySQL configuration
+TEST_F(DbAccessParserTest, validTypeMysql) {
+ const char* config[] = {"type", "mysql",
+ "host", "erewhon",
+ "user", "kea",
+ "password", "keapassword",
+ "name", "keatest",
+ NULL};
+
+ string json_config = toJson(config);
+ ConstElementPtr json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
+ TestDbAccessParser parser("lease-database");
+ EXPECT_NO_THROW(parser.build(json_elements));
+ checkAccessString("Valid mysql", parser.getDbAccessParameters(), config);
+}
+
+// A missing 'type' keyword should cause an exception to be thrown.
+TEST_F(DbAccessParserTest, missingTypeKeyword) {
+ const char* config[] = {"host", "erewhon",
+ "user", "kea",
+ "password", "keapassword",
+ "name", "keatest",
+ NULL};
+
+ string json_config = toJson(config);
+ ConstElementPtr json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
+ TestDbAccessParser parser("lease-database");
+ EXPECT_THROW(parser.build(json_elements), TypeKeywordMissing);
+}
+
+// Check that the factory function works.
+TEST_F(DbAccessParserTest, factory) {
+
+ // Check that the parser is built through the factory.
+ boost::scoped_ptr<DhcpConfigParser> parser(
+ DbAccessParser::factory("lease-database"));
+ EXPECT_TRUE(parser);
+ DbAccessParser* dbap = dynamic_cast<DbAccessParser*>(parser.get());
+ EXPECT_NE(static_cast<DbAccessParser*>(NULL), dbap);
+}
+
+// Check reconfiguration. Checks that incremental changes applied to the
+// database configuration are incremental.
+TEST_F(DbAccessParserTest, incrementalChanges) {
+ const char* config1[] = {"type", "memfile",
+ NULL};
+
+ // Applying config2 will cause a wholesale change.
+ const char* config2[] = {"type", "mysql",
+ "host", "erewhon",
+ "user", "kea",
+ "password", "keapassword",
+ "name", "keatest",
+ NULL};
+
+ // Applying incremental2 should cause a change to config3.
+ const char* incremental2[] = {"user", "me",
+ "password", "meagain",
+ NULL};
+ const char* config3[] = {"type", "mysql",
+ "host", "erewhon",
+ "user", "me",
+ "password", "meagain",
+ "name", "keatest",
+ NULL};
+
+ // incremental3 will cause an exception. There should be no change
+ // to the returned value.
+ const char* incremental3[] = {"type", "invalid",
+ "user", "you",
+ "password", "youagain",
+ NULL};
+
+ // incremental4 is a compatible change and should cause a transition
+ // to config4.
+ const char* incremental4[] = {"user", "them",
+ "password", "",
+ NULL};
+ const char* config4[] = {"type", "mysql",
+ "host", "erewhon",
+ "user", "them",
+ "password", "",
+ "name", "keatest",
+ NULL};
+
+ TestDbAccessParser parser("lease-database");
+
+ // First configuration string should cause a representation of that string
+ // to be held.
+ string json_config = toJson(config1);
+ ConstElementPtr json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
+ EXPECT_NO_THROW(parser.build(json_elements));
+ checkAccessString("Initial configuration", parser.getDbAccessParameters(),
+ config1);
+
+ // Applying a wholesale change will cause the access string to change
+ // to a representation of the new configuration.
+ json_config = toJson(config2);
+ json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
+ EXPECT_NO_THROW(parser.build(json_elements));
+ checkAccessString("Subsequent configuration", parser.getDbAccessParameters(),
+ config2);
+
+ // Applying an incremental change will cause the representation to change
+ // incrementally.
+ json_config = toJson(incremental2);
+ json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
+ EXPECT_NO_THROW(parser.build(json_elements));
+ checkAccessString("Incremental configuration", parser.getDbAccessParameters(),
+ config3);
+
+ // Applying the next incremental change should cause an exception to be
+ // thrown and there be no change to the access string.
+ json_config = toJson(incremental3);
+ json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
+ EXPECT_THROW(parser.build(json_elements), BadValue);
+ checkAccessString("Incompatible incremental change", parser.getDbAccessParameters(),
+ config3);
+
+ // Applying an incremental change will cause the representation to change
+ // incrementally.
+ json_config = toJson(incremental4);
+ json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
+ EXPECT_NO_THROW(parser.build(json_elements));
+ checkAccessString("Compatible incremental change", parser.getDbAccessParameters(),
+ config4);
+}
+
+// Check that the database access string is constructed correctly.
+TEST_F(DbAccessParserTest, getDbAccessString) {
+ const char* config[] = {"type", "mysql",
+ "host", "" ,
+ "name", "keatest",
+ NULL};
+
+ string json_config = toJson(config);
+ ConstElementPtr json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
+ TestDbAccessParser parser("lease-database");
+ EXPECT_NO_THROW(parser.build(json_elements));
+
+ // Get the database access string
+ std::string dbaccess = parser.getDbAccessString();
+
+ // String should be either "type=mysql name=keatest" or
+ // "name=keatest type=mysql". The "host" entry is null, so should not be
+ // output.
+ EXPECT_TRUE((dbaccess == "type=mysql name=keatest") ||
+ (dbaccess == "name=keatest type=mysql"));
+}
+
+// Check that the "commit" function actually opens the database. We will
+// only do this for the "memfile" database, as that does not assume that the
+// test has been built with MySQL support.
+TEST_F(DbAccessParserTest, commit) {
+
+ // Verify that no lease database is open
+ EXPECT_THROW({
+ LeaseMgr& manager = LeaseMgrFactory::instance();
+ manager.getType(); // Never executed but satisfies compiler
+ }, isc::dhcp::NoLeaseManager);
+
+ // Set up the parser to open the memfile database.
+ const char* config[] = {"type", "memfile",
+ NULL};
+ string json_config = toJson(config);
+ ConstElementPtr json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
+ TestDbAccessParser parser("lease-database");
+ EXPECT_NO_THROW(parser.build(json_elements));
+
+ // Ensure that the access string is as expected.
+ EXPECT_EQ(std::string("type=memfile"), parser.getDbAccessString());
+
+ // Committal of the parser changes should open the database.
+ EXPECT_NO_THROW(parser.commit());
+
+ // Verify by checking the type of database open.
+ std::string dbtype;
+ EXPECT_NO_THROW(dbtype = LeaseMgrFactory::instance().getType());
+ EXPECT_EQ(std::string("memfile"), dbtype);
+}
+
+}; // Anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/option_space_unittest.cc b/src/lib/dhcpsrv/tests/option_space_unittest.cc
deleted file mode 100644
index f8d75c8..0000000
--- a/src/lib/dhcpsrv/tests/option_space_unittest.cc
+++ /dev/null
@@ -1,150 +0,0 @@
-// 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 <dhcpsrv/option_space.h>
-
-#include <gtest/gtest.h>
-
-using namespace isc::dhcp;
-using namespace isc;
-
-namespace {
-
-// The purpose of this test is to verify that the constructor
-// creates an object with members initialized to correct values.
-TEST(OptionSpaceTest, constructor) {
- // Create some option space.
- OptionSpace space("isc", true);
- EXPECT_EQ("isc", space.getName());
- EXPECT_TRUE(space.isVendorSpace());
-
- // Create another object with different values
- // to check that the values will change.
- OptionSpace space2("abc", false);
- EXPECT_EQ("abc", space2.getName());
- EXPECT_FALSE(space2.isVendorSpace());
-
- // Verify that constructor throws exception if invalid
- // option space name is provided.
- EXPECT_THROW(OptionSpace("invalid%space.name"), InvalidOptionSpace);
-}
-
-// The purpose of this test is to verify that the vendor-space flag
-// can be overriden.
-TEST(OptionSpaceTest, setVendorSpace) {
- OptionSpace space("isc", true);
- EXPECT_EQ("isc", space.getName());
- EXPECT_TRUE(space.isVendorSpace());
-
- // Override the vendor space flag.
- space.clearVendorSpace();
- EXPECT_FALSE(space.isVendorSpace());
-}
-
-// The purpose of this test is to verify that the static function
-// to validate the option space name works correctly.
-TEST(OptionSpaceTest, validateName) {
- // Positive test scenarios: letters, digits, dashes, underscores
- // lower/upper case allowed.
- EXPECT_TRUE(OptionSpace::validateName("abc"));
- EXPECT_TRUE(OptionSpace::validateName("dash-allowed"));
- EXPECT_TRUE(OptionSpace::validateName("two-dashes-allowed"));
- EXPECT_TRUE(OptionSpace::validateName("underscore_allowed"));
- EXPECT_TRUE(OptionSpace::validateName("underscore_three_times_allowed"));
- EXPECT_TRUE(OptionSpace::validateName("digits0912"));
- EXPECT_TRUE(OptionSpace::validateName("1234"));
- EXPECT_TRUE(OptionSpace::validateName("UPPER_CASE_allowed"));
-
- // Negative test scenarions: empty strings, dots, spaces are not
- // allowed
- EXPECT_FALSE(OptionSpace::validateName(""));
- EXPECT_FALSE(OptionSpace::validateName(" "));
- EXPECT_FALSE(OptionSpace::validateName(" isc "));
- EXPECT_FALSE(OptionSpace::validateName("isc "));
- EXPECT_FALSE(OptionSpace::validateName(" isc"));
- EXPECT_FALSE(OptionSpace::validateName("isc with-space"));
-
- // Hyphens and underscores are not allowed at the beginning
- // and at the end of the option space name.
- EXPECT_FALSE(OptionSpace::validateName("-isc"));
- EXPECT_FALSE(OptionSpace::validateName("isc-"));
- EXPECT_FALSE(OptionSpace::validateName("_isc"));
- EXPECT_FALSE(OptionSpace::validateName("isc_"));
-
- // Test other special characters
- const char specials[] = { '!', '@', '#', '$', '%', '^', '&', '*', '(', ')',
- '+', '=', '[', ']', '{', '}', ';', ':', '"', '\'',
- '\\', '|', '<','>', ',', '.', '?', '~', '`' };
- for (int i = 0; i < sizeof(specials); ++i) {
- std::ostringstream stream;
- // Concatenate valid option space name: "abc" with an invalid character.
- // That way we get option space names like: "abc!", "abc$" etc. It is
- // expected that the validating function fails form them.
- stream << "abc" << specials[i];
- EXPECT_FALSE(OptionSpace::validateName(stream.str()))
- << "Test failed for special character '" << specials[i] << "'.";
- }
-}
-
-// The purpose of this test is to verify that the constructors of the
-// OptionSpace6 class set the class members to correct values.
-TEST(OptionSpace6Test, constructor) {
- // Create some option space and do not specify enterprise number.
- // In such case the vendor space flag is expected to be
- // set to false.
- OptionSpace6 space1("abcd");
- EXPECT_EQ("abcd", space1.getName());
- EXPECT_FALSE(space1.isVendorSpace());
-
- // Create an option space and specify an enterprise number. In this
- // case the vendor space flag is expected to be set to true and the
- // enterprise number should be set to a desired value.
- OptionSpace6 space2("abcd", 2145);
- EXPECT_EQ("abcd", space2.getName());
- EXPECT_TRUE(space2.isVendorSpace());
- EXPECT_EQ(2145, space2.getEnterpriseNumber());
-
- // Verify that constructors throw an exception when invalid option
- // space name has been specified.
- EXPECT_THROW(OptionSpace6("isc dhcp"), InvalidOptionSpace);
- EXPECT_THROW(OptionSpace6("isc%dhcp", 2145), InvalidOptionSpace);
-}
-
-// The purpose of this test is to verify an option space can be marked
-// vendor option space and enterprise number can be set.
-TEST(OptionSpace6Test, setVendorSpace) {
- OptionSpace6 space("isc");
- EXPECT_EQ("isc", space.getName());
- EXPECT_FALSE(space.isVendorSpace());
-
- // Mark it vendor option space and set enterprise id.
- space.setVendorSpace(1234);
- EXPECT_TRUE(space.isVendorSpace());
- EXPECT_EQ(1234, space.getEnterpriseNumber());
-
- // Override the enterprise number to make sure and make sure that
- // the new number is returned by the object.
- space.setVendorSpace(2345);
- EXPECT_TRUE(space.isVendorSpace());
- EXPECT_EQ(2345, space.getEnterpriseNumber());
-
- // Clear the vendor option space flag.
- space.clearVendorSpace();
- EXPECT_FALSE(space.isVendorSpace());
-}
-
-
-}; // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/subnet_unittest.cc b/src/lib/dhcpsrv/tests/subnet_unittest.cc
index 689af87..9f06c89 100644
--- a/src/lib/dhcpsrv/tests/subnet_unittest.cc
+++ b/src/lib/dhcpsrv/tests/subnet_unittest.cc
@@ -505,4 +505,15 @@ TEST(Subnet6Test, get) {
EXPECT_EQ(32, subnet.get().second);
}
+// This trivial test checks if interface name is stored properly
+// in Subnet6 objects.
+TEST(Subnet6Test, iface) {
+ Subnet6 subnet(IOAddress("2001:db8::"), 32, 1, 2, 3, 4);
+
+ EXPECT_TRUE(subnet.getIface().empty());
+
+ subnet.setIface("en1");
+ EXPECT_EQ("en1", subnet.getIface());
+}
+
};
diff --git a/src/lib/dns/master_lexer.cc b/src/lib/dns/master_lexer.cc
index 7febd58..9563355 100644
--- a/src/lib/dns/master_lexer.cc
+++ b/src/lib/dns/master_lexer.cc
@@ -48,6 +48,7 @@ using namespace master_lexer_internal;
struct MasterLexer::MasterLexerImpl {
MasterLexerImpl() : source_(NULL), token_(MasterToken::NOT_STARTED),
+ total_size_(0), popped_size_(0),
paren_count_(0), last_was_eol_(true),
has_previous_(false),
previous_paren_count_(0),
@@ -91,11 +92,28 @@ struct MasterLexer::MasterLexerImpl {
separators_.test(c & 0x7f));
}
+ void setTotalSize() {
+ assert(source_ != NULL);
+ if (total_size_ != SOURCE_SIZE_UNKNOWN) {
+ const size_t current_size = source_->getSize();
+ if (current_size != SOURCE_SIZE_UNKNOWN) {
+ total_size_ += current_size;
+ } else {
+ total_size_ = SOURCE_SIZE_UNKNOWN;
+ }
+ }
+ }
+
std::vector<InputSourcePtr> sources_;
InputSource* source_; // current source (NULL if sources_ is empty)
MasterToken token_; // currently recognized token (set by a state)
std::vector<char> data_; // placeholder for string data
+ // Keep track of the total size of all sources and characters that have
+ // been read from sources already popped.
+ size_t total_size_; // accumulated size (# of chars) of sources
+ size_t popped_size_; // total size of sources that have been popped
+
// These are used in states, and defined here only as a placeholder.
// The main lexer class does not need these members.
size_t paren_count_; // nest count of the parentheses
@@ -139,6 +157,7 @@ MasterLexer::pushSource(const char* filename, std::string* error) {
impl_->source_ = impl_->sources_.back().get();
impl_->has_previous_ = false;
impl_->last_was_eol_ = true;
+ impl_->setTotalSize();
return (true);
}
@@ -154,6 +173,7 @@ MasterLexer::pushSource(std::istream& input) {
impl_->source_ = impl_->sources_.back().get();
impl_->has_previous_ = false;
impl_->last_was_eol_ = true;
+ impl_->setTotalSize();
}
void
@@ -162,6 +182,7 @@ MasterLexer::popSource() {
isc_throw(InvalidOperation,
"MasterLexer::popSource on an empty source");
}
+ impl_->popped_size_ += impl_->source_->getPosition();
impl_->sources_.pop_back();
impl_->source_ = impl_->sources_.empty() ? NULL :
impl_->sources_.back().get();
@@ -191,22 +212,12 @@ MasterLexer::getSourceLine() const {
size_t
MasterLexer::getTotalSourceSize() const {
- size_t total_size = 0;
- BOOST_FOREACH(InputSourcePtr& src, impl_->sources_) {
- // If the size of any pushed source is unknown, the total is also
- // considered unknown.
- if (src->getSize() == SOURCE_SIZE_UNKNOWN) {
- return (SOURCE_SIZE_UNKNOWN);
- }
-
- total_size += src->getSize();
- }
- return (total_size);
+ return (impl_->total_size_);
}
size_t
MasterLexer::getPosition() const {
- size_t position = 0;
+ size_t position = impl_->popped_size_;
BOOST_FOREACH(InputSourcePtr& src, impl_->sources_) {
position += src->getPosition();
}
diff --git a/src/lib/dns/master_lexer.h b/src/lib/dns/master_lexer.h
index d14febe..31c6443 100644
--- a/src/lib/dns/master_lexer.h
+++ b/src/lib/dns/master_lexer.h
@@ -477,7 +477,7 @@ public:
///
/// This method returns the sum of the size of sources that have been
/// pushed to the lexer by the time of the call. It would give the
- /// caller of some hint about the amount of data the lexer is working on.
+ /// caller some hint about the amount of data the lexer is working on.
///
/// The size of a normal file is equal to the file size at the time of
/// the source is pushed. The size of other type of input stream is
@@ -488,25 +488,42 @@ public:
/// stream is unknown. It happens, for example, if the standard input
/// is associated with a pipe from the output of another process and it's
/// specified as an input source. If the size of some of the pushed
- /// pushed source is unknown, this method returns SOURCE_SIZE_UNKNOWN.
+ /// source is unknown, this method returns SOURCE_SIZE_UNKNOWN.
///
- /// If there is no source pushed in the lexer, it returns 0.
+ /// The total size won't change when a source is popped. So the return
+ /// values of this method will monotonically increase or
+ /// \c SOURCE_SIZE_UNKNOWN; once it returns \c SOURCE_SIZE_UNKNOWN,
+ /// any subsequent call will also result in that value, by the above
+ /// definition.
+ ///
+ /// Before pushing any source, it returns 0.
///
/// \throw None
size_t getTotalSourceSize() const;
- /// \brief Return the position of lexer in the currently pushed sources.
+ /// \brief Return the position of lexer in the pushed sources so far.
///
/// This method returns the position in terms of the number of recognized
- /// characters from all sources. Roughly speaking, the position in a
- /// single source is the offset from the beginning of the file or stream
- /// to the current "read cursor" of the lexer, and the return value of
- /// this method is the sum of the position in all the pushed sources.
+ /// characters from all sources that have been pushed by the time of the
+ /// call. Conceptually, the position in a single source is the offset
+ /// from the beginning of the file or stream to the current "read cursor"
+ /// of the lexer. The return value of this method is the sum of the
+ /// positions in all the pushed sources. If any of the sources has
+ /// already been popped, the position of the source at the time of the
+ /// pop operation will be used for the calculation.
///
/// If the lexer reaches the end for each of all the pushed sources,
/// the return value should be equal to that of \c getTotalSourceSize().
+ /// It's generally expected that a source is popped when the lexer
+ /// reaches the end of the source. So, when the application of this
+ /// class parses all contents of all sources, possibly with multiple
+ /// pushes and pops, the return value of this method and
+ /// \c getTotalSourceSize() should be identical (unless the latter
+ /// returns SOURCE_SIZE_UNKNOWN). But this is not necessarily
+ /// guaranteed as the application can pop a source in the middle of
+ /// parsing it.
///
- /// If there is no source pushed in the lexer, it returns 0.
+ /// Before pushing any source, it returns 0.
///
/// The return values of this method and \c getTotalSourceSize() would
/// give the caller an idea of the progress of the lexer at the time of
@@ -515,11 +532,10 @@ public:
/// this way may not make much sense; it can only give an informational
/// hint of the progress.
///
- /// Note also that if a source is popped, this method will normally return
- /// a smaller number by definition (and so will \c getTotalSourceSize()).
- /// Likewise, the conceptual "read cursor" would move backward after a
+ /// Note that the conceptual "read cursor" would move backward after a
/// call to \c ungetToken(), in which case this method will return a
- /// smaller value, too.
+ /// smaller value. That is, unlike \c getTotalSourceSize(), return
+ /// values of this method may not always monotonically increase.
///
/// \throw None
size_t getPosition() const;
diff --git a/src/lib/dns/master_loader.cc b/src/lib/dns/master_loader.cc
index 400db43..29e7e1d 100644
--- a/src/lib/dns/master_loader.cc
+++ b/src/lib/dns/master_loader.cc
@@ -76,7 +76,8 @@ public:
previous_name_(false),
complete_(false),
seen_error_(false),
- warn_rfc1035_ttl_(true)
+ warn_rfc1035_ttl_(true),
+ rr_count_(0)
{}
void pushSource(const std::string& filename, const Name& current_origin) {
@@ -103,6 +104,9 @@ public:
bool loadIncremental(size_t count_limit);
+ size_t getSize() const { return (lexer_.getTotalSourceSize()); }
+ size_t getPosition() const { return (lexer_.getPosition()); }
+
private:
void reportError(const std::string& filename, size_t line,
const std::string& reason)
@@ -418,12 +422,14 @@ private:
vector<IncludeInfo> include_info_;
bool previous_name_; // True if there was a previous name in this file
// (false at the beginning or after an $INCLUDE line)
+
public:
bool complete_; // All work done.
bool seen_error_; // Was there at least one error during the
// load?
bool warn_rfc1035_ttl_; // should warn if implicit TTL determination
// from the previous RR is used.
+ size_t rr_count_; // number of RRs successfully loaded
};
// A helper method of loadIncremental, parsing the first token of a new line.
@@ -554,6 +560,7 @@ MasterLoader::MasterLoaderImpl::loadIncremental(size_t count_limit) {
rdata);
// Good, we loaded another one
++count;
+ ++rr_count_;
} else {
seen_error_ = true;
if (!many_errors_) {
@@ -630,5 +637,15 @@ MasterLoader::loadedSucessfully() const {
return (impl_->complete_ && !impl_->seen_error_);
}
+size_t
+MasterLoader::getSize() const {
+ return (impl_->getSize());
+}
+
+size_t
+MasterLoader::getPosition() const {
+ return (impl_->getPosition());
+}
+
} // end namespace dns
} // end namespace isc
diff --git a/src/lib/dns/master_loader.h b/src/lib/dns/master_loader.h
index 9d8a755..37b2ef4 100644
--- a/src/lib/dns/master_loader.h
+++ b/src/lib/dns/master_loader.h
@@ -142,6 +142,44 @@ public:
/// finishing the load.
bool loadedSucessfully() const;
+ /// \brief Return the total size of the zone files and streams.
+ ///
+ /// This method returns the size of the source of the zone to be loaded
+ /// (master zone files or streams) that is known at the time of the call.
+ /// For a zone file, it's the size of the file; for a stream, it's the
+ /// size of the data (in bytes) available at the start of the load.
+ /// If separate zone files are included via the $INCLUDE directive, the
+ /// sum of the sizes of these files are added.
+ ///
+ /// If the loader is constructed with a stream, the size can be
+ /// "unknown" as described for \c MasterLexer::getTotalSourceSize().
+ /// In this case this method always returns
+ /// \c MasterLexer::SOURCE_SIZE_UNKNOWN.
+ ///
+ /// If the loader is constructed with a zone file, this method
+ /// initially returns 0. So until either \c load() or \c loadIncremental()
+ /// is called, the value is meaningless.
+ ///
+ /// Note that when the source includes separate files, this method
+ /// cannot take into account the included files that the loader has not
+ /// recognized at the time of call. So it's possible that this method
+ /// returns different values at different times of call.
+ ///
+ /// \throw None
+ size_t getSize() const;
+
+ /// \brief Return the position of the loader in zone.
+ ///
+ /// This method returns a conceptual "position" of the loader in the
+ /// zone to be loaded. Specifically, it returns the total number of
+ /// characters contained in the zone files and streams and recognized
+ /// by the loader. Before starting the load it returns 0; on successful
+ /// completion it will be equal to the return value of \c getSize()
+ /// (unless the latter returns \c MasterLexer::SOURCE_SIZE_UNKNOWN).
+ ///
+ /// \throw None
+ size_t getPosition() const;
+
private:
class MasterLoaderImpl;
MasterLoaderImpl* impl_;
@@ -151,3 +189,7 @@ private:
} // end namespace isc
#endif // MASTER_LOADER_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/python/rrset_collection_python.cc b/src/lib/dns/python/rrset_collection_python.cc
index df313f7..cfdcdae 100644
--- a/src/lib/dns/python/rrset_collection_python.cc
+++ b/src/lib/dns/python/rrset_collection_python.cc
@@ -55,10 +55,23 @@ setTypeError(PyObject* pobj, const char* var_name, const char* type_name) {
}
}
+// RRsetCollectionBase: the base RRsetCollection class in Python.
//
-// RRsetCollectionBase
-//
-
+// Any derived RRsetCollection class is supposed to be inherited from this
+// class:
+// - If the derived class is implemented via a C++ wrapper (associated with
+// a C++ implementation of RRsetCollection), its PyTypeObject should
+// specify rrset_collection_base_type for tp_base. Its C/C++-representation
+// of objects should be compatible with s_RRsetCollection, and the wrapper
+// should set its cppobj member to point to the corresponding C++
+// RRsetCollection object. Normally it doesn't have to provide Python
+// wrapper of find(); the Python interpreter will then call find() on
+// the base class, which ensures that the corresponding C++ version of
+// find() will be used.
+// - If the derived class is implemented purely in Python, it must implement
+// find() in Python within the class. As explained in the first bullet,
+// the base class method is generally expected to be used only for C++
+// wrapper of RRsetCollection derived class.
namespace {
int
RRsetCollectionBase_init(PyObject*, PyObject*, PyObject*) {
@@ -70,8 +83,13 @@ RRsetCollectionBase_init(PyObject*, PyObject*, PyObject*) {
void
RRsetCollectionBase_destroy(PyObject* po_self) {
s_RRsetCollection* self = static_cast<s_RRsetCollection*>(po_self);
- delete self->cppobj;
- self->cppobj = NULL;
+
+ // Any C++-wrapper of derived RRsetCollection class should have its own
+ // destroy function (as it may manage cppobj in its own way);
+ // Python-only derived classes shouldn't set cppobj (which is
+ // 0-initialized). So this assertion must hold.
+ assert(self->cppobj == NULL);
+
Py_TYPE(self)->tp_free(self);
}
@@ -79,6 +97,9 @@ PyObject*
RRsetCollectionBase_find(PyObject* po_self, PyObject* args) {
s_RRsetCollection* self = static_cast<s_RRsetCollection*>(po_self);
+ // If this function is called with cppobj being NULL, this means
+ // a pure-Python derived class skips implementing its own find().
+ // This is an error (see general description above).
if (self->cppobj == NULL) {
PyErr_Format(PyExc_TypeError, "find() is not implemented in the "
"derived RRsetCollection class");
@@ -108,6 +129,9 @@ RRsetCollectionBase_find(PyObject* po_self, PyObject* args) {
}
Py_RETURN_NONE;
}
+ } catch (const RRsetCollectionError& ex) {
+ PyErr_SetString(po_RRsetCollectionError, ex.what());
+ return (NULL);
} catch (const std::exception& ex) {
const string ex_what = "Unexpected failure in "
"RRsetCollectionBase.find: " + string(ex.what());
@@ -137,6 +161,9 @@ PyMethodDef RRsetCollectionBase_methods[] = {
namespace isc {
namespace dns {
namespace python {
+// Definition of class specific exception(s)
+PyObject* po_RRsetCollectionError;
+
// This defines the complete type for reflection in python and
// parsing of PyObject* to s_RRsetCollection
// Most of the functions are not actually implemented and NULL here.
@@ -193,18 +220,27 @@ PyTypeObject rrset_collection_base_type = {
// Module Initialization, all statics are initialized here
bool
initModulePart_RRsetCollectionBase(PyObject* mod) {
- // We initialize the static description object with PyType_Ready(),
- // then add it to the module. This is not just a check! (leaving
- // this out results in segmentation faults)
- if (PyType_Ready(&rrset_collection_base_type) < 0) {
+ if (!initClass(rrset_collection_base_type, "RRsetCollectionBase", mod)) {
return (false);
}
- void* p = &rrset_collection_base_type;
- if (PyModule_AddObject(mod, "RRsetCollectionBase",
- static_cast<PyObject*>(p)) < 0) {
+
+ try {
+ po_RRsetCollectionError =
+ PyErr_NewException("dns.RRsetCollectionError",
+ po_IscException, NULL);
+ PyObjectContainer(po_RRsetCollectionError).installToModule(
+ mod, "RRsetCollectionError");
+ } catch (const std::exception& ex) {
+ const std::string ex_what =
+ "Unexpected failure in RRsetCollectionBase initialization: " +
+ std::string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ return (false);
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError, "Unexpected failure in "
+ "RRsetCollectionBase initialization");
return (false);
}
- Py_INCREF(&rrset_collection_base_type);
return (true);
}
@@ -405,18 +441,9 @@ PyTypeObject rrset_collection_type = {
// Module Initialization, all statics are initialized here
bool
initModulePart_RRsetCollection(PyObject* mod) {
- // We initialize the static description object with PyType_Ready(),
- // then add it to the module. This is not just a check! (leaving
- // this out results in segmentation faults)
- if (PyType_Ready(&rrset_collection_type) < 0) {
- return (false);
- }
- void* p = &rrset_collection_type;
- if (PyModule_AddObject(mod, "RRsetCollection",
- static_cast<PyObject*>(p)) < 0) {
+ if (!initClass(rrset_collection_type, "RRsetCollection", mod)) {
return (false);
}
- Py_INCREF(&rrset_collection_type);
return (true);
}
diff --git a/src/lib/dns/python/rrset_collection_python.h b/src/lib/dns/python/rrset_collection_python.h
index 98cb84b..ea442bb 100644
--- a/src/lib/dns/python/rrset_collection_python.h
+++ b/src/lib/dns/python/rrset_collection_python.h
@@ -23,10 +23,13 @@ class RRsetCollectionBase;
namespace python {
-// The s_* Class simply covers one instantiation of the object
+// The s_* Class simply covers one instantiation of the object.
// This structure will be commonly used for all derived classes of
// RRsetCollectionBase. cppobj will point to an instance of the specific
-// derived class.
+// derived class of (C++) RRsetCollectionBase.
+//
+// A C++ wrapper for Python version of RRsetCollection should set this
+// variable, and skip the implementation of C++ wrapper of find() method.
class s_RRsetCollection : public PyObject {
public:
s_RRsetCollection() : cppobj(NULL) {}
@@ -39,6 +42,9 @@ extern PyTypeObject rrset_collection_base_type;
// Python type information for dns.RRsetCollection
extern PyTypeObject rrset_collection_type;
+// Class specific exceptions
+extern PyObject* po_RRsetCollectionError;
+
bool initModulePart_RRsetCollectionBase(PyObject* mod);
bool initModulePart_RRsetCollection(PyObject* mod);
diff --git a/src/lib/dns/python/rrset_collection_python_inc.cc b/src/lib/dns/python/rrset_collection_python_inc.cc
index 5c1e532..f6eb8a3 100644
--- a/src/lib/dns/python/rrset_collection_python_inc.cc
+++ b/src/lib/dns/python/rrset_collection_python_inc.cc
@@ -1,7 +1,7 @@
namespace {
// Modifications
// - libdns++ => isc.dns, libdatasrc => isc.datasrc
-// - note about the constructor.
+// - note about the direct construction.
// - add note about iteration
const char* const RRsetCollectionBase_doc = "\
Generic class to represent a set of RRsets.\n\
@@ -18,8 +18,8 @@ maybe class) and a way to iterate over all RRsets.\n\
See RRsetCollection for a simple isc.dns implementation. Other modules\n\
such as isc.datasrc will have another implementation.\n\
\n\
-This base class cannot be directly instantiated, so no constructor is\n\
-defined.\n\
+This base class cannot be directly instantiated. Such an attempt will\n\
+result in a TypeError exception.\n\
\n\
";
@@ -79,18 +79,18 @@ RRsetCollection(filename, origin, rrclass)\n\
origin (isc.dns.Name) The zone origin.\n\
rrclass (isc.dns.RRClass) The zone class.\n\
\n\
-RRsetCollection(input_stream, origin, rrclass)\n\
+RRsetCollection(input, origin, rrclass)\n\
\n\
Constructor.\n\
\n\
This constructor is similar to the previous one, but instead of\n\
- taking a filename to load a zone from, it takes a byte object,\n\
+ taking a filename to load a zone from, it takes a bytes object,\n\
representing the zone contents in text.\n\
The constructor throws IscException if there is an error\n\
during loading.\n\
\n\
Parameters:\n\
- input (byte) Textual representation of the zone.\n\
+ input (bytes) Textual representation of the zone.\n\
origin (isc.dns.Name) The zone origin.\n\
rrclass (isc.dns.RRClass) The zone class.\n\
\n\
diff --git a/src/lib/dns/rrset_collection_base.h b/src/lib/dns/rrset_collection_base.h
index 5ae172a..7ccf7b5 100644
--- a/src/lib/dns/rrset_collection_base.h
+++ b/src/lib/dns/rrset_collection_base.h
@@ -185,6 +185,8 @@ public:
}
};
+typedef boost::shared_ptr<RRsetCollectionBase> RRsetCollectionPtr;
+
} // end of namespace dns
} // end of namespace isc
diff --git a/src/lib/dns/tests/master_lexer_unittest.cc b/src/lib/dns/tests/master_lexer_unittest.cc
index aececa7..039a0c3 100644
--- a/src/lib/dns/tests/master_lexer_unittest.cc
+++ b/src/lib/dns/tests/master_lexer_unittest.cc
@@ -52,7 +52,6 @@ void
checkEmptySource(const MasterLexer& lexer) {
EXPECT_TRUE(lexer.getSourceName().empty());
EXPECT_EQ(0, lexer.getSourceLine());
- EXPECT_EQ(0, lexer.getTotalSourceSize());
EXPECT_EQ(0, lexer.getPosition());
}
@@ -78,6 +77,7 @@ TEST_F(MasterLexerTest, pushStream) {
lexer.popSource();
EXPECT_EQ(0, lexer.getSourceCount());
checkEmptySource(lexer);
+ EXPECT_EQ(4, lexer.getTotalSourceSize()); // this shouldn't change
}
TEST_F(MasterLexerTest, pushStreamFail) {
@@ -105,6 +105,7 @@ TEST_F(MasterLexerTest, pushFile) {
lexer.popSource();
checkEmptySource(lexer);
EXPECT_EQ(0, lexer.getSourceCount());
+ EXPECT_EQ(143, lexer.getTotalSourceSize()); // this shouldn't change
// If we give a non NULL string pointer, its content will be intact
// if pushSource succeeds.
@@ -133,22 +134,44 @@ TEST_F(MasterLexerTest, pushFileFail) {
}
TEST_F(MasterLexerTest, nestedPush) {
- ss << "test";
+ const string test_txt = "test";
+ ss << test_txt;
lexer.pushSource(ss);
+
+ EXPECT_EQ(test_txt.size(), lexer.getTotalSourceSize());
+ EXPECT_EQ(0, lexer.getPosition());
+
EXPECT_EQ(expected_stream_name, lexer.getSourceName());
+ // Read the string; getPosition() should reflect that.
+ EXPECT_EQ(MasterToken::STRING, lexer.getNextToken().getType());
+ EXPECT_EQ(test_txt.size(), lexer.getPosition());
+
// We can push another source without popping the previous one.
lexer.pushSource(TEST_DATA_SRCDIR "/masterload.txt");
EXPECT_EQ(TEST_DATA_SRCDIR "/masterload.txt", lexer.getSourceName());
- EXPECT_EQ(143 + 4, lexer.getTotalSourceSize()); // see above for magic nums
+ EXPECT_EQ(143 + test_txt.size(),
+ lexer.getTotalSourceSize()); // see above for magic nums
+
+ // the next token should be the EOL (skipping a comment line), its
+ // position in the file is 35 (hardcoded).
+ EXPECT_EQ(MasterToken::END_OF_LINE, lexer.getNextToken().getType());
+ EXPECT_EQ(test_txt.size() + 35, lexer.getPosition());
// popSource() works on the "topmost" (last-pushed) source
lexer.popSource();
EXPECT_EQ(expected_stream_name, lexer.getSourceName());
- EXPECT_EQ(4, lexer.getTotalSourceSize());
+
+ // pop shouldn't change the total size and the current position
+ EXPECT_EQ(143 + test_txt.size(), lexer.getTotalSourceSize());
+ EXPECT_EQ(test_txt.size() + 35, lexer.getPosition());
lexer.popSource();
EXPECT_TRUE(lexer.getSourceName().empty());
+
+ // size and position still shouldn't change
+ EXPECT_EQ(143 + test_txt.size(), lexer.getTotalSourceSize());
+ EXPECT_EQ(test_txt.size() + 35, lexer.getPosition());
}
TEST_F(MasterLexerTest, unknownSourceSize) {
@@ -164,9 +187,9 @@ TEST_F(MasterLexerTest, unknownSourceSize) {
// Then the total size is also unknown.
EXPECT_EQ(MasterLexer::SOURCE_SIZE_UNKNOWN, lexer.getTotalSourceSize());
- // If we pop that source, the size becomes known again.
+ // Even if we pop that source, the size is still unknown.
lexer.popSource();
- EXPECT_EQ(4, lexer.getTotalSourceSize());
+ EXPECT_EQ(MasterLexer::SOURCE_SIZE_UNKNOWN, lexer.getTotalSourceSize());
}
TEST_F(MasterLexerTest, invalidPop) {
@@ -317,7 +340,7 @@ TEST_F(MasterLexerTest, includeAndInitialWS) {
lexer.pushSource(ss2);
EXPECT_EQ(MasterToken::INITIAL_WS,
lexer.getNextToken(MasterLexer::INITIAL_WS).getType());
- EXPECT_EQ(2, lexer.getPosition()); // should be sum of position positions.
+ EXPECT_EQ(2, lexer.getPosition()); // should be sum of pushed positions.
}
// Test only one token can be ungotten
diff --git a/src/lib/dns/tests/master_loader_unittest.cc b/src/lib/dns/tests/master_loader_unittest.cc
index 051c662..636f73d 100644
--- a/src/lib/dns/tests/master_loader_unittest.cc
+++ b/src/lib/dns/tests/master_loader_unittest.cc
@@ -153,12 +153,23 @@ TEST_F(MasterLoaderTest, basicLoad) {
RRClass::IN(), MasterLoader::MANY_ERRORS);
EXPECT_FALSE(loader_->loadedSucessfully());
+
+ // The following three should be set to 0 initially in case the loader
+ // is constructed from a file name.
+ EXPECT_EQ(0, loader_->getSize());
+ EXPECT_EQ(0, loader_->getPosition());
+
loader_->load();
EXPECT_TRUE(loader_->loadedSucessfully());
EXPECT_TRUE(errors_.empty());
EXPECT_TRUE(warnings_.empty());
+ // 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());
+
checkBasicRRs();
}
@@ -195,6 +206,47 @@ TEST_F(MasterLoaderTest, include) {
}
}
+TEST_F(MasterLoaderTest, includeAndIncremental) {
+ // Check getSize() and getPosition() are adjusted before and after
+ // $INCLUDE.
+ const string first_rr = "before.example.org. 0 A 192.0.2.1\n";
+ const string include_str = "$INCLUDE " TEST_DATA_SRCDIR "/example.org";
+ const string zone_data = first_rr + include_str + "\n" +
+ "www 3600 IN AAAA 2001:db8::1\n";
+ stringstream ss(zone_data);
+ setLoader(ss, Name("example.org."), RRClass::IN(), MasterLoader::DEFAULT);
+
+ // On construction, getSize() returns the size of the data (exclude the
+ // the file to be included); position is set to 0.
+ EXPECT_EQ(zone_data.size(), loader_->getSize());
+ EXPECT_EQ(0, loader_->getPosition());
+
+ // Read the first RR. getSize() doesn't change; position should be
+ // at the end of the first line.
+ loader_->loadIncremental(1);
+ 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
+ // 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,
+ 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());
+
+ // we were not interested in checking RRs in this test. clear them to
+ // not confuse TearDown().
+ rrsets_.clear();
+}
+
// A commonly used helper to check callback message.
void
checkCallbackMessage(const string& actual_msg, const string& expected_msg,
@@ -260,7 +312,7 @@ TEST_F(MasterLoaderTest, popAfterError) {
const string include_str = "$include " TEST_DATA_SRCDIR
"/broken.zone\nwww 3600 IN AAAA 2001:db8::1\n";
stringstream ss(include_str);
- // We don't test without MANY_ERRORS, we want to see what happens
+ // We perform the test with MANY_ERRORS, we want to see what happens
// after the error.
setLoader(ss, Name("example.org."), RRClass::IN(),
MasterLoader::MANY_ERRORS);
@@ -277,11 +329,19 @@ TEST_F(MasterLoaderTest, popAfterError) {
// Check it works the same when created based on a stream, not filename
TEST_F(MasterLoaderTest, streamConstructor) {
- stringstream zone_stream(prepareZone("", true));
+ const string zone_data(prepareZone("", true));
+ stringstream zone_stream(zone_data);
setLoader(zone_stream, Name("example.org."), RRClass::IN(),
MasterLoader::MANY_ERRORS);
EXPECT_FALSE(loader_->loadedSucessfully());
+
+ // Unlike the basicLoad test, if we construct the loader from a stream
+ // getSize() returns the data size in the stream immediately after the
+ // construction.
+ EXPECT_EQ(zone_data.size(), loader_->getSize());
+ EXPECT_EQ(0, loader_->getPosition());
+
loader_->load();
EXPECT_TRUE(loader_->loadedSucessfully());
@@ -290,6 +350,11 @@ TEST_F(MasterLoaderTest, streamConstructor) {
checkRR("example.org", RRType::SOA(), "ns1.example.org. "
"admin.example.org. 1234 3600 1800 2419200 7200");
checkRR("correct.example.org", RRType::A(), "192.0.2.2");
+
+ // On completion of the load, both getSize() and getPosition() return the
+ // size of the data.
+ EXPECT_EQ(zone_data.size(), loader_->getSize());
+ EXPECT_EQ(zone_data.size(), loader_->getPosition());
}
// Try loading data incrementally.
diff --git a/src/lib/python/isc/Makefile.am b/src/lib/python/isc/Makefile.am
index e072de8..8f5f144 100644
--- a/src/lib/python/isc/Makefile.am
+++ b/src/lib/python/isc/Makefile.am
@@ -1,5 +1,5 @@
SUBDIRS = datasrc cc config dns log net notify util testutils acl bind10
-SUBDIRS += xfrin log_messages server_common ddns sysinfo
+SUBDIRS += xfrin log_messages server_common ddns sysinfo statistics
python_PYTHON = __init__.py
diff --git a/src/lib/python/isc/datasrc/datasrc.cc b/src/lib/python/isc/datasrc/datasrc.cc
index 37c9baa..c183af9 100644
--- a/src/lib/python/isc/datasrc/datasrc.cc
+++ b/src/lib/python/isc/datasrc/datasrc.cc
@@ -21,6 +21,7 @@
#include <datasrc/client.h>
#include <datasrc/database.h>
#include <datasrc/sqlite3_accessor.h>
+#include <datasrc/zone_loader.h>
#include "datasrc.h"
#include "client_python.h"
@@ -34,6 +35,9 @@
#include <util/python/pycppwrapper_util.h>
#include <dns/python/pydnspp_common.h>
+#include <stdexcept>
+#include <string>
+
using namespace isc::datasrc;
using namespace isc::datasrc::python;
using namespace isc::util::python;
@@ -195,22 +199,20 @@ initModulePart_ZoneLoader(PyObject* mod) {
}
Py_INCREF(&zone_loader_type);
- return (true);
-}
-
-bool
-initModulePart_ZoneUpdater(PyObject* mod) {
- // We initialize the static description object with PyType_Ready(),
- // then add it to the module. This is not just a check! (leaving
- // this out results in segmentation faults)
- if (PyType_Ready(&zoneupdater_type) < 0) {
+ try {
+ installClassVariable(zone_loader_type, "PROGRESS_UNKNOWN",
+ Py_BuildValue("d", ZoneLoader::PROGRESS_UNKNOWN));
+ } catch (const std::exception& ex) {
+ const std::string ex_what =
+ "Unexpected failure in ZoneLoader initialization: " +
+ std::string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
return (false);
- }
- void* zip = &zoneupdater_type;
- if (PyModule_AddObject(mod, "ZoneUpdater", static_cast<PyObject*>(zip)) < 0) {
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError,
+ "Unexpected failure in ZoneLoader initialization");
return (false);
}
- Py_INCREF(&zoneupdater_type);
return (true);
}
diff --git a/src/lib/python/isc/datasrc/tests/datasrc_test.py b/src/lib/python/isc/datasrc/tests/datasrc_test.py
index 659e7a8..36cf951 100644
--- a/src/lib/python/isc/datasrc/tests/datasrc_test.py
+++ b/src/lib/python/isc/datasrc/tests/datasrc_test.py
@@ -528,6 +528,41 @@ class DataSrcUpdater(unittest.TestCase):
self.assertEqual("www.example.com. 3600 IN A 192.0.2.1\n",
rrset.to_text())
+ def test_updater_rrset_collection(self):
+ dsc = isc.datasrc.DataSourceClient("sqlite3", WRITE_ZONE_DB_CONFIG)
+ updater = dsc.get_updater(isc.dns.Name("example.com"), False)
+ updater_refs = sys.getrefcount(updater)
+
+ # Get (internally create) updater's RRset collection
+ rrsets = updater.get_rrset_collection()
+
+ # From this point we cannot make further updates
+ rrset = RRset(isc.dns.Name('www.example.com'), isc.dns.RRClass.IN(),
+ isc.dns.RRType.AAAA(), isc.dns.RRTTL(10))
+ rrset.add_rdata(isc.dns.Rdata(isc.dns.RRType.AAAA(),
+ isc.dns.RRClass.IN(), '2001:db8::1'))
+ self.assertRaises(isc.datasrc.Error, updater.add_rrset, rrset)
+
+ # Checks basic API
+ found = rrsets.find(isc.dns.Name("www.example.com"),
+ isc.dns.RRClass.IN(), isc.dns.RRType.A())
+ self.assertEqual("www.example.com. 3600 IN A 192.0.2.1\n",
+ found.to_text())
+ self.assertEqual(None, rrsets.find(isc.dns.Name("www.example.com"),
+ isc.dns.RRClass.IN(),
+ isc.dns.RRType.AAAA()))
+
+ # Once committed collection cannot be used any more.
+ updater.commit()
+ self.assertRaises(isc.dns.RRsetCollectionError,
+ rrsets.find, isc.dns.Name("www.example.com"),
+ isc.dns.RRClass.IN(), isc.dns.RRType.A())
+
+ # When we destroy the RRsetCollection it should release the refcount
+ # to the updater.
+ rrsets = None
+ self.assertEqual(updater_refs, sys.getrefcount(updater))
+
def test_two_modules(self):
# load two modules, and check if they don't interfere
mem_cfg = { "type": "memory", "class": "IN", "zones": [] };
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 cb239ed..62f67cd 100644
--- a/src/lib/python/isc/datasrc/tests/zone_loader_test.py
+++ b/src/lib/python/isc/datasrc/tests/zone_loader_test.py
@@ -38,7 +38,7 @@ ORIG_SOA_TXT = 'example.com. 3600 IN SOA master.example.com. ' +\
'admin.example.com. 1234 3600 1800 2419200 7200\n'
NEW_SOA_TXT = 'example.com. 1000 IN SOA a.dns.example.com. ' +\
'mail.example.com. 1 1 1 1 1\n'
-
+PROGRESS_UNKNOWN = isc.datasrc.ZoneLoader.PROGRESS_UNKNOWN
class ZoneLoaderTests(unittest.TestCase):
def setUp(self):
@@ -111,22 +111,50 @@ class ZoneLoaderTests(unittest.TestCase):
def test_load_from_file(self):
self.loader = isc.datasrc.ZoneLoader(self.client, self.test_name,
self.test_file)
+ self.assertEqual(0, self.loader.get_rr_count())
+ self.assertEqual(0, self.loader.get_progress())
+
self.check_load()
+ # Expected values are hardcoded, taken from the test zone file,
+ # assuming it won't change too often. progress should reach 100% (=1).
+ self.assertEqual(8, self.loader.get_rr_count())
+ self.assertEqual(1, self.loader.get_progress())
+
def test_load_from_client(self):
self.source_client = isc.datasrc.DataSourceClient('sqlite3',
DB_SOURCE_CLIENT_CONFIG)
self.loader = isc.datasrc.ZoneLoader(self.client, self.test_name,
self.source_client)
+
+ self.assertEqual(0, self.loader.get_rr_count())
+ self.assertEqual(PROGRESS_UNKNOWN, self.loader.get_progress())
+
self.check_load()
- def check_load_incremental(self):
+ # In case of loading from another data source, progress is unknown.
+ self.assertEqual(8, self.loader.get_rr_count())
+ self.assertEqual(PROGRESS_UNKNOWN, self.loader.get_progress())
+
+ def check_load_incremental(self, from_file=True):
# New zone has 8 RRs
# After 5, it should return False
self.assertFalse(self.loader.load_incremental(5))
# New zone should not have been loaded yet
self.check_zone_soa(ORIG_SOA_TXT)
+ # In case it's from a zone file, get_progress should be in the middle
+ # of (0, 1). expected value is taken from the test zone file
+ # (total size = 422, current position = 288)
+ if from_file:
+ # To avoid any false positive due to rounding errors, we convert
+ # them to near integers between 0 and 100.
+ self.assertEqual(int((288 * 100) / 422),
+ int(self.loader.get_progress() * 100))
+ # Also check the return value has higher precision.
+ self.assertNotEqual(int(288 * 100 / 422),
+ 100 * self.loader.get_progress())
+
# After 5 more, it should return True (only having read 3)
self.assertTrue(self.loader.load_incremental(5))
# New zone should now be loaded
@@ -147,7 +175,7 @@ class ZoneLoaderTests(unittest.TestCase):
DB_SOURCE_CLIENT_CONFIG)
self.loader = isc.datasrc.ZoneLoader(self.client, self.test_name,
self.source_client)
- self.check_load_incremental()
+ self.check_load_incremental(False)
def test_bad_file(self):
self.check_zone_soa(ORIG_SOA_TXT)
diff --git a/src/lib/python/isc/datasrc/updater_inc.cc b/src/lib/python/isc/datasrc/updater_inc.cc
index 32715ec..f040f85 100644
--- a/src/lib/python/isc/datasrc/updater_inc.cc
+++ b/src/lib/python/isc/datasrc/updater_inc.cc
@@ -76,6 +76,10 @@ This method must not be called once commit() is performed. If it calls\n\
after commit() the implementation must throw a isc.datasrc.Error\n\
exception.\n\
\n\
+Implementations of ZoneUpdater may not allow adding or deleting RRsets\n\
+after get_rrset_collection() is called. In this case, implementations\n\
+throw an InvalidOperation exception.\n\
+\n\
Todo As noted above we may have to revisit the design details as we\n\
gain experiences:\n\
\n\
@@ -133,6 +137,10 @@ This method must not be called once commit() is performed. If it calls\n\
after commit() the implementation must throw a isc.datasrc.Error\n\
exception.\n\
\n\
+Implementations of ZoneUpdater may not allow adding or deleting RRsets\n\
+after get_rrset_collection() is called. In this case, implementations\n\
+throw an InvalidOperation exception.\n\
+\n\
Todo: As noted above we may have to revisit the design details as we\n\
gain experiences:\n\
\n\
@@ -178,4 +186,32 @@ Exceptions:\n\
error, or wrapper error\n\\n\
\n\
";
+
+// Modifications
+// - isc.datasrc.RRsetCollectionBase => isc.dns.RRsetCollectionBase
+// (in the Python wrapper, the former is completely invisible)
+// - remove other reference to isc.datasrc.RRsetCollectionBase
+const char* const ZoneUpdater_getRRsetCollection_doc = "\
+get_rrset_collection() -> isc.dns.RRsetCollectionBase \n\
+\n\
+Return an RRsetCollection for the updater.\n\
+\n\
+This method returns an RRsetCollection for the updater, implementing\n\
+the isc.dns.RRsetCollectionBase interface. Typically, the returned\n\
+RRsetCollection is a singleton for its ZoneUpdater. The returned\n\
+RRsetCollection object must not be used after its corresponding\n\
+ZoneUpdater has been destroyed. The returned RRsetCollection object\n\
+may be used to search RRsets from the ZoneUpdater. The actual\n\
+RRsetCollection returned has a behavior dependent on the ZoneUpdater\n\
+implementation.\n\
+\n\
+The behavior of the RRsetCollection is similar to the behavior of the\n\
+Zonefinder returned by get_finder(). Implementations of ZoneUpdater\n\
+may not allow adding or deleting RRsets after get_rrset_collection()\n\
+is called. Implementations of ZoneUpdater may disable a previously\n\
+returned RRsetCollection after commit() is called. If an\n\
+RRsetCollection is disabled, using methods such as find() and using\n\
+its iterator would cause an exception to be thrown.\n\
+\n\
+";
} // unnamed namespace
diff --git a/src/lib/python/isc/datasrc/updater_python.cc b/src/lib/python/isc/datasrc/updater_python.cc
index 97ffa00..cb727c3 100644
--- a/src/lib/python/isc/datasrc/updater_python.cc
+++ b/src/lib/python/isc/datasrc/updater_python.cc
@@ -32,6 +32,7 @@
#include <dns/python/rrset_python.h>
#include <dns/python/rrclass_python.h>
#include <dns/python/rrtype_python.h>
+#include <dns/python/rrset_collection_python.h>
#include "datasrc.h"
#include "updater_python.h"
@@ -195,6 +196,107 @@ ZoneUpdater_find_all(PyObject* po_self, PyObject* args) {
&self->cppobj->getFinder(), args));
}
+namespace {
+// Below we define Python RRsetCollection class generated by the updater.
+// It's never expected to be instantiated directly from Python code, so
+// everything is hidden here, and tp_init always fails.
+
+class s_UpdaterRRsetCollection : public s_RRsetCollection {
+public:
+ s_UpdaterRRsetCollection() : s_RRsetCollection() {}
+ PyObject* base_obj_;
+};
+
+int
+RRsetCollection_init(PyObject*, PyObject*, PyObject*) {
+ // can't be called directly; actually, the constructor shouldn't even be
+ // called, but we catch the case just in case.
+ PyErr_SetString(PyExc_TypeError,
+ "datasrc.RRsetCollection cannot be constructed directly");
+
+ return (-1);
+}
+
+void
+RRsetCollection_destroy(PyObject* po_self) {
+ s_UpdaterRRsetCollection* const self =
+ static_cast<s_UpdaterRRsetCollection*>(po_self);
+
+ // We don't own the C++ collection object; we shouldn't delete it here.
+
+ // Note: we need to check if this is NULL; it remains NULL in case of
+ // direct instantiation (which fails).
+ if (self->base_obj_ != NULL) {
+ Py_DECREF(self->base_obj_);
+ }
+ Py_TYPE(self)->tp_free(self);
+}
+
+PyTypeObject updater_rrset_collection_type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "datasrc.UpdaterRRsetCollection",
+ sizeof(s_UpdaterRRsetCollection), // tp_basicsize
+ 0, // tp_itemsize
+ RRsetCollection_destroy, // tp_dealloc
+ NULL, // tp_print
+ NULL, // tp_getattr
+ NULL, // tp_setattr
+ NULL, // tp_reserved
+ NULL, // tp_repr
+ NULL, // tp_as_number
+ NULL, // tp_as_sequence
+ NULL, // tp_as_mapping
+ NULL, // tp_hash
+ NULL, // tp_call
+ NULL, // tp_str
+ NULL, // tp_getattro
+ NULL, // tp_setattro
+ NULL, // tp_as_buffer
+ Py_TPFLAGS_DEFAULT, // tp_flags
+ NULL,
+ NULL, // tp_traverse
+ NULL, // tp_clear
+ NULL, // tp_richcompare
+ 0, // tp_weaklistoffset
+ NULL, // tp_iter
+ NULL, // tp_iternext
+ NULL, // tp_methods
+ NULL, // tp_members
+ NULL, // tp_getset
+ NULL, // tp_base (rrset_collection_base_type, set at run time)
+ NULL, // tp_dict
+ NULL, // tp_descr_get
+ NULL, // tp_descr_set
+ 0, // tp_dictoffset
+ RRsetCollection_init, // tp_init
+ NULL, // tp_alloc
+ PyType_GenericNew, // tp_new
+ NULL, // tp_free
+ NULL, // tp_is_gc
+ NULL, // tp_bases
+ NULL, // tp_mro
+ NULL, // tp_cache
+ NULL, // tp_subclasses
+ NULL, // tp_weaklist
+ NULL, // tp_del
+ 0 // tp_version_tag
+};
+} // unnamed namespace
+
+PyObject*
+ZoneUpdater_getRRsetCollection(PyObject* po_self, PyObject*) {
+ s_ZoneUpdater* const self = static_cast<s_ZoneUpdater*>(po_self);
+
+ s_UpdaterRRsetCollection* collection =
+ static_cast<s_UpdaterRRsetCollection*>(
+ PyObject_New(s_RRsetCollection, &updater_rrset_collection_type));
+ collection->cppobj = &self->cppobj->getRRsetCollection();
+ collection->base_obj_ = po_self;;
+ Py_INCREF(collection->base_obj_);
+
+ return (collection);
+}
+
// This list contains the actual set of functions we have in
// python. Each entry has
// 1. Python method name
@@ -207,6 +309,8 @@ PyMethodDef ZoneUpdater_methods[] = {
{ "delete_rrset", ZoneUpdater_deleteRRset,
METH_VARARGS, ZoneUpdater_deleteRRset_doc },
{ "commit", ZoneUpdater_commit, METH_NOARGS, ZoneUpdater_commit_doc },
+ { "get_rrset_collection", ZoneUpdater_getRRsetCollection,
+ METH_NOARGS, ZoneUpdater_getRRsetCollection_doc },
// Instead of a getFinder, we implement the finder functionality directly
// This is because ZoneFinder is non-copyable, and we should not create
// a ZoneFinder object from a reference only (which is what is returned
@@ -292,6 +396,56 @@ createZoneUpdaterObject(isc::datasrc::ZoneUpdaterPtr source,
return (py_zu);
}
+bool
+initModulePart_ZoneUpdater(PyObject* mod) {
+ // We initialize the static description object with PyType_Ready(),
+ // then add it to the module. This is not just a check! (leaving
+ // this out results in segmentation faults)
+ if (PyType_Ready(&zoneupdater_type) < 0) {
+ return (false);
+ }
+ void* zip = &zoneupdater_type;
+ if (PyModule_AddObject(mod, "ZoneUpdater",
+ static_cast<PyObject*>(zip)) < 0)
+ {
+ return (false);
+ }
+ Py_INCREF(&zoneupdater_type);
+
+ // get_rrset_collection() needs the base class type information. Since
+ // it's defined in a separate loadable module, we retrieve its C object
+ // via the Python interpreter. Directly referring to
+ // isc::dns::python::rrset_collection_base_type might work depending on
+ // the runtime environment (and in fact it does for some), but that would
+ // be less portable.
+ try {
+ if (updater_rrset_collection_type.tp_base == NULL) {
+ PyObjectContainer dns_module(PyImport_ImportModule("isc.dns"));
+ PyObjectContainer dns_dict(PyModule_GetDict(dns_module.get()));
+ // GetDict doesn't acquire a reference, so we need to get it to
+ // meet the container's requirement.
+ Py_INCREF(dns_dict.get());
+ PyObjectContainer base(
+ PyDict_GetItemString(dns_dict.get(), "RRsetCollectionBase"));
+ PyTypeObject* pt_rrset_collection_base =
+ static_cast<PyTypeObject*>(static_cast<void*>(base.get()));
+ updater_rrset_collection_type.tp_base = pt_rrset_collection_base;
+ if (PyType_Ready(&updater_rrset_collection_type) < 0) {
+ isc_throw(Unexpected, "failed to import isc.dns module");
+ }
+
+ // Make sure the base type won't suddenly disappear. Note that we
+ // are effectively leaking it; it's intentional.
+ Py_INCREF(base.get());
+ }
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError,
+ "Unexpected failure in ZoneUpdater initialization");
+ return (false);
+ }
+
+ return (true);
+}
} // namespace python
} // namespace datasrc
} // namespace isc
diff --git a/src/lib/python/isc/datasrc/updater_python.h b/src/lib/python/isc/datasrc/updater_python.h
index b09c524..b7f1452 100644
--- a/src/lib/python/isc/datasrc/updater_python.h
+++ b/src/lib/python/isc/datasrc/updater_python.h
@@ -36,7 +36,7 @@ extern PyTypeObject zoneupdater_type;
PyObject* createZoneUpdaterObject(isc::datasrc::ZoneUpdaterPtr source,
PyObject* base_obj = NULL);
-
+bool initModulePart_ZoneUpdater(PyObject* mod);
} // namespace python
} // namespace datasrc
} // namespace isc
diff --git a/src/lib/python/isc/datasrc/zone_loader_inc.cc b/src/lib/python/isc/datasrc/zone_loader_inc.cc
index 405ad1a..ae6fa3c 100644
--- a/src/lib/python/isc/datasrc/zone_loader_inc.cc
+++ b/src/lib/python/isc/datasrc/zone_loader_inc.cc
@@ -113,4 +113,64 @@ on the next call (which will load 0 RRs). This is because the end of\n\
iterator or master file is detected when reading past the end, not\n\
when the last one is read.\n\
";
+
+const char* const ZoneLoader_getRRCount_doc = "\
+get_rr_count() -> integer\n\
+\n\
+Return the number of RRs loaded.\n\
+\n\
+This method returns the number of RRs loaded via this loader by the\n\
+time of the call. Before starting the load it will return 0. It will\n\
+return the total number of RRs of the zone on and after completing the\n\
+load.\n\
+\n\
+Exceptions:\n\
+ None\n\
+\n\
+";
+
+// Modifications
+// - double => float
+const char* const ZoneLoader_getProgress_doc = "\
+get_progress() -> float\n\
+\n\
+Return the current progress of the loader.\n\
+\n\
+This method returns the current estimated progress of loader as a\n\
+value between 0 and 1 (inclusive); it's 0 before starting the load,\n\
+and 1 at the completion, and a value between these (exclusive) in the\n\
+middle of loading. It's an implementation detail how to calculate the\n\
+progress, which may vary depending on how the loader is constructed\n\
+and may even be impossible to detect effectively.\n\
+\n\
+If the progress cannot be determined, this method returns a special\n\
+value of PROGRESS_UNKNOWN, which is not included in the range between\n\
+0 and 1.\n\
+\n\
+As such, the application should use the return value only for\n\
+informational purposes such as logging. For example, it shouldn't be\n\
+used to determine whether loading is completed by comparing it to 1.\n\
+It should also expect the possibility of getting PROGRESS_UNKNOWN at\n\
+any call to this method; it shouldn't assume the specific way of\n\
+internal implementation as described below (which is provided for\n\
+informational purposes only).\n\
+\n\
+In this implementation, if the loader is constructed with a file name,\n\
+the progress value is measured by the number of characters read from\n\
+the zone file divided by the size of the zone file (with taking into\n\
+account any included files). Note that due to the possibility of\n\
+intermediate included files, the total file size cannot be fully fixed\n\
+until the completion of the load. And, due to this possibility, return\n\
+values from this method may not always increase monotonically.\n\
+\n\
+If it's constructed with another data source client, this method\n\
+always returns PROGRESS_UNKNOWN; in future, however, it may become\n\
+possible to return something more useful, e.g, based on the result of\n\
+get_rr_count() and the total number of RRs if the underlying data\n\
+source can provide the latter value efficiently.\n\
+\n\
+Exceptions:\n\
+ None\n\
+\n\
+";
} // unnamed namespace
diff --git a/src/lib/python/isc/datasrc/zone_loader_python.cc b/src/lib/python/isc/datasrc/zone_loader_python.cc
index 98264b3..c1915cc 100644
--- a/src/lib/python/isc/datasrc/zone_loader_python.cc
+++ b/src/lib/python/isc/datasrc/zone_loader_python.cc
@@ -187,6 +187,18 @@ ZoneLoader_loadIncremental(PyObject* po_self, PyObject* args) {
}
}
+PyObject*
+ZoneLoader_getRRCount(PyObject* po_self, PyObject*) {
+ s_ZoneLoader* self = static_cast<s_ZoneLoader*>(po_self);
+ return (Py_BuildValue("I", self->cppobj->getRRCount()));
+}
+
+PyObject*
+ZoneLoader_getProgress(PyObject* po_self, PyObject*) {
+ s_ZoneLoader* self = static_cast<s_ZoneLoader*>(po_self);
+ return (Py_BuildValue("d", self->cppobj->getProgress()));
+}
+
// This list contains the actual set of functions we have in
// python. Each entry has
// 1. Python method name
@@ -197,6 +209,10 @@ PyMethodDef ZoneLoader_methods[] = {
{ "load", ZoneLoader_load, METH_NOARGS, ZoneLoader_load_doc },
{ "load_incremental", ZoneLoader_loadIncremental, METH_VARARGS,
ZoneLoader_loadIncremental_doc },
+ { "get_rr_count", ZoneLoader_getRRCount, METH_NOARGS,
+ ZoneLoader_getRRCount_doc },
+ { "get_progress", ZoneLoader_getProgress, METH_NOARGS,
+ ZoneLoader_getProgress_doc },
{ NULL, NULL, 0, NULL }
};
diff --git a/src/lib/python/isc/statistics/Makefile.am b/src/lib/python/isc/statistics/Makefile.am
new file mode 100644
index 0000000..9be1312
--- /dev/null
+++ b/src/lib/python/isc/statistics/Makefile.am
@@ -0,0 +1,9 @@
+SUBDIRS = . tests
+
+python_PYTHON = __init__.py counters.py
+pythondir = $(pyexecdir)/isc/statistics
+
+CLEANDIRS = __pycache__
+
+clean-local:
+ rm -rf $(CLEANDIRS)
diff --git a/src/lib/python/isc/statistics/__init__.py b/src/lib/python/isc/statistics/__init__.py
new file mode 100644
index 0000000..9e77ed6
--- /dev/null
+++ b/src/lib/python/isc/statistics/__init__.py
@@ -0,0 +1 @@
+from isc.statistics.counters import *
diff --git a/src/lib/python/isc/statistics/counters.py b/src/lib/python/isc/statistics/counters.py
new file mode 100644
index 0000000..99b989d
--- /dev/null
+++ b/src/lib/python/isc/statistics/counters.py
@@ -0,0 +1,403 @@
+# Copyright (C) 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.
+
+"""BIND 10 statistics counters module
+
+This module handles the statistics counters for BIND 10 modules. For
+using the module `counter.py`, first a counters object should be created
+in each module (like b10-xfrin or b10-xfrout) after importing this
+module. A spec file can be specified as an argument when creating the
+counters object:
+
+ from isc.statistics import Counters
+ self.counters = Counters("/path/to/foo.spec")
+
+The first argument of Counters() can be specified, which is the location
+of the specification file (like src/bin/xfrout/xfrout.spec). If Counters
+is constructed this way, statistics counters can be accessed from each
+module. For example, in case that the item `xfrreqdone` is defined in
+statistics_spec in xfrout.spec, the following methods are
+callable. Since these methods require the string of the zone name in the
+first argument, if we have the following code in b10-xfrout:
+
+ self.counters.inc('zones', zone_name, 'xfrreqdone')
+
+then the counter for xfrreqdone corresponding to zone_name is
+incremented. For getting the current number of this counter, we can use
+the following code:
+
+ number = self.counters.get('zones', zone_name, 'xfrreqdone')
+
+then the current count is obtained and set in the variable
+`number`. Such a getter method would be mainly used for unit-testing.
+As other example, for the item `axfr_running`, the decrementer method is
+also callable. This method is used for decrementing a counter. For the
+item `axfr_running`, an argument like zone name is not required:
+
+ self.counters.dec('axfr_running')
+
+These methods are effective in other modules. For example, in case that
+this module `counter.py` is once imported in a main module such as
+b10-xfrout, then for the item `notifyoutv4`, the `inc()` method can be
+invoked in another module such as notify_out.py, which is firstly
+imported in the main module.
+
+ self.counters.inc('zones', zone_name, 'notifyoutv4')
+
+In this example this is for incrementing the counter of the item
+`notifyoutv4`. Thus, such statement can be also written in another
+library like isc.notify.notify_out. If this module `counter.py` isn't
+imported in the main module but imported in such a library module as
+isc.notify.notify_out, in this example, empty methods would be invoked,
+which is directly defined in `counter.py`.
+"""
+
+import threading
+import isc.config
+from datetime import datetime
+
+# static internal functions
+def _add_counter(element, spec, identifier):
+ """Returns value of the identifier if the identifier is in the
+ element. Otherwise, sets a default value from the spec and
+ returns it. If the top-level type of the identifier is named_set
+ and the second-level type is map, it sets a set of default values
+ under the level and then returns the default value of the
+ identifier. This method raises DataNotFoundError if the element is
+ invalid for spec."""
+ try:
+ return isc.cc.data.find(element, identifier)
+ except isc.cc.data.DataNotFoundError:
+ pass
+ # check whether spec and identifier are correct
+ isc.config.find_spec_part(spec, identifier)
+ # examine spec of the top-level item first
+ spec_ = isc.config.find_spec_part(spec, identifier.split('/')[0])
+ if spec_['item_type'] == 'named_set' and \
+ spec_['named_set_item_spec']['item_type'] == 'map':
+ map_spec = spec_['named_set_item_spec']['map_item_spec']
+ for name in isc.config.spec_name_list(map_spec):
+ spec_ = isc.config.find_spec_part(map_spec, name)
+ id_str = '%s/%s/%s' % \
+ tuple(identifier.split('/')[0:2] + [name])
+ isc.cc.data.set(element, id_str, spec_['item_default'])
+ else:
+ spec_ = isc.config.find_spec_part(spec, identifier)
+ isc.cc.data.set(element, identifier, spec_['item_default'])
+ return isc.cc.data.find(element, identifier)
+
+def _set_counter(element, spec, identifier, value):
+ """Invokes _add_counter() for checking whether the identifier is
+ in the element. If not, it creates a new identifier in the element
+ and set the default value from the spec. After that, it sets the
+ value specified in the arguments."""
+ _add_counter(element, spec, identifier)
+ isc.cc.data.set(element, identifier, value)
+
+def _get_counter(element, identifier):
+ """Returns the value of the identifier in the element"""
+ return isc.cc.data.find(element, identifier)
+
+def _inc_counter(element, spec, identifier, step=1):
+ """Increments the value of the identifier in the element to the
+ step from the current value. If the identifier isn't in the
+ element, it creates a new identifier in the element."""
+ isc.cc.data.set(element, identifier,
+ _add_counter(element, spec, identifier) + step)
+
+def _start_timer():
+ """Returns the current datetime as a datetime object."""
+ return datetime.now()
+
+def _stop_timer(start_time, element, spec, identifier):
+ """Sets duration time in seconds as a value of the identifier in
+ the element, which is in seconds between start_time and the
+ current time and is float-type."""
+ delta = datetime.now() - start_time
+ # FIXME: The following statement can be replaced by:
+ # sec = delta.total_seconds()
+ # but total_seconds() is not available in Python 3.1. Please update
+ # this code when we depend on Python 3.2.
+ sec = round(delta.days * 86400 + delta.seconds + \
+ delta.microseconds * 1E-6, 6)
+ _set_counter(element, spec, identifier, sec)
+
+def _concat(*args, sep='/'):
+ """A helper function that is used to generate an identifier for
+ statistics item names. It concatenates words in args with a
+ separator('/')
+ """
+ return sep.join(args)
+
+class _Statistics():
+ """Statistics data set"""
+ # default statistics data
+ _data = {}
+ # default statistics spec used in case the specfile is omitted when
+ # constructing a Counters() object
+ _spec = [
+ {
+ "item_name": "zones",
+ "item_type": "named_set",
+ "item_optional": False,
+ "item_default": {
+ "_SERVER_" : {
+ "notifyoutv4" : 0,
+ "notifyoutv6" : 0
+ }
+ },
+ "item_title": "Zone names",
+ "item_description": "Zone names",
+ "named_set_item_spec": {
+ "item_name": "zonename",
+ "item_type": "map",
+ "item_optional": False,
+ "item_default": {},
+ "item_title": "Zone name",
+ "item_description": "Zone name",
+ "map_item_spec": [
+ {
+ "item_name": "notifyoutv4",
+ "item_type": "integer",
+ "item_optional": False,
+ "item_default": 0,
+ "item_title": "IPv4 notifies",
+ "item_description": "Number of IPv4 notifies per zone name sent out"
+ },
+ {
+ "item_name": "notifyoutv6",
+ "item_type": "integer",
+ "item_optional": False,
+ "item_default": 0,
+ "item_title": "IPv6 notifies",
+ "item_description": "Number of IPv6 notifies per zone name sent out"
+ }
+ ]
+ }
+ }
+ ]
+
+class Counters():
+ """A class for holding and manipulating all statistics counters
+ for a module. A Counters object may be created by specifying a spec
+ file of the module in argument. According to statistics
+ specification in the spec file, a counter value can be incremented,
+ decremented or obtained. Methods such as inc(), dec() and get() are
+ useful for this. Counters objects also have timer functionality.
+ The timer can be started and stopped, and the duration between
+ start and stop can be obtained. Methods such as start_timer(),
+ stop_timer() and get() are useful for this. Saved counters can be
+ cleared by the method clear_all(). Manipulating counters and
+ timers can be temporarily disabled. If disabled, counter values are
+ not changed even if methods to update them are invoked. Including
+ per-zone counters, a list of counters which can be handled in the
+ class are like the following:
+
+ zones/example.com./notifyoutv4
+ zones/example.com./notifyoutv6
+ zones/example.com./xfrrej
+ zones/example.com./xfrreqdone
+ zones/example.com./soaoutv4
+ zones/example.com./soaoutv6
+ zones/example.com./axfrreqv4
+ zones/example.com./axfrreqv6
+ zones/example.com./ixfrreqv4
+ zones/example.com./ixfrreqv6
+ zones/example.com./xfrsuccess
+ zones/example.com./xfrfail
+ zones/example.com./time_to_ixfr
+ zones/example.com./time_to_axfr
+ ixfr_running
+ axfr_running
+ socket/unixdomain/open
+ socket/unixdomain/openfail
+ socket/unixdomain/close
+ socket/unixdomain/bindfail
+ socket/unixdomain/acceptfail
+ socket/unixdomain/accept
+ socket/unixdomain/senderr
+ socket/unixdomain/recverr
+ socket/ipv4/tcp/open
+ socket/ipv4/tcp/openfail
+ socket/ipv4/tcp/close
+ socket/ipv4/tcp/connfail
+ socket/ipv4/tcp/conn
+ socket/ipv4/tcp/senderr
+ socket/ipv4/tcp/recverr
+ socket/ipv6/tcp/open
+ socket/ipv6/tcp/openfail
+ socket/ipv6/tcp/close
+ socket/ipv6/tcp/connfail
+ socket/ipv6/tcp/conn
+ socket/ipv6/tcp/senderr
+ socket/ipv6/tcp/recverr
+ """
+
+ # '_SERVER_' is a special zone name representing an entire
+ # count. It doesn't mean a specific zone, but it means an
+ # entire count in the server.
+ _entire_server = '_SERVER_'
+ # zone names are contained under this dirname in the spec file.
+ _perzone_prefix = 'zones'
+ # default statistics data set
+ _statistics = _Statistics()
+
+ def __init__(self, spec_file_name=None):
+ """A constructor for the Counters class. A path to the spec file
+ can be specified in spec_file_name. Statistics data based on
+ statistics spec can be accumulated if spec_file_name is
+ specified. If omitted, a default statistics spec is used. The
+ default statistics spec is defined in a hidden class named
+ _Statistics().
+ """
+ self._zones_item_list = []
+ self._start_time = {}
+ self._disabled = False
+ self._rlock = threading.RLock()
+ if not spec_file_name: return
+ # change the default statistics spec
+ self._statistics._spec = \
+ isc.config.module_spec_from_file(spec_file_name).\
+ get_statistics_spec()
+ if self._perzone_prefix in \
+ isc.config.spec_name_list(self._statistics._spec):
+ self._zones_item_list = isc.config.spec_name_list(
+ isc.config.find_spec_part(
+ self._statistics._spec, self._perzone_prefix)\
+ ['named_set_item_spec']['map_item_spec'])
+
+ def clear_all(self):
+ """clears all statistics data"""
+ with self._rlock:
+ self._statistics._data = {}
+
+ def disable(self):
+ """disables incrementing/decrementing counters"""
+ with self._rlock:
+ self._disabled = True
+
+ def enable(self):
+ """enables incrementing/decrementing counters"""
+ with self._rlock:
+ self._disabled = False
+
+ def _incdec(self, *args, step=1):
+ """A common helper function for incrementing or decrementing a
+ counter. It acquires a lock to support multi-threaded
+ use. isc.cc.data.DataNotFoundError is raised when incrementing
+ the counter of the item undefined in the spec file."""
+ identifier = _concat(*args)
+ with self._rlock:
+ if self._disabled: return
+ _inc_counter(self._statistics._data,
+ self._statistics._spec,
+ identifier, step)
+
+ def inc(self, *args):
+ """An incrementer for a counter. It acquires a lock to support
+ multi-threaded use. isc.cc.data.DataNotFoundError is raised when
+ incrementing the counter of the item undefined in the spec file."""
+ return self._incdec(*args)
+
+ def dec(self, *args):
+ """A decrementer for a counter. It acquires a lock to support
+ multi-threaded use. isc.cc.data.DataNotFoundError is raised when
+ decrementing the counter of the item undefined in the spec file."""
+ return self._incdec(*args, step=-1)
+
+ def get(self, *args):
+ """A getter method for counters. It returns the current number
+ of the specified counter. isc.cc.data.DataNotFoundError is
+ raised when the counter doesn't have a number yet."""
+ identifier = _concat(*args)
+ return _get_counter(self._statistics._data, identifier)
+
+ 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."""
+ identifier = _concat(*args)
+ with self._rlock:
+ if self._disabled: return
+ isc.cc.data.set(self._start_time, identifier, _start_timer())
+
+ def stop_timer(self, *args):
+ """Stops a timer which is identified by args. It acquires a lock
+ to support multi-threaded use. If the timer isn't started by
+ start_timer() yet, it raises no exception. However if args
+ aren't defined in the spec file, it raises DataNotFoundError.
+ """
+ identifier = _concat(*args)
+ with self._rlock:
+ if self._disabled: return
+ try:
+ start_time = isc.cc.data.find(self._start_time,
+ identifier)
+ except isc.cc.data.DataNotFoundError:
+ # do not set the end time if the timer isn't started
+ return
+ # set the end time
+ _stop_timer(
+ start_time,
+ self._statistics._data,
+ self._statistics._spec,
+ identifier)
+ # A datetime value of once used timer should be deleted
+ # for a future use.
+ # Here, names of branch and leaf are obtained from a
+ # string of identifier. The branch name is equivalent to
+ # the position of datetime to be deleted and the leaf name
+ # is equivalent to the value of datetime to be deleted.
+ (branch, leaf) = identifier.rsplit('/', 1)
+ # Then map of branch is obtained from self._start_time by
+ # using isc.cc.data.find().
+ branch_map = isc.cc.data.find(self._start_time, branch)
+ # Finally a value of the leaf name is deleted from the
+ # map.
+ del branch_map[leaf]
+
+ def get_statistics(self):
+ """Calculates an entire server's counts, and returns statistics
+ data in a format to send out to the stats module, including each
+ counter. If nothing is counted yet, then it returns an empty
+ dictionary."""
+ # entire copy
+ statistics_data = self._statistics._data.copy()
+ # If there is no 'zones' found in statistics_data,
+ # i.e. statistics_data contains no per-zone counter, it just
+ # returns statistics_data because calculating total counts
+ # across the zone names isn't necessary.
+ if self._perzone_prefix not in statistics_data:
+ return statistics_data
+ zones = statistics_data[self._perzone_prefix]
+ # Start calculation for '_SERVER_' counts
+ zones_spec = isc.config.find_spec_part(self._statistics._spec,
+ self._perzone_prefix)
+ zones_attrs = zones_spec['item_default'][self._entire_server]
+ zones_data = {}
+ for attr in zones_attrs:
+ id_str = '%s/%s' % (self._entire_server, attr)
+ sum_ = 0
+ for name in zones:
+ if attr in zones[name]:
+ sum_ += zones[name][attr]
+ if sum_ > 0:
+ _set_counter(zones_data, zones_spec,
+ id_str, sum_)
+ # insert entire-server counts
+ statistics_data[self._perzone_prefix] = dict(
+ statistics_data[self._perzone_prefix],
+ **zones_data)
+ return statistics_data
diff --git a/src/lib/python/isc/statistics/tests/Makefile.am b/src/lib/python/isc/statistics/tests/Makefile.am
new file mode 100644
index 0000000..8dcc296
--- /dev/null
+++ b/src/lib/python/isc/statistics/tests/Makefile.am
@@ -0,0 +1,33 @@
+PYCOVERAGE_RUN=@PYCOVERAGE_RUN@
+PYTESTS = counters_test.py
+EXTRA_DIST = $(PYTESTS)
+EXTRA_DIST += testdata/test_spec1.spec
+EXTRA_DIST += testdata/test_spec2.spec
+EXTRA_DIST += testdata/test_spec3.spec
+
+# 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)
+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
+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
+ rm -f .coverage
+ ${LN_S} $(abs_top_srcdir)/.coverage .coverage
+endif
+ for pytest in $(PYTESTS) ; do \
+ echo Running test: $$pytest ; \
+ PYTHONPATH=$(COMMON_PYTHON_PATH) \
+ $(LIBRARY_PATH_PLACEHOLDER) \
+ B10_FROM_BUILD=$(abs_top_builddir) \
+ B10_FROM_SOURCE=$(abs_top_srcdir) \
+ TESTDATASRCDIR=$(abs_top_srcdir)/src/lib/python/isc/statistics/tests/testdata \
+ $(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
+ done
diff --git a/src/lib/python/isc/statistics/tests/counters_test.py b/src/lib/python/isc/statistics/tests/counters_test.py
new file mode 100644
index 0000000..ff15efc
--- /dev/null
+++ b/src/lib/python/isc/statistics/tests/counters_test.py
@@ -0,0 +1,416 @@
+# Copyright (C) 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.
+
+'''Tests for isc.statistics.counter'''
+
+import unittest
+import threading
+from datetime import timedelta
+import os
+import imp
+import isc.config
+
+TEST_ZONE_NAME_STR = "example.com."
+TESTDATA_SRCDIR = os.getenv("TESTDATASRCDIR")
+
+from isc.statistics import counters
+
+def setup_functor(event, cycle, functor, *args):
+ """Waits until the event is started, and then invokes the functor
+ by times of the cycle with args."""
+ event.wait()
+ for i in range(cycle): functor(*args)
+
+def start_functor(concurrency, number, functor, *args):
+ """Creates the threads of the number and makes them start. Sets
+ the event and waits until these threads are finished."""
+ threads = []
+ event = threading.Event()
+ for i in range(concurrency):
+ threads.append(threading.Thread(\
+ target=setup_functor, \
+ args=(event, number, functor,) + args))
+ for th in threads: th.start()
+ event.set()
+ for th in threads: th.join()
+
+class TestBasicMethods(unittest.TestCase):
+ TEST_SPECFILE_LOCATION = TESTDATA_SRCDIR + os.sep + 'test_spec1.spec'
+
+ def setUp(self):
+ imp.reload(counters)
+ self.counters = counters.Counters(self.TEST_SPECFILE_LOCATION)
+
+ def tearDown(self):
+ self.counters.clear_all()
+
+ def test_clear_counters(self):
+ self.assertRaises(isc.cc.data.DataNotFoundError,
+ self.counters.get, 'counter')
+ self.counters.inc('counter')
+ self.assertEqual(self.counters.get('counter'), 1)
+ self.counters.clear_all()
+ self.assertRaises(isc.cc.data.DataNotFoundError,
+ self.counters.get, 'counter')
+
+ def test_enablediable(self):
+ self.assertFalse(self.counters._disabled)
+ self.counters.disable()
+ self.assertTrue(self.counters._disabled)
+ self.counters.enable()
+ self.assertFalse(self.counters._disabled)
+
+ def test_add_counter_normal(self):
+ element = {'counter' : 1}
+ self.assertEqual(\
+ counters._add_counter(element, [], 'counter'), 1)
+
+ def test_add_counter_wrongspec(self):
+ self.assertRaises(isc.cc.data.DataNotFoundError,
+ counters._add_counter,
+ {}, [], 'counter')
+
+ def test_add_counter_empty(self):
+ self.assertEqual(\
+ counters._add_counter(
+ {},
+ [ { 'item_name' : 'counter',
+ 'item_type' : 'integer',
+ 'item_default' : 0 } ],
+ 'counter'), 0)
+
+ def test_add_counter_empty_namedset(self):
+ elem = {}
+ spec = [ { 'item_name': 'dirs',
+ 'item_type': 'named_set',
+ 'named_set_item_spec': {
+ 'item_name': 'dir',
+ 'item_type': 'map',
+ 'map_item_spec': [
+ { 'item_name': 'counter1',
+ 'item_type': 'integer',
+ 'item_default': 0 },
+ { 'item_name': 'counter2',
+ 'item_type': 'integer',
+ 'item_default': 0 } ]}
+ }]
+ self.assertEqual(\
+ counters._add_counter(elem, spec, 'dirs/foo/counter1'), 0)
+ self.assertEqual(\
+ counters._add_counter(elem, spec, 'dirs/bar/counter2'), 0)
+
+ def test_timer(self):
+ t1 = counters._start_timer()
+ t2 = t1 - timedelta(seconds=1)
+ self.assertEqual((t1 - t2).seconds, 1)
+ elem = {}
+ spec = [{ 'item_name': 'time',
+ 'item_type': 'real',
+ 'item_default': 0.0 }]
+ counters._stop_timer(t2, elem, spec, 'time')
+ self.assertGreater(counters._get_counter(elem,'time'), 1)
+
+ def test_rasing_incrementers(self):
+ """ use Thread"""
+ concurrency = 3 # number of the threads
+ number = 10000 # number of counting per thread
+ counter_name = "counter"
+ timer_name = "seconds"
+ start_time = counters._start_timer()
+ start_functor(concurrency, number, self.counters.inc,
+ counter_name)
+ counters._stop_timer(start_time,
+ self.counters._statistics._data,
+ self.counters._statistics._spec,
+ timer_name)
+ self.assertEqual(
+ counters._get_counter(self.counters._statistics._data,
+ counter_name),
+ concurrency * number)
+ self.assertGreater(
+ counters._get_counter(self.counters._statistics._data,
+ timer_name), 0)
+
+ def test_concat(self):
+ # only strings
+ a = ( 'a','b','c','d' )
+ self.assertEqual('a/b/c/d', counters._concat(*a))
+ self.assertEqual('aTbTcTd', counters._concat(*a, sep='T'))
+ self.assertEqual('a\\b\\c\\d', counters._concat(*a, sep='\\'))
+ # mixed with other types
+ b = a + (1,)
+ self.assertRaises(TypeError, counters._concat, *b)
+ b = a + (1.1,)
+ self.assertRaises(TypeError, counters._concat, *b)
+ b = a + ([],)
+ self.assertRaises(TypeError, counters._concat, *b)
+ b = a + ({},)
+ self.assertRaises(TypeError, counters._concat, *b)
+
+class BaseTestCounters():
+
+ def setUp(self):
+ imp.reload(counters)
+ self._statistics_data = {}
+ self.counters = counters.Counters(self.TEST_SPECFILE_LOCATION)
+ self._entire_server = self.counters._entire_server
+ self._perzone_prefix = self.counters._perzone_prefix
+
+ def tearDown(self):
+ self.counters.clear_all()
+
+ def check_get_statistics(self):
+ """Checks no differences between the value returned from
+ get_statistics() and locally collected statistics data. Also
+ checks the result isn't changed even after the method is
+ invoked twice. Finally checks it is valid for the the
+ statistics spec."""
+ self.assertEqual(self.counters.get_statistics(),
+ self._statistics_data)
+ # Idempotency check
+ self.assertEqual(self.counters.get_statistics(),
+ self._statistics_data)
+ if self.TEST_SPECFILE_LOCATION:
+ self.assertTrue(isc.config.module_spec_from_file(
+ self.TEST_SPECFILE_LOCATION).validate_statistics(
+ False, self._statistics_data))
+ else:
+ self.assertTrue(isc.config.ModuleSpec(
+ {'module_name': 'Foo',
+ 'statistics': self.counters._statistics._spec}
+ ).validate_statistics(
+ False, self._statistics_data))
+
+ def test_perzone_counters(self):
+ # for per-zone counters
+ for name in self.counters._zones_item_list:
+ args = (self._perzone_prefix, TEST_ZONE_NAME_STR, name)
+ if name.find('time_to_') == 0:
+ self.counters.start_timer(*args)
+ self.counters.stop_timer(*args)
+ self.assertGreater(self.counters.get(*args), 0)
+ sec = self.counters.get(*args)
+ for zone_str in (self._entire_server, TEST_ZONE_NAME_STR):
+ isc.cc.data.set(self._statistics_data,
+ '%s/%s/%s' % (args[0], zone_str, name), sec)
+ # twice exec stopper, then second is not changed
+ self.counters.stop_timer(*args)
+ self.assertEqual(self.counters.get(*args), sec)
+ else:
+ self.counters.inc(*args)
+ self.assertEqual(self.counters.get(*args), 1)
+ # checks disable/enable
+ self.counters.disable()
+ self.counters.inc(*args)
+ self.assertEqual(self.counters.get(*args), 1)
+ self.counters.enable()
+ self.counters.inc(*args)
+ self.assertEqual(self.counters.get(*args), 2)
+ for zone_str in (self._entire_server, TEST_ZONE_NAME_STR):
+ isc.cc.data.set(self._statistics_data,
+ '%s/%s/%s' % (args[0], zone_str, name), 2)
+ self.check_get_statistics()
+
+ def test_xfrrunning_counters(self):
+ # for counters of xfer running
+ _suffix = 'xfr_running'
+ _xfrrunning_names = \
+ isc.config.spec_name_list(self.counters._statistics._spec,
+ "", True)
+ for name in _xfrrunning_names:
+ if name.find(_suffix) != 1: continue
+ args = name.split('/')
+ self.counters.inc(*args)
+ self.assertEqual(self.counters.get(*args), 1)
+ self.counters.dec(*args)
+ self.assertEqual(self.counters.get(*args), 0)
+ # checks disable/enable
+ self.counters.disable()
+ self.counters.inc(*args)
+ self.assertEqual(self.counters.get(*args), 0)
+ self.counters.enable()
+ self.counters.inc(*args)
+ self.assertEqual(self.counters.get(*args), 1)
+ self.counters.disable()
+ self.counters.dec(*args)
+ self.assertEqual(self.counters.get(*args), 1)
+ self.counters.enable()
+ self.counters.dec(*args)
+ self.assertEqual(self.counters.get(*args), 0)
+ self._statistics_data[name] = 0
+ self.check_get_statistics()
+
+ def test_socket_counters(self):
+ # for ipsocket/unixsocket counters
+ _prefix = 'socket/'
+ _socket_names = \
+ isc.config.spec_name_list(self.counters._statistics._spec,
+ "", True)
+ for name in _socket_names:
+ if name.find(_prefix) != 0: continue
+ args = name.split('/')
+ self.counters.inc(*args)
+ self.assertEqual(self.counters.get(*args), 1)
+ # checks disable/enable
+ self.counters.disable()
+ self.counters.inc(*args)
+ self.assertEqual(self.counters.get(*args), 1)
+ self.counters.enable()
+ self.counters.inc(*args)
+ self.assertEqual(self.counters.get(*args), 2)
+ isc.cc.data.set(
+ self._statistics_data, '/'.join(args), 2)
+ self.check_get_statistics()
+
+ def test_undefined_item(self):
+ # test DataNotFoundError raising when specifying item defined
+ # in the specfile
+ self.assertRaises(isc.cc.data.DataNotFoundError,
+ self.counters.inc, '__undefined__')
+ self.assertRaises(isc.cc.data.DataNotFoundError,
+ self.counters.dec, '__undefined__')
+ self.counters.start_timer('__undefined__')
+ self.assertRaises(isc.cc.data.DataNotFoundError,
+ self.counters.stop_timer, '__undefined__')
+ self.assertRaises(isc.cc.data.DataNotFoundError,
+ self.counters.get, '__undefined__')
+
+class TestCounters0(unittest.TestCase, BaseTestCounters):
+ TEST_SPECFILE_LOCATION = None
+ def setUp(self):
+ BaseTestCounters.setUp(self)
+ def tearDown(self):
+ BaseTestCounters.tearDown(self)
+
+class TestCounters1(unittest.TestCase, BaseTestCounters):
+ TEST_SPECFILE_LOCATION = TESTDATA_SRCDIR + os.sep + 'test_spec1.spec'
+ def setUp(self):
+ BaseTestCounters.setUp(self)
+ def tearDown(self):
+ BaseTestCounters.tearDown(self)
+
+class TestCounters2(unittest.TestCase, BaseTestCounters):
+ TEST_SPECFILE_LOCATION = TESTDATA_SRCDIR + os.sep + 'test_spec2.spec'
+ def setUp(self):
+ BaseTestCounters.setUp(self)
+ def tearDown(self):
+ BaseTestCounters.tearDown(self)
+
+class TestCounters3(unittest.TestCase, BaseTestCounters):
+ TEST_SPECFILE_LOCATION = TESTDATA_SRCDIR + os.sep + 'test_spec3.spec'
+ @classmethod
+ def setUpClass(cls):
+ imp.reload(counters)
+ def setUp(self):
+ BaseTestCounters.setUp(self)
+ def tearDown(self):
+ BaseTestCounters.tearDown(self)
+
+class BaseDummyModule():
+ """A base dummy class"""
+ TEST_SPECFILE_LOCATION = TESTDATA_SRCDIR + os.sep + 'test_spec2.spec'
+ def __init__(self):
+ self.counters = counters.Counters(self.TEST_SPECFILE_LOCATION)
+
+ def get_counters(self):
+ return self.counters.get_statistics()
+
+ def clear_counters(self):
+ self.counters.clear_all()
+
+class DummyNotifyOut(BaseDummyModule):
+ """A dummy class equivalent to notify.notify_out.NotifyOut"""
+ def __init__(self):
+ self.counters = counters.Counters()
+
+ def inc_counters(self):
+ """increments counters"""
+ self.counters.inc('zones', TEST_ZONE_NAME_STR, 'notifyoutv4')
+ self.counters.inc('zones', TEST_ZONE_NAME_STR, 'notifyoutv6')
+
+class DummyXfroutSession(BaseDummyModule):
+ """A dummy class equivalent to XfroutSession in b10-xfrout"""
+ def inc_counters(self):
+ """increments counters"""
+ self.counters.inc('zones', TEST_ZONE_NAME_STR, 'xfrreqdone')
+ self.counters.inc('zones', TEST_ZONE_NAME_STR, 'xfrrej')
+ self.counters.inc('axfr_running')
+ self.counters.inc('ixfr_running')
+ self.counters.dec('axfr_running')
+ self.counters.dec('ixfr_running')
+
+class DummyUnixSockServer(BaseDummyModule):
+ """A dummy class equivalent to UnixSockServer in b10-xfrout"""
+ def inc_counters(self):
+ """increments counters"""
+ self.counters.inc('socket', 'unixdomain', 'open')
+ self.counters.inc('socket', 'unixdomain', 'close')
+
+class DummyXfroutServer(BaseDummyModule):
+ """A dummy class equivalent to XfroutServer in b10-xfrout"""
+ def __init__(self):
+ super().__init__()
+ self.xfrout_sess = DummyXfroutSession()
+ self.unix_socket_server = DummyUnixSockServer()
+ self.notifier = DummyNotifyOut()
+
+ def inc_counters(self):
+ self.xfrout_sess.inc_counters()
+ self.unix_socket_server.inc_counters()
+ self.notifier.inc_counters()
+
+class TestDummyNotifyOut(unittest.TestCase):
+ """Tests counters are incremented in which the spec file is not
+ loaded"""
+ def setUp(self):
+ imp.reload(counters)
+ self.notifier = DummyNotifyOut()
+ self.notifier.inc_counters()
+
+ def tearDown(self):
+ self.notifier.clear_counters()
+
+ def test_counters(self):
+ self.assertEqual(
+ {'zones': {'_SERVER_': {'notifyoutv4': 1, 'notifyoutv6': 1},
+ TEST_ZONE_NAME_STR: {'notifyoutv4': 1, 'notifyoutv6': 1}}},
+ self.notifier.get_counters())
+
+class TestDummyXfroutServer(unittest.TestCase):
+ """Tests counters are incremented or decremented in which the same
+ spec file is multiply loaded in each child class"""
+ def setUp(self):
+ imp.reload(counters)
+ self.xfrout_server = DummyXfroutServer()
+ self.xfrout_server.inc_counters()
+
+ def tearDown(self):
+ self.xfrout_server.clear_counters()
+
+ def test_counters(self):
+ self.assertEqual(
+ {'axfr_running': 0, 'ixfr_running': 0,
+ 'socket': {'unixdomain': {'open': 1, 'close': 1}},
+ 'zones': {'_SERVER_': {'notifyoutv4': 1,
+ 'notifyoutv6': 1,
+ 'xfrrej': 1, 'xfrreqdone': 1},
+ TEST_ZONE_NAME_STR: {'notifyoutv4': 1,
+ 'notifyoutv6': 1,
+ 'xfrrej': 1,
+ 'xfrreqdone': 1}}},
+ self.xfrout_server.get_counters())
+
+if __name__== "__main__":
+ unittest.main()
diff --git a/src/lib/python/isc/statistics/tests/testdata/test_spec1.spec b/src/lib/python/isc/statistics/tests/testdata/test_spec1.spec
new file mode 100644
index 0000000..dbe7c6c
--- /dev/null
+++ b/src/lib/python/isc/statistics/tests/testdata/test_spec1.spec
@@ -0,0 +1,26 @@
+{
+ "module_spec": {
+ "module_name": "Simple",
+ "config_data": [],
+ "commands": [],
+ "statistics": [
+ {
+ "item_name": "counter",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_title": "Counter",
+ "item_description": "Counter",
+ "item_default": 0
+ },
+ {
+ "item_name": "seconds",
+ "item_type": "real",
+ "item_optional": false,
+ "item_title": "Seconds",
+ "item_description": "Seconds",
+ "item_default": 0.0
+ }
+ ]
+ }
+}
+
diff --git a/src/lib/python/isc/statistics/tests/testdata/test_spec2.spec b/src/lib/python/isc/statistics/tests/testdata/test_spec2.spec
new file mode 100644
index 0000000..11b8706
--- /dev/null
+++ b/src/lib/python/isc/statistics/tests/testdata/test_spec2.spec
@@ -0,0 +1,187 @@
+{
+ "module_spec": {
+ "module_name": "XfroutLike",
+ "config_data": [],
+ "commands": [],
+ "statistics": [
+ {
+ "item_name": "zones",
+ "item_type": "named_set",
+ "item_optional": false,
+ "item_default": {
+ "_SERVER_" : {
+ "notifyoutv4" : 0,
+ "notifyoutv6" : 0,
+ "xfrrej" : 0,
+ "xfrreqdone" : 0
+ }
+ },
+ "item_title": "Zone names",
+ "item_description": "Zone names for Xfrout statistics",
+ "named_set_item_spec": {
+ "item_name": "zonename",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "item_title": "Zone name",
+ "item_description": "Zone name for Xfrout statistics",
+ "map_item_spec": [
+ {
+ "item_name": "notifyoutv4",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "IPv4 notifies",
+ "item_description": "Number of IPv4 notifies per zone name sent out from Xfrout"
+ },
+ {
+ "item_name": "notifyoutv6",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "IPv6 notifies",
+ "item_description": "Number of IPv6 notifies per zone name sent out from Xfrout"
+ },
+ {
+ "item_name": "xfrrej",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "XFR rejected requests",
+ "item_description": "Number of XFR requests per zone name rejected by Xfrout"
+ },
+ {
+ "item_name": "xfrreqdone",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "Requested zone transfers",
+ "item_description": "Number of requested zone transfers completed per zone name"
+ }
+ ]
+ }
+ },
+ {
+ "item_name": "ixfr_running",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "IXFR running",
+ "item_description": "Number of IXFRs in progress"
+ },
+ {
+ "item_name": "axfr_running",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "AXFR running",
+ "item_description": "Number of AXFRs in progress"
+ },
+ {
+ "item_name": "socket",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {
+ "unixdomain": {
+ "open": 0,
+ "openfail": 0,
+ "close": 0,
+ "bindfail": 0,
+ "acceptfail": 0,
+ "accept": 0,
+ "senderr": 0,
+ "recverr": 0
+ }
+ },
+ "item_title": "Socket",
+ "item_description": "Socket",
+ "map_item_spec": [
+ {
+ "item_name": "unixdomain",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {
+ "open": 0,
+ "openfail": 0,
+ "close": 0,
+ "bindfail": 0,
+ "acceptfail": 0,
+ "accept": 0,
+ "senderr": 0,
+ "recverr": 0
+ },
+ "item_title": "Unix Domain",
+ "item_description": "Unix Domain",
+ "map_item_spec": [
+ {
+ "item_name": "open",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "Open",
+ "item_description": "Unix Domain sockets opened successfully"
+ },
+ {
+ "item_name": "openfail",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "Open failures",
+ "item_description": "Unix Domain sockets open failures"
+ },
+ {
+ "item_name": "close",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "Close",
+ "item_description": "Unix Domain sockets closed"
+ },
+ {
+ "item_name": "bindfail",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "Bind failures",
+ "item_description": "Unix Domain sockets bind failures"
+ },
+ {
+ "item_name": "acceptfail",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "Accept failures",
+ "item_description": "Unix Domain sockets incoming accept failures"
+ },
+ {
+ "item_name": "accept",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "Accept",
+ "item_description": "Unix Domain sockets incoming connections successfully accepted"
+ },
+ {
+ "item_name": "senderr",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "Send errors",
+ "item_description": "Unix Domain sockets send errors"
+ },
+ {
+ "item_name": "recverr",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "Receive errors",
+ "item_description": "Unix Domain sockets receive errors"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+}
+
diff --git a/src/lib/python/isc/statistics/tests/testdata/test_spec3.spec b/src/lib/python/isc/statistics/tests/testdata/test_spec3.spec
new file mode 100644
index 0000000..c97a09a
--- /dev/null
+++ b/src/lib/python/isc/statistics/tests/testdata/test_spec3.spec
@@ -0,0 +1,382 @@
+{
+ "module_spec": {
+ "module_name": "XfrinLike",
+ "module_description": "XFR in daemon",
+ "config_data": [],
+ "commands": [],
+ "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,
+ "time_to_ixfr": 0.0,
+ "time_to_axfr": 0.0
+ }
+ },
+ "item_title": "Zone names",
+ "item_description": "Zone names for Xfrout statistics",
+ "named_set_item_spec": {
+ "item_name": "zonename",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "item_title": "Zone name",
+ "item_description": "Zone name for Xfrout statistics",
+ "map_item_spec": [
+ {
+ "item_name": "soaoutv4",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "SOAOutv4",
+ "item_description": "Number of IPv4 SOA queries sent from Xfrin"
+ },
+ {
+ "item_name": "soaoutv6",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "SOAOutv6",
+ "item_description": "Number of IPv6 SOA queries sent from Xfrin"
+ },
+ {
+ "item_name": "axfrreqv4",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "AXFRReqv4",
+ "item_description": "Number of IPv4 AXFR requests sent from Xfrin"
+ },
+ {
+ "item_name": "axfrreqv6",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "AXFRReqv6",
+ "item_description": "Number of IPv6 AXFR requests sent from Xfrin"
+ },
+ {
+ "item_name": "ixfrreqv4",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "IXFRReqv4",
+ "item_description": "Number of IPv4 IXFR requests sent from Xfrin"
+ },
+ {
+ "item_name": "ixfrreqv6",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "IXFRReqv6",
+ "item_description": "Number of IPv6 IXFR requests sent from Xfrin"
+ },
+ {
+ "item_name": "xfrsuccess",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "XfrSuccess",
+ "item_description": "Number of zone transfer requests succeeded"
+ },
+ {
+ "item_name": "xfrfail",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "XfrFail",
+ "item_description": "Number of zone transfer requests failed"
+ },
+ {
+ "item_name": "time_to_ixfr",
+ "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_name": "time_to_axfr",
+ "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_name": "ixfr_running",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "IXFRs running",
+ "item_description": "Number of IXFRs in progress"
+ },
+ {
+ "item_name": "axfr_running",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "AXFRs running",
+ "item_description": "Number of AXFRs in progress"
+ },
+ {
+ "item_name": "ixfr_deferred",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "IXFRs deferred",
+ "item_description": "Number of deferred IXFRs"
+ },
+ {
+ "item_name": "axfr_deferred",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "AXFRs deferred",
+ "item_description": "Number of deferred AXFRs"
+ },
+ {
+ "item_name": "soa_in_progress",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "SOA queries",
+ "item_description": "Number of SOA queries in progress"
+ },
+ {
+ "item_name": "socket",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {
+ "ipv4": {
+ "tcp": {
+ "open": 0,
+ "openfail": 0,
+ "close": 0,
+ "connfail": 0,
+ "conn": 0,
+ "senderr": 0,
+ "recverr": 0
+ }
+ },
+ "ipv6": {
+ "tcp": {
+ "open": 0,
+ "openfail": 0,
+ "close": 0,
+ "connfail": 0,
+ "conn": 0,
+ "senderr": 0,
+ "recverr": 0
+ }
+ }
+ },
+ "item_title": "Socket",
+ "item_description": "Socket",
+ "map_item_spec": [
+ {
+ "item_name": "ipv4",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {
+ "tcp": {
+ "open": 0,
+ "openfail": 0,
+ "close": 0,
+ "connfail": 0,
+ "conn": 0,
+ "senderr": 0,
+ "recverr": 0
+ }
+ },
+ "item_title": "IPv4",
+ "item_description": "IPv4",
+ "map_item_spec": [
+ {
+ "item_name": "tcp",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {
+ "open": 0,
+ "openfail": 0,
+ "close": 0,
+ "connfail": 0,
+ "conn": 0,
+ "senderr": 0,
+ "recverr": 0
+ },
+ "item_title": "TCP",
+ "item_description": "TCP/IP",
+ "map_item_spec": [
+ {
+ "item_name": "open",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "Open",
+ "item_description": "IPv4 TCP sockets opened successfully"
+ },
+ {
+ "item_name": "openfail",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "Open failures",
+ "item_description": "IPv4 TCP sockets open failures"
+ },
+ {
+ "item_name": "close",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "Close",
+ "item_description": "IPv4 TCP sockets closed"
+ },
+ {
+ "item_name": "connfail",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "Connect failures",
+ "item_description": "IPv4 TCP sockets connection failures"
+ },
+ {
+ "item_name": "conn",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "Connect",
+ "item_description": "IPv4 TCP connections established successfully"
+ },
+ {
+ "item_name": "senderr",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "Send errors",
+ "item_description": "IPv4 TCP sockets send errors"
+ },
+ {
+ "item_name": "recverr",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "Receive errors",
+ "item_description": "IPv4 TCP sockets receive errors"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "item_name": "ipv6",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {
+ "tcp": {
+ "open": 0,
+ "openfail": 0,
+ "close": 0,
+ "connfail": 0,
+ "conn": 0,
+ "senderr": 0,
+ "recverr": 0
+ }
+ },
+ "item_title": "IPv6",
+ "item_description": "IPv6",
+ "map_item_spec": [
+ {
+ "item_name": "tcp",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {
+ "open": 0,
+ "openfail": 0,
+ "close": 0,
+ "connfail": 0,
+ "conn": 0,
+ "senderr": 0,
+ "recverr": 0
+ },
+ "item_title": "TCP",
+ "item_description": "TCP/IP",
+ "map_item_spec": [
+ {
+ "item_name": "open",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "Open",
+ "item_description": "IPv6 TCP sockets opened successfully"
+ },
+ {
+ "item_name": "openfail",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "Open failures",
+ "item_description": "IPv6 TCP sockets open failures"
+ },
+ {
+ "item_name": "close",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "Close",
+ "item_description": "IPv6 TCP sockets closed"
+ },
+ {
+ "item_name": "connfail",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "Connect failures",
+ "item_description": "IPv6 TCP sockets connection failures"
+ },
+ {
+ "item_name": "conn",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "Connect",
+ "item_description": "IPv6 TCP connections established successfully"
+ },
+ {
+ "item_name": "senderr",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "Send errors",
+ "item_description": "IPv6 TCP sockets send errors"
+ },
+ {
+ "item_name": "recverr",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "Receive errors",
+ "item_description": "IPv6 TCP sockets receive errors"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+}
diff --git a/tests/system/ifconfig.sh b/tests/system/ifconfig.sh
index c0c365a..7c695e2 100755
--- a/tests/system/ifconfig.sh
+++ b/tests/system/ifconfig.sh
@@ -63,7 +63,7 @@ esac
case "$1" in
start|up)
- for ns in 1 2 3 4 5 6 7
+ for ns in 1 2 3 4 5 6 7 8
do
if test -n "$base"
then
@@ -145,7 +145,7 @@ case "$1" in
;;
stop|down)
- for ns in 7 6 5 4 3 2 1
+ for ns in 8 7 6 5 4 3 2 1
do
if test -n "$base"
then
diff --git a/tests/tools/perfdhcp/command_options.cc b/tests/tools/perfdhcp/command_options.cc
index 9de07ca..f770373 100644
--- a/tests/tools/perfdhcp/command_options.cc
+++ b/tests/tools/perfdhcp/command_options.cc
@@ -134,7 +134,7 @@ CommandOptions::parse(int argc, char** const argv) {
bool
CommandOptions::initialize(int argc, char** argv) {
- char opt = 0; // Subsequent options returned by getopt()
+ int opt = 0; // Subsequent options returned by getopt()
std::string drop_arg; // Value of -D<value>argument
size_t percent_loc = 0; // Location of % sign in -D<value>
double drop_percent = 0; // % value (1..100) in -D<value%>
More information about the bind10-changes
mailing list