BIND 10 trac2522, updated. c7e380374b96d23f682e81a8ff159c8e39538103 [2522] convert SSHFP to use Impl struct

BIND 10 source code commits bind10-changes at lists.isc.org
Fri May 17 05:36:53 UTC 2013


The branch, trac2522 has been updated
       via  c7e380374b96d23f682e81a8ff159c8e39538103 (commit)
       via  8cb0a56a019c3cbaacc8ccdeebaf3906eeda8a74 (commit)
       via  b48e4ef0e4f09fdaa48f11dd97e1c378007cebaf (commit)
       via  ab579f5245e995718410e2f59bb7a82814f138a0 (commit)
       via  943f4ea870d5d00101dcdb4dfb5a6892ed3eb634 (commit)
       via  ef6d94432720eaf1fb73d65d78915a23aada2a25 (commit)
       via  9eab41529f7da65498b241f988a8ca096a4286c8 (commit)
       via  a2500cf8f53c5beaa5e9f872458f10650f91b95d (commit)
       via  da4f84b70faf79d279dd1ad978ee007b9fbd6447 (commit)
       via  adc3e2f94dcb38c4594408774599d946605f3cdb (commit)
       via  60c3b4447c47bd08d990d91a3a412ac6b329ff08 (commit)
       via  dc4cf0c6d071ef5661203ef42894ff8ecc6a90e4 (commit)
       via  1a3909427319f08fb5dacef02baa8c89b0b97408 (commit)
       via  c8aff6addd2137edd3b5b727ff40c3f7f6b00e71 (commit)
       via  320f10a5395ec1799f11d172ad6936da5467e815 (commit)
       via  323d64cc1465f7a1ef8813fa7a541f495c5e9dba (commit)
       via  2b5052e66a62634e8da2495a0d97018a144b7919 (commit)
       via  3d234878c77fab6f7bb172c5ef2bd21cde768a72 (commit)
       via  69cdc5f74d067196ec35daad43f842f716897bcc (commit)
       via  2f808b5a9d101109016361c6143b1add3887d1b6 (commit)
       via  04c159afd7ae18b98b50d1a3d2e5f1eefdb3bae8 (commit)
       via  c3f6b67fa16a07f7f7ede24dd85feaa7c157e1cb (commit)
       via  d5a472119f28ad0f7f15756997c741ba25208ab9 (commit)
       via  56099e3711191e22b41cc6f404a69569c1f3b7e1 (commit)
       via  89e0087450e73ea2336f56c0f7024f4741eaeba0 (commit)
       via  0404a592b185c5473343a3faa25de18845cd192c (commit)
       via  be918f9a8b657005605fc8856ed037fafc2374e9 (commit)
       via  c6603decaadcd33ccf9aee4a7b22447acec4b7f6 (commit)
       via  315d2846dec8b2f36df1aa8ed23feddfc621a356 (commit)
       via  131a89175e97714ac87f14f95703cd76df7a965c (commit)
       via  bc6af598cfc62d68cc4a80c0aa10d8573d63cfc0 (commit)
       via  468687262b865e264f2bdc235ec4aff45fffaf29 (commit)
       via  7ece6fb69d2791ea263c080083a725c36e7a12ea (commit)
       via  65b7adb8a1578ae6f070b9c877149d0db77aefda (commit)
       via  d2861efce4cf71c924de470c48594727e0eba80f (commit)
       via  70066be6e51afc479dc485dfdf9ab79169a9d3c1 (commit)
       via  a19cdcff7accbce09eab6bf7ea359a830564c1f8 (commit)
       via  c630ff8a3861c5597a5c77a81eee52c09aaef643 (commit)
       via  45e7d86237a1b483d27819c5f5fe0d5c6ebb6dc4 (commit)
       via  e1a0ea8ef5c51b9b25afa111fbfe9347afbe5413 (commit)
       via  d36c276d69d8897175964b8a2002405c53630663 (commit)
       via  7ccddd64de7f7928d20f7eff23053b64c644349d (commit)
       via  5bdee1a5056c8e3517d2dd3de954d40323027ee1 (commit)
       via  034e1d3b1490054f265c4e4ac20cfa43127a5625 (commit)
       via  dac1394a30be534559eda0cfeaa140eace9000a6 (commit)
       via  885821d16691e4179678e4d7f61d550774c1a7a6 (commit)
       via  225a1e04ba13d2a48bbf3e1f958ce4e3f86aed49 (commit)
       via  caefbb6f9016955ad62391d799fd69886aa363b4 (commit)
       via  b308ba3bf71ccc768f302022e7ab571ff2cfbdfd (commit)
       via  72604e2508ea78f0f0fcce74b6e34838e75675a2 (commit)
       via  0475216923e0c60294c0901ae3e6af09f5e961ae (commit)
       via  8f4a354b90fbf0be2ddc5c54f72325d52f0eff2e (commit)
       via  5045f86bbd1c7ffaf07c95f7c6ce4d64b1e1c467 (commit)
       via  94d255e2f177399d659e53ae06d622f2576b7e4c (commit)
       via  b9608c20b42ed6db6592a1f7851c91efce974b32 (commit)
       via  214b2747a3213b7267a4a8a597d07851f00fb1c8 (commit)
       via  fd6c9be84e460ead7e1fd3e0888828854adccb4f (commit)
       via  71be4dc12e4ffee2641ea54f70bd26f16bdc1460 (commit)
       via  673bbeb9b8b1ecd0629c0807578624705c58085b (commit)
       via  fc4e4e5c2c469c8c464146bfe56cda99effd4593 (commit)
       via  e3c5c430e69e4b3b2211421be2dc23723c4c40eb (commit)
       via  9f92c87372c0a3b37fabb3ee1963dca5409ccde5 (commit)
       via  943453b21cb4a91ada2f0da363906e29d3659421 (commit)
       via  271a74c31ae50d5f713dd6bccc2127f56f950466 (commit)
       via  96f3f46af4cba730dfccaf13ea8350529dc71bb6 (commit)
       via  30004e59d00244ebea35e8df0abce51acb2cdef2 (commit)
       via  340c8a6ce05ef5c72e81801066191c18875bbec1 (commit)
       via  46af17adcadf913b3ee26a70394b8faaf5dd7b36 (commit)
       via  f85b274b85b57a094d33ca06dfbe12ae67bb47df (commit)
       via  bec8813a793940089543c8875afc3ea475662459 (commit)
       via  e8655ab138c37f22582c7370677a4361cb6d6363 (commit)
       via  23db9e9d61274b5c8c4d23b493cbd582eb9eda4a (commit)
       via  2a6431ad41175316c82c7d13c0bffa0de9a44a7b (commit)
       via  8d6493eb98994ac01c4305d12a148d78d048b681 (commit)
       via  f182b85e7ab01bae5c9c7a181a2b9db7977a9948 (commit)
       via  fa72716d183569839306fa8bde1d7ec451ce8919 (commit)
       via  55dd08057c52c3f0baf3f1b34f23f87c2ee7ff35 (commit)
       via  63e9d90b093a7938f0b7cdb34e9e71379a15d5de (commit)
       via  ac3da26ee0505f828be081d284b4216f5ada820a (commit)
       via  79d4b470181b50699125d1cf3394e11071f6178d (commit)
       via  b6de5342bd648c4f38e892df6c93177c243ec993 (commit)
       via  0a89b921bb8cb268721e8ca38d99f7ea967fdf8c (commit)
       via  c0acd591b18839863e5c46be32daeebc07c162c4 (commit)
       via  5ee122cfa69f8bd6be3c4861407a3da35cb72bdb (commit)
       via  5ee0c3b143458e52d59e3e162c5d9cd4e11e0d1f (commit)
       via  2a8863be72c4fdf0b9aba1e05e525de7fe18d4b4 (commit)
       via  861a1055e8d48f682b5aed9942f1eb1963d93a55 (commit)
       via  a6328cdc2907b9becde3d261373521dc1a3b963f (commit)
       via  1c295702731e5fb76d8ea39c14104347fb0a8b91 (commit)
       via  51be92846c3bf4881701f17e869eb9f12f9cd239 (commit)
       via  35df226d16838f37390270d9d3d1a5e736cfcd12 (commit)
       via  2aaa162fd5f5921aac58baeeb0dd026b7a0a2fbb (commit)
       via  db5ddcf7b62eb21d9cf0f057ee45ec31adc832f3 (commit)
       via  125dd1132b09275fbb528ebdd4358f5977f17402 (commit)
       via  f82f47b299e9f48c86c6c8f7967f29c381d5df06 (commit)
       via  ca3018c0273b41dac1f67892cdf59e3673a183f5 (commit)
       via  1d3b43a5f11c82ea80c9c8469cb8aee4f504cb20 (commit)
       via  8250e0313cf3e7f81646e8dd978f4a16f0578222 (commit)
       via  d24f908722f73071dd220b3457123d3e9dd2deae (commit)
       via  6b983412d569f2e7e81f6e6bebe37720f146d9b2 (commit)
       via  199beea87baee4dd3d4339ec20c32d0d2cb47f40 (commit)
       via  f3ca4f39256d94a48ed38f886b70d8195f89c83d (commit)
       via  b4093345f5b38af66fc7b2630baeaf843a817f74 (commit)
       via  0a2277b4be0ef6e326d170ad7a0836a17b82c0f4 (commit)
       via  6b3b78fa009e557f65deee688302c573106a3ef1 (commit)
       via  4c8efe3ad82e6f62f663579c4e792f70db7012bc (commit)
       via  3bfd98d48a7b8e132931a950cf5d52ae1b324159 (commit)
       via  d06ff546486bc123c1fafbe8b20bb5e60557970c (commit)
       via  d6cae3d58234c7fabfc8d1c9bc5b1b59c7c1e3cf (commit)
       via  91a4343fe608ab91dda3f8caf05fef277a2eb215 (commit)
       via  31f138b96f8713469feb16ff361e40334ec8b83d (commit)
       via  4b79543220fa1f81ffaf16bdcfb79845587f51cd (commit)
       via  858d5facb7af996524fed919231d5ff55ad54aa4 (commit)
       via  2fdd3551e1012e61c414c475741f0013fce9e11e (commit)
       via  df3ee4b9e04459e2ba6dbc50702b3c7f29c0ceb7 (commit)
       via  af37bfe0c400e0dd923f57e9775d67a8f17af802 (commit)
       via  b82db9d608868c11c68d692458769e9afb47f871 (commit)
       via  784caaf1d2a386777df43178579417107c6492e4 (commit)
       via  8544c8e88d5519acf996ef6c4659b75b52ce6839 (commit)
       via  5db914470d01eeb3ebde4b71f13647b093df3138 (commit)
       via  1cef6595d167a14adcec8674ec51d6059baa06fc (commit)
       via  04e248bf10707b003b4584a2c06e9733f6729d28 (commit)
       via  61ec72fa83146fe30e2165e02639eab082531712 (commit)
       via  084f1f59a6da39c03e5de0ef079216ef94e277dc (commit)
       via  a50e0a4671586daa8142b17d4f1a7d2ecd9e44bf (commit)
       via  07326330d220e74b0f84572d601d50d83ce48945 (commit)
       via  71e0b2b77c475c41ff73668455d1dc447f01e8e5 (commit)
       via  f22ba824385dd587b9e5b54491a225c192d823e0 (commit)
       via  c9095ff3de68440efa427a536985bb01e0125504 (commit)
       via  870cc9bca34feab530b12cb870fd92f365513571 (commit)
       via  fdb992f0a2699cdad3bcaef79c2ca35f8e700c16 (commit)
       via  9ac31c2a7d7d2ee78ef2de4d56d3f57f6f45d75a (commit)
       via  5305956b8ed2e5983eb4255e959ffc26c6017693 (commit)
       via  5629c01345df4fd78a544169fac69e2afc9322a9 (commit)
       via  9d00667f84006fc001b04953519e78b1ee5f0010 (commit)
       via  d7885ecddb52be49de729021a70b42e60aca2cea (commit)
       via  1f578c9b208b0fb2e58b101d537a9d07f66ad690 (commit)
       via  4f63e23b7aac425bafd4e0c6e5c4099b157206c5 (commit)
       via  a4b1c8d4bb37b273c9027f41b00eeae54ad2f83d (commit)
       via  4a8316ddc1bff361a289ed8c406bbe90fde79e16 (commit)
       via  5c0c1cb8f090edf516c21cf03506075bd866915c (commit)
       via  e346959d031b0ce05939f801e83f653892757e49 (commit)
       via  60b242c2d161e30a05236fb6dae3ff21daa0b6b8 (commit)
       via  307e3c7a946a593945050b9ea8159df85f5cf28d (commit)
       via  2e97db1d7db5a65bc19f6909269ae930d2e8da18 (commit)
       via  4d5c20e29fd4a74db31024bdcdaa99d431af3219 (commit)
       via  20df0b0ce702241ce8265690388483c5ece3eb16 (commit)
       via  ff8d95f4e0afc3d2b8fc5f3e75eb41edee1f969e (commit)
       via  678965be5a2ca3ba12981884e48911028953f853 (commit)
       via  836fabb60589aaf7b901176892b6c28ab464e8d6 (commit)
       via  eb8aaf5c3b048047cd1cc138ee4951405bb59ce3 (commit)
       via  c97e9105e1c25d4708d2f153c93c1a29a73c9537 (commit)
       via  30c5683babdc96906117dfd677c3313d3a82fea9 (commit)
       via  acbf4d8c9e3deb825a7c193a26738b12ef870166 (commit)
       via  14ee53dc65a150d46f51e4cf6d666c38187ce075 (commit)
       via  9344c41c186e020cddf191880ff5bef4e653b461 (commit)
       via  b448dabbe9de31366ae5a20c0aae423f6257d953 (commit)
       via  c11f99307c27f07ad5b8ab9abb80f6ee5be32efc (commit)
       via  0eec3b5ce47348b8fdf8eae9ffc61dcb4db0ccb0 (commit)
       via  c37fd56ccccd540bde166eb7f29c2c691bbff154 (commit)
       via  be426ec46dfd7aa3573872059ac9420dd4fd7d75 (commit)
       via  208c318edbba5b98ebbd865f15c31bd77626328c (commit)
       via  bae4e4960422f2f86af1fc48d1f1d1a8f23a1b95 (commit)
       via  b0f2acac75969ee9773b3ec3eb584e017cbfb117 (commit)
       via  5ac7de770cd112d0bc3842dd79c612bae3acc611 (commit)
       via  dc2960e9845fe08a27d49dcf9d6474ad8b825fb1 (commit)
       via  d1d37e198212833b5ca1e5c943f3f8aeef805a17 (commit)
       via  ad8e7ea08e814913ff6fadbf593a3fba44d4bbbc (commit)
       via  26a9ee9fd3b8d2cf0c9643ac5441bcedfa3c2ab3 (commit)
       via  d6d04321180adf453ecb64c28e03b2c43591e521 (commit)
       via  be67ff2306c8aa98efd9ae3b017a2926dd3fd46e (commit)
       via  cb5eb7ddeb879f1658e446d06e9556aa075d4e3a (commit)
       via  d69fb646ed39dafe4277334a28b52d948e6348ae (commit)
       via  655179cfb251cd07a03469524e91c4ed81b57803 (commit)
       via  aca3456d32de52b95c0ec0986c041cc9ab63e132 (commit)
       via  55a49c67cdff76b0dca55d425ae977fb3feda9c2 (commit)
       via  85aef712019c4b0293e1726c705b6bfc749ad537 (commit)
       via  fcf8b9df5fb8511e9bd78f42bd797f2a8718c6e6 (commit)
       via  7fe200c92eb411c554a82fedb9b4126686961f0c (commit)
       via  0ef3b7adcd45bbd3127fc3aef69cf6798e69c807 (commit)
       via  936b3ffaaaca125fe6a87ea3ad3910aa2d45c3d5 (commit)
       via  695a3119423907a059435465f98669976a969f28 (commit)
       via  4d94e850cc95e491ce268c9782c0eae335e7f7ea (commit)
       via  2cfdda6be3038b9318c9f72da1b7c20488f2ae99 (commit)
       via  01b2a2fa9d86fbc5796626461b282581e46d2979 (commit)
       via  4de51f893dc92b863a9d3baa747153c2a011923c (commit)
       via  a1b3cfd353802b33b1ed51b51c9d98f6fe56d2bc (commit)
       via  75445a1c6e208d27ab8d85166ebd079be947f98b (commit)
       via  4b74e2550ba3030f666d03bf57f65f1d6d1a30f7 (commit)
       via  e42a6c969cda1e3676c9884e9697349e18db92b7 (commit)
       via  f4486e178f04944792e169c1a7a0c5098dd2c223 (commit)
       via  857b8cbf4ad057e50232eb152bd1ec4903da807f (commit)
       via  e4cf4a7222b006c5534edaecb23614fb224585ff (commit)
       via  fc40317d31b6c76327ff83d81c9ee3598e05cf3d (commit)
       via  63808a73aa582e3bac75c70bc332e3eaca8152ba (commit)
       via  986925540226c118c63b33803914f9e5d44c0576 (commit)
       via  68a52f5e6e31895b350bb049b7eee9f262efbb1a (commit)
       via  df40284b1ee1bd833f9cc79f0da0c1202d38d951 (commit)
       via  feae5f317ee4c0940fb49c92f08228e21b720a8e (commit)
       via  cef5bd992ace60f4e7af50ba9e46d7e27aa9e39e (commit)
       via  6c13b0c102f7716cb70b381b5f615e1b29a05ce3 (commit)
       via  8add7a15c72e218d7a954c4cb2765206806e1237 (commit)
       via  29f51829c08e4248ee79f073c466bad761d07f5d (commit)
       via  0d7421b938c0017f364daf2bd4ef5d27aa1c66fa (commit)
       via  605921ab9a037b9361ac4eafeefe82c9b0d83a9d (commit)
       via  35edc2d075c899f9da26bf6644c8c113826aa16b (commit)
       via  e2f1735572f26c3f8003ec17f6b9be333ac4320d (commit)
       via  d9749e1f6d660696c07c2e3474fce9f291d5b959 (commit)
       via  6cd568fb20e3714a77e98d31f0387e941702c327 (commit)
       via  64cdf20648e3a3aa50334c4d555838213901602e (commit)
       via  6655139a125d4f9032bef3dbbc0a49f9ae2b87d7 (commit)
       via  0e92ed020eecca48e4fb045221d732d591d1afad (commit)
      from  7482587271347cc071f95cf1709a0370c0087fd7 (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 c7e380374b96d23f682e81a8ff159c8e39538103
Author: Paul Selkirk <pselkirk at isc.org>
Date:   Fri May 17 01:26:31 2013 -0400

    [2522] convert SSHFP to use Impl struct

commit 8cb0a56a019c3cbaacc8ccdeebaf3906eeda8a74
Author: Paul Selkirk <pselkirk at isc.org>
Date:   Thu May 16 17:27:58 2013 -0400

    [2522] add MasterLexer constructors for in::SSHFP, ch::A, hs::A.
    
    Also removed OldRdataFactory/new_rdata_factory_users stuff, since all Rdata classes now use the new RdataFactory.

commit b48e4ef0e4f09fdaa48f11dd97e1c378007cebaf
Merge: ab579f5 7482587
Author: Paul Selkirk <pselkirk at isc.org>
Date:   Wed May 15 23:39:14 2013 -0400

    Merge branch 'trac2522' of ssh://git.bind10.isc.org/var/bind10/git/bind10 into trac2522
    
    Conflicts:
    	src/lib/dns/gen-rdatacode.py.in

commit ab579f5245e995718410e2f59bb7a82814f138a0
Author: JINMEI Tatuya <jinmei at isc.org>
Date:   Tue May 14 17:30:03 2013 -0700

    [2522] some simple suggested fixes:
    
    - use the unnamed namespace for the entire content to hide as many things as
      possible.
    - define rdata_tsig inside the fixture class to avoid initialization fiasco.
      (to make it possible, also make toWireCommonChecks a member function).

commit 943f4ea870d5d00101dcdb4dfb5a6892ed3eb634
Author: JINMEI Tatuya <jinmei at isc.org>
Date:   Tue May 14 17:27:09 2013 -0700

    [2522] trivial style fix

commit ef6d94432720eaf1fb73d65d78915a23aada2a25
Author: JINMEI Tatuya <jinmei at isc.org>
Date:   Tue May 14 17:06:17 2013 -0700

    [2522] minor style fixes

commit 9eab41529f7da65498b241f988a8ca096a4286c8
Author: JINMEI Tatuya <jinmei at isc.org>
Date:   Tue May 14 16:36:12 2013 -0700

    [2522] trivial style cleanup: folded long lines

commit a2500cf8f53c5beaa5e9f872458f10650f91b95d
Author: Paul Selkirk <pselkirk at isc.org>
Date:   Wed May 8 11:29:02 2013 -0400

    [2522] fix a typo

commit da4f84b70faf79d279dd1ad978ee007b9fbd6447
Author: Paul Selkirk <pselkirk at isc.org>
Date:   Wed May 8 10:59:40 2013 -0400

    [2522] initialize impl_ directly in TSIG lexer constructor

commit adc3e2f94dcb38c4594408774599d946605f3cdb
Author: Paul Selkirk <pselkirk at isc.org>
Date:   Wed May 8 00:35:22 2013 -0400

    [2522] TSIG algorigthm can be a reference, maybe a bit more efficient

commit 60c3b4447c47bd08d990d91a3a412ac6b329ff08
Author: Paul Selkirk <pselkirk at isc.org>
Date:   Tue May 7 23:58:42 2013 -0400

    [2522] convert TSIG string constructor tests to checkFromText

commit dc4cf0c6d071ef5661203ef42894ff8ecc6a90e4
Author: Paul Selkirk <pselkirk at isc.org>
Date:   Tue May 7 22:13:22 2013 -0400

    [2522] more TSIG unit tests

commit 1a3909427319f08fb5dacef02baa8c89b0b97408
Author: Paul Selkirk <pselkirk at isc.org>
Date:   Tue May 7 15:59:29 2013 -0400

    [2522] add MasterLexer constructor for TSIG

commit c8aff6addd2137edd3b5b727ff40c3f7f6b00e71
Author: Paul Selkirk <pselkirk at isc.org>
Date:   Tue May 7 13:16:17 2013 -0400

    [2522] fix RP unit test

commit 320f10a5395ec1799f11d172ad6936da5467e815
Author: Paul Selkirk <pselkirk at isc.org>
Date:   Tue May 7 13:08:27 2013 -0400

    [2522] more MINFO unit tests

commit 323d64cc1465f7a1ef8813fa7a541f495c5e9dba
Author: Paul Selkirk <pselkirk at isc.org>
Date:   Mon May 6 10:30:04 2013 -0400

    [2522] add MasterLexer constructor for MINFO

commit 2b5052e66a62634e8da2495a0d97018a144b7919
Author: Paul Selkirk <pselkirk at isc.org>
Date:   Mon May 6 10:28:55 2013 -0400

    [2522] normalize some RP documentation comments

commit 3d234878c77fab6f7bb172c5ef2bd21cde768a72
Author: Paul Selkirk <pselkirk at isc.org>
Date:   Sat May 4 19:25:30 2013 -0400

    [2522] add MasterLexer constructor for RP

-----------------------------------------------------------------------

Summary of changes:
 ChangeLog                                          |   24 +-
 Makefile.am                                        |    4 +-
 configure.ac                                       |   13 +-
 doc/devel/mainpage.dox                             |    1 +
 doc/guide/bind10-guide.xml                         |   92 ++++-
 m4macros/Makefile.am                               |    2 +
 m4macros/ax_sqlite3_for_bind10.m4                  |   25 ++
 src/bin/dbutil/tests/Makefile.am                   |    6 +
 src/bin/dhcp4/tests/config_parser_unittest.cc      |    8 +-
 src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc     |   26 +-
 src/bin/dhcp4/tests/dhcp4_srv_unittest.cc          |   65 +--
 src/bin/dhcp6/config_parser.cc                     |   30 +-
 src/bin/dhcp6/dhcp6.spec                           |    6 +
 src/bin/dhcp6/dhcp6_srv.cc                         |   44 ++-
 src/bin/dhcp6/tests/config_parser_unittest.cc      |  108 ++++-
 src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc     |   18 +-
 src/bin/dhcp6/tests/dhcp6_srv_unittest.cc          |  170 +++++++-
 src/bin/resolver/bench/fake_resolution.cc          |    4 +-
 src/bin/resolver/bench/fake_resolution.h           |    7 +
 src/bin/stats/tests/Makefile.am                    |    2 +-
 ...b10-stats-httpd_test.py => stats-httpd_test.py} |  229 ++++++-----
 .../tests/{b10-stats_test.py => stats_test.py}     |  348 +++++++++-------
 src/bin/stats/tests/test_utils.py                  |  363 ++++++-----------
 src/bin/xfrin/b10-xfrin.xml                        |  126 ++++++
 src/bin/xfrin/tests/xfrin_test.py                  |  212 +++++++++-
 src/bin/xfrin/xfrin.py.in                          |   56 ++-
 src/bin/xfrin/xfrin.spec                           |  113 ++++++
 src/bin/xfrin/xfrin_messages.mes                   |    3 +
 src/lib/asiodns/sync_udp_server.cc                 |    9 +
 src/lib/asiodns/sync_udp_server.h                  |   19 +-
 src/lib/asiolink/io_service.cc                     |   20 +-
 src/lib/datasrc/memory/memory_messages.mes         |    6 +-
 src/lib/dhcp/libdhcp++.dox                         |   47 +++
 src/lib/dhcp/pkt6.cc                               |   84 ++++
 src/lib/dhcp/pkt6.h                                |   47 ++-
 src/lib/dhcp/tests/iface_mgr_unittest.cc           |  187 ++++-----
 src/lib/dhcp/tests/libdhcp++_unittest.cc           |   13 +-
 src/lib/dhcp/tests/option4_addrlst_unittest.cc     |   90 ++---
 src/lib/dhcp/tests/option6_addrlst_unittest.cc     |   65 ++-
 src/lib/dhcp/tests/option6_ia_unittest.cc          |   61 ++-
 src/lib/dhcp/tests/option6_iaaddr_unittest.cc      |   23 +-
 src/lib/dhcp/tests/option_unittest.cc              |  264 ++++++-------
 src/lib/dhcp/tests/pkt4_unittest.cc                |  164 ++++----
 src/lib/dhcp/tests/pkt6_unittest.cc                |  218 +++++++---
 src/lib/dhcpsrv/cfgmgr.cc                          |   30 +-
 src/lib/dhcpsrv/cfgmgr.h                           |    1 -
 src/lib/dhcpsrv/dhcpsrv_messages.mes               |    9 +-
 src/lib/dhcpsrv/subnet.h                           |   16 +
 src/lib/dhcpsrv/tests/alloc_engine_unittest.cc     |   81 ++--
 src/lib/dhcpsrv/tests/cfgmgr_unittest.cc           |  120 +++++-
 src/lib/dhcpsrv/tests/lease_mgr_unittest.cc        |   27 +-
 src/lib/dhcpsrv/tests/schema_copy.h                |    6 +
 src/lib/dhcpsrv/tests/subnet_unittest.cc           |   16 +
 src/lib/dns/gen-rdatacode.py.in                    |   53 +--
 src/lib/dns/rdata/ch_3/a_1.cc                      |    7 +-
 src/lib/dns/rdata/generic/opt_41.cc                |   19 +-
 src/lib/dns/rdata/generic/rrsig_46.cc              |  142 +++++--
 src/lib/dns/rdata/generic/rrsig_46.h               |   12 +-
 src/lib/dns/rdata/generic/sshfp_44.cc              |  211 +++++++---
 src/lib/dns/rdata/generic/sshfp_44.h               |   16 +-
 src/lib/dns/rdata/hs_4/a_1.cc                      |    7 +-
 src/lib/dns/rdata/in_1/dhcid_49.cc                 |   90 +++--
 src/lib/dns/rdata/in_1/dhcid_49.h                  |    5 +-
 src/lib/dns/tests/message_unittest.cc              |    4 +-
 src/lib/dns/tests/rdata_dhcid_unittest.cc          |   93 +++--
 src/lib/dns/tests/rdata_ds_like_unittest.cc        |   53 ++-
 src/lib/dns/tests/rdata_opt_unittest.cc            |    2 +-
 src/lib/dns/tests/rdata_rrsig_unittest.cc          |  233 +++++++++--
 src/lib/dns/tests/rdata_sshfp_unittest.cc          |    5 +-
 src/lib/dns/tests/rrcollator_unittest.cc           |    6 +-
 src/lib/dns/tests/rrset_unittest.cc                |    4 +-
 src/lib/python/isc/statistics/counters.py          |    8 +-
 .../python/isc/statistics/tests/counters_test.py   |    4 +-
 .../isc/statistics/tests/testdata/test_spec3.spec  |   16 +-
 .../xfrin/retransfer_master.conf.orig              |    3 -
 ...er.conf.orig => retransfer_master_v4.conf.orig} |    9 +-
 .../xfrin/retransfer_slave_notify.conf.orig        |    1 +
 ...y.conf.orig => retransfer_slave_notify_v4.conf} |    7 +-
 tests/lettuce/features/terrain/bind10_control.py   |  118 +++---
 tests/lettuce/features/terrain/terrain.py          |    2 +
 .../lettuce/features/xfrin_notify_handling.feature |  416 +++++++++++++++-----
 81 files changed, 3567 insertions(+), 1707 deletions(-)
 create mode 100644 m4macros/Makefile.am
 create mode 100644 m4macros/ax_sqlite3_for_bind10.m4
 rename src/bin/stats/tests/{b10-stats-httpd_test.py => stats-httpd_test.py} (87%)
 rename src/bin/stats/tests/{b10-stats_test.py => stats_test.py} (87%)
 copy tests/lettuce/configurations/xfrin/{retransfer_master.conf.orig => retransfer_master_v4.conf.orig} (89%)
 copy tests/lettuce/configurations/xfrin/{retransfer_slave_notify.conf.orig => retransfer_slave_notify_v4.conf} (85%)

-----------------------------------------------------------------------
diff --git a/ChangeLog b/ChangeLog
index 594a1e9..3350068 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,21 @@
+612.	[func]		tomek
+	b10-dhcp6: Support for relayed DHCPv6 traffic has been added.
+
+611.	[func]		naokikambe
+	Added Xfrin statistics items such as the number of successful
+	transfers.  These are per-zone type counters.  Their values can be
+	obtained with zone names by invoking "Stats show Xfrin" via bindctl
+	while Xfrin is running.
+	(Trac #2252, git e1a0ea8ef5c51b9b25afa111fbfe9347afbe5413)
+
+bind10-1.0.0beta2 released on May 10, 2013
+
+610.	[bug]		muks
+	When the sqlite3 program is not available on the system (in
+	PATH), we no longer attempt to run some tests which depend
+	on it.
+	(Trac #1909, git f85b274b85b57a094d33ca06dfbe12ae67bb47df)
+
 609.	[bug]		jinmei
 	Handled some rare error cases in DNS server classes correctly.
 	This fix specifically solves occasional crash of b10-auth due to
@@ -36,8 +54,8 @@
 	(Trac #2879, git 4c45f29f28ae766a9f7dc3142859f1d0000284e1)
 
 605.	[bug]		tmark
-	Modified perfdhcp to calculate the times displayed for packet sent 
-	and received as time elapsed since perfdhcp process start time.  
+	Modified perfdhcp to calculate the times displayed for packet sent
+	and received as time elapsed since perfdhcp process start time.
 	Previously these were times since the start of the epoch.
 	However the large numbers involved caused loss of precision
 	in the calculation of the test statistics.
@@ -61,7 +79,7 @@
 	(Trac #2770, git a622140d411b3f07a68a1451e19df36118a80650)
 
 602.	[bug]		tmark
-	Perdhcp will now exit gracefully if the command line argument for
+	Perfdhcp will now exit gracefully if the command line argument for
 	IP version (-4 or -6) does not match the command line argument
 	given for the server. Prior to this perfdhcp would core when given
 	an IP version of -6 but a valid IPv4 address for server.
diff --git a/Makefile.am b/Makefile.am
index 8e01f4a..10ef321 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -2,7 +2,7 @@ ACLOCAL_AMFLAGS = -I m4macros -I examples/m4 ${ACLOCAL_FLAGS}
 # ^^^^^^^^ This has to be the first line and cannot come later in this
 # Makefile.am due to some bork in some versions of autotools.
 
-SUBDIRS = compatcheck doc . src tests
+SUBDIRS = compatcheck doc . src tests m4macros
 USE_LCOV=@USE_LCOV@
 LCOV=@LCOV@
 GENHTML=@GENHTML@
@@ -46,7 +46,7 @@ endif
 clean-cpp-coverage:
 	@if [ $(USE_LCOV) = yes ] ; then \
 		$(LCOV) --directory . --zerocounters; \
-		rm -rf coverage/; \
+		rm -rf $(abs_top_srcdir)/coverage-cpp-html/; \
 	else \
 		echo "C++ code coverage not enabled at configuration time." ; \
 		echo "Use: ./configure --with-lcov" ; \
diff --git a/configure.ac b/configure.ac
index e35e498..1b5e39b 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2,7 +2,7 @@
 # Process this file with autoconf to produce a configure script.
 
 AC_PREREQ([2.59])
-AC_INIT(bind10, 20130221, bind10-dev at isc.org)
+AC_INIT(bind10, 20130510, bind10-dev at isc.org)
 AC_CONFIG_SRCDIR(README)
 # serial-tests is not available in automake version before 1.13. In
 # automake 1.13 and higher, AM_PROG_INSTALL is undefined, so we'll check
@@ -388,8 +388,6 @@ In this case we will continue, but naming of python processes will not work.])
     fi
 fi
 
-# TODO: check for _sqlite3.py module
-
 # (g++ only check)
 # Python 3.2 has an unused parameter in one of its headers. This
 # has been reported, but not fixed as of yet, so we check if we need
@@ -1043,12 +1041,16 @@ AC_SUBST(GTEST_LDFLAGS)
 AC_SUBST(GTEST_LDADD)
 AC_SUBST(GTEST_SOURCE)
 
-dnl check for pkg-config itself so we don't try the m4 macro without pkg-config
+dnl check for pkg-config itself
 AC_CHECK_PROG(HAVE_PKG_CONFIG, pkg-config, yes, no)
 if test "x$HAVE_PKG_CONFIG" = "xno" ; then
   AC_MSG_ERROR(Please install pkg-config)
 fi
-PKG_CHECK_MODULES(SQLITE, sqlite3 >= 3.3.9, enable_features="$enable_features SQLite3")
+
+AX_SQLITE3_FOR_BIND10
+if test "x$have_sqlite" = "xyes" ; then
+  enable_features="$enable_features SQLite3"
+fi
 
 #
 # ASIO: we extensively use it as the C++ event management module.
@@ -1323,6 +1325,7 @@ AC_CONFIG_FILES([Makefile
                  tests/tools/perfdhcp/Makefile
                  tests/tools/perfdhcp/tests/Makefile
                  tests/tools/perfdhcp/tests/testdata/Makefile
+                 m4macros/Makefile
                  dns++.pc
                ])
 AC_OUTPUT([doc/version.ent
diff --git a/doc/devel/mainpage.dox b/doc/devel/mainpage.dox
index 295fd03..92f86eb 100644
--- a/doc/devel/mainpage.dox
+++ b/doc/devel/mainpage.dox
@@ -30,6 +30,7 @@
  *   - @subpage dhcpv6ConfigInherit
  * - @subpage libdhcp
  *   - @subpage libdhcpIntro
+ *   - @subpage libdhcpRelay
  *   - @subpage libdhcpIfaceMgr
  * - @subpage libdhcpsrv
  *   - @subpage leasemgr
diff --git a/doc/guide/bind10-guide.xml b/doc/guide/bind10-guide.xml
index d0d1d4c..568bbea 100644
--- a/doc/guide/bind10-guide.xml
+++ b/doc/guide/bind10-guide.xml
@@ -4842,29 +4842,22 @@ should include options from the isc option space:
       <section id="dhcp6-config-subnets">
         <title>Subnet Selection</title>
           <para>
-          The DHCPv6 server may receive requests from local (connected
-          to the same subnet as the server) and remote (connecting via
-          relays) clients.
-          <note>
-          <para>
-          Currently relayed DHCPv6 traffic is not supported.  The server will
-          only respond to local DHCPv6 requests - see <xref linkend="dhcp6-limit"/>
-          </para>
-          </note>
-          As it may have many subnet configurations defined, it
-          must select appropriate subnet for a given request. To do this, the server first
+          The DHCPv6 server may receive requests from local (connected to the
+          same subnet as the server) and remote (connecting via relays) clients.
+          As server may have many subnet configurations defined, it must select
+          appropriate subnet for a given request. To do this, the server first
           checks if there is only one subnet defined and source of the packet is
-          link-local. If this is the case, the server assumes that the only subnet
-          defined is local and client is indeed connected to it. This check
-          simplifies small deployments.
+          link-local. If this is the case, the server assumes that the only
+          subnet defined is local and client is indeed connected to it. This
+          check simplifies small deployments.
           </para>
           <para>
           If there are two or more subnets defined, the server can not assume
           which of those (if any) subnets are local. Therefore an optional
-          "interface" parameter is available within a subnet definition to designate that a given subnet
-          is local, i.e. reachable directly over specified interface. For example
-          the server that is intended to serve a local subnet over eth0 may be configured
-          as follows:
+          "interface" parameter is available within a subnet definition to
+          designate that a given subnet is local, i.e. reachable directly over
+          specified interface. For example the server that is intended to serve
+          a local subnet over eth0 may be configured as follows:
 <screen>
 > <userinput>config add Dhcp6/subnet6</userinput>
 > <userinput>config set Dhcp6/subnet6[1]/subnet "2001:db8:beef::/48"</userinput>
@@ -4875,6 +4868,66 @@ should include options from the isc option space:
         </para>
       </section>
 
+      <section id="dhcp6-relays">
+        <title>DHCPv6 Relays</title>
+        <para>
+          A DHCPv6 server with multiple subnets defined must select the
+          appropriate subnet when it receives a request from client.  For clients
+          connected via relays, two mechanisms are used:
+        </para>
+        <para>
+          The first uses the linkaddr field in the RELAY_FORW message. The name
+          of this field is somewhat misleading in that it does not contain a link-layer
+          address: instead, it holds an address (typically a global address) that is
+          used to identify a link. The DHCPv6 server checks if the address belongs
+          to a defined subnet and, if it does, that subnet is selected for the client's
+          request.
+        </para>
+        <para>
+          The second mechanism is based on interface-id options. While forwarding a client's
+          message, relays may insert an interface-id option into the message that
+          identifies the interface on the relay that received the message. (Some
+          relays allow configuration of that parameter, but it is sometimes
+          hardcoded and may range from the very simple (e.g. "vlan100") to the very cryptic:
+          one example seen on real hardware was "ISAM144|299|ipv6|nt:vp:1:110"). The
+          server can use this information to select the appropriate subnet.
+          The information is also returned to the relay which then knows the
+          interface to use to transmit the response to the client. In order for
+          this to work successfully, the relay interface IDs must be unique within
+          the network and the server configuration must match those values.
+        </para>
+        <para>
+          When configuring the DHCPv6 server, it should be noted that two
+          similarly-named parameters can be configured for a subnet:
+          <itemizedlist>
+            <listitem><simpara>
+              "interface" defines which local network interface can be used
+              to access a given subnet.
+            </simpara></listitem>
+            <listitem><simpara>
+              "interface-id" specifies the content of the interface-id option
+              used by relays to identify the interface on the relay to which
+              the response packet is sent.
+            </simpara></listitem>
+          </itemizedlist>
+          The two are mutually exclusive: a subnet cannot be both reachable locally
+          (direct traffic) and via relays (remote traffic). Specifying both is a
+          configuration error and the DHCPv6 server will refuse such a configuration.
+        </para>
+
+        <para>
+          To specify interface-id with value "vlan123", the following commands can
+          be used:
+          <screen>
+> <userinput>config add Dhcp6/subnet6</userinput>
+> <userinput>config set Dhcp6/subnet6[0]/subnet "2001:db8:beef::/48"</userinput>
+> <userinput>config set Dhcp6/subnet6[0]/pool [ "2001:db8:beef::/48" ]</userinput>
+> <userinput>config set Dhcp6/subnet6[0]/interface-id "vland123"</userinput>
+> <userinput>config commit</userinput>
+</screen>
+        </para>
+      </section>
+
    </section>
 
     <section id="dhcp6-serverid">
@@ -4941,9 +4994,6 @@ Dhcp6/renew-timer	1000	integer	(default)
           </para>
         </listitem>
         <listitem>
-          <simpara>Relayed traffic is not supported.</simpara>
-        </listitem>
-        <listitem>
           <simpara>Temporary addresses are not supported.</simpara>
         </listitem>
         <listitem>
diff --git a/m4macros/Makefile.am b/m4macros/Makefile.am
new file mode 100644
index 0000000..eeae7f9
--- /dev/null
+++ b/m4macros/Makefile.am
@@ -0,0 +1,2 @@
+EXTRA_DIST  = ax_boost_for_bind10.m4
+EXTRA_DIST += ax_sqlite3_for_bind10.m4
diff --git a/m4macros/ax_sqlite3_for_bind10.m4 b/m4macros/ax_sqlite3_for_bind10.m4
new file mode 100644
index 0000000..4eb7f94
--- /dev/null
+++ b/m4macros/ax_sqlite3_for_bind10.m4
@@ -0,0 +1,25 @@
+dnl @synopsis AX_SQLITE3_FOR_BIND10
+dnl
+dnl Test for the sqlite3 library and program, intended to be used within
+dnl BIND 10, and to test BIND 10.
+dnl
+dnl We use pkg-config to look for the sqlite3 library, so the sqlite3
+dnl development package with the .pc file must be installed.
+dnl
+dnl This macro sets SQLITE_CFLAGS and SQLITE_LIBS. It also sets
+dnl SQLITE3_PROGRAM to the path of the sqlite3 program, if it is found
+dnl in PATH.
+
+AC_DEFUN([AX_SQLITE3_FOR_BIND10], [
+
+PKG_CHECK_MODULES(SQLITE, sqlite3 >= 3.3.9,
+    have_sqlite="yes",
+    have_sqlite="no (sqlite3 not detected)")
+
+# Check for sqlite3 program
+AC_PATH_PROG(SQLITE3_PROGRAM, sqlite3, no)
+AM_CONDITIONAL(HAVE_SQLITE3_PROGRAM, test "x$SQLITE3_PROGRAM" != "xno")
+
+# TODO: check for _sqlite3.py module
+
+])dnl AX_SQLITE3_FOR_BIND10
diff --git a/src/bin/dbutil/tests/Makefile.am b/src/bin/dbutil/tests/Makefile.am
index aaa57cc..1030c63 100644
--- a/src/bin/dbutil/tests/Makefile.am
+++ b/src/bin/dbutil/tests/Makefile.am
@@ -5,5 +5,11 @@ SUBDIRS = . testdata
 noinst_SCRIPTS = dbutil_test.sh
 
 check-local:
+if HAVE_SQLITE3_PROGRAM
 	B10_LOCKFILE_DIR_FROM_BUILD=$(abs_top_builddir) \
 	$(SHELL) $(abs_builddir)/dbutil_test.sh
+else
+	@echo ""
+	@echo " **** The sqlite3 program is required to run dbutil tests **** "
+	@echo ""
+endif
diff --git a/src/bin/dhcp4/tests/config_parser_unittest.cc b/src/bin/dhcp4/tests/config_parser_unittest.cc
index dd11ec3..61e6b61 100644
--- a/src/bin/dhcp4/tests/config_parser_unittest.cc
+++ b/src/bin/dhcp4/tests/config_parser_unittest.cc
@@ -25,7 +25,10 @@
 #include <dhcp/option_int.h>
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/cfgmgr.h>
+
 #include <boost/foreach.hpp>
+#include <boost/scoped_ptr.hpp>
+
 #include <iostream>
 #include <fstream>
 #include <sstream>
@@ -47,7 +50,7 @@ public:
         // Open port 0 means to not do anything at all. We don't want to
         // deal with sockets here, just check if configuration handling
         // is sane.
-        srv_ = new Dhcpv4Srv(0);
+        srv_.reset(new Dhcpv4Srv(0));
     }
 
     // Checks if global parameter of name have expected_value
@@ -73,7 +76,6 @@ public:
 
     ~Dhcp4ParserTest() {
         resetConfiguration();
-        delete srv_;
     };
 
     /// @brief Create the simple configuration with single option.
@@ -278,7 +280,7 @@ public:
         }
     }
 
-    Dhcpv4Srv* srv_;
+    boost::scoped_ptr<Dhcpv4Srv> srv_;
 
     int rcode_;
     ConstElementPtr comment_;
diff --git a/src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc b/src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc
index fd4e90e..13b57be 100644
--- a/src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc
+++ b/src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -18,6 +18,7 @@
 #include <dhcp/dhcp4.h>
 #include <dhcp4/ctrl_dhcp4_srv.h>
 
+#include <boost/scoped_ptr.hpp>
 #include <gtest/gtest.h>
 
 #include <fstream>
@@ -36,7 +37,7 @@ using namespace isc::config;
 namespace {
 
 class NakedControlledDhcpv4Srv: public ControlledDhcpv4Srv {
-    // "naked" DHCPv4 server, exposes internal fields
+    // "Naked" DHCPv4 server, exposes internal fields
 public:
     NakedControlledDhcpv4Srv():ControlledDhcpv4Srv(DHCP4_SERVER_PORT + 10000) { }
 };
@@ -52,21 +53,21 @@ public:
 
 TEST_F(CtrlDhcpv4SrvTest, commands) {
 
-    ControlledDhcpv4Srv* srv = NULL;
-    ASSERT_NO_THROW({
-        srv = new ControlledDhcpv4Srv(DHCP4_SERVER_PORT + 10000);
-    });
+    boost::scoped_ptr<ControlledDhcpv4Srv> srv;
+    ASSERT_NO_THROW(
+        srv.reset(new ControlledDhcpv4Srv(DHCP4_SERVER_PORT + 10000))
+    );
 
-    // use empty parameters list
+    // Use empty parameters list
     ElementPtr params(new isc::data::MapElement());
     int rcode = -1;
 
-    // case 1: send bogus command
+    // Case 1: send bogus command
     ConstElementPtr result = ControlledDhcpv4Srv::execDhcpv4ServerCommand("blah", params);
     ConstElementPtr comment = parseAnswer(rcode, result);
     EXPECT_EQ(1, rcode); // expect failure (no such command as blah)
 
-    // case 2: send shutdown command without any parameters
+    // Case 2: send shutdown command without any parameters
     result = ControlledDhcpv4Srv::execDhcpv4ServerCommand("shutdown", params);
     comment = parseAnswer(rcode, result);
     EXPECT_EQ(0, rcode); // expect success
@@ -75,13 +76,10 @@ TEST_F(CtrlDhcpv4SrvTest, commands) {
     ConstElementPtr x(new isc::data::IntElement(pid));
     params->set("pid", x);
 
-    // case 3: send shutdown command with 1 parameter: pid
+    // Case 3: send shutdown command with 1 parameter: pid
     result = ControlledDhcpv4Srv::execDhcpv4ServerCommand("shutdown", params);
     comment = parseAnswer(rcode, result);
     EXPECT_EQ(0, rcode); // expect success
-
-
-    delete srv;
 }
 
-} // end of anonymous namespace
+} // End of anonymous namespace
diff --git a/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc b/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
index 18e3ee3..cabd8b7 100644
--- a/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
+++ b/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
@@ -30,6 +30,8 @@
 #include <dhcpsrv/utils.h>
 #include <gtest/gtest.h>
 
+#include <boost/scoped_ptr.hpp>
+
 #include <fstream>
 #include <iostream>
 
@@ -142,8 +144,7 @@ public:
 
         // domain-name
         OptionDefinition def("domain-name", DHO_DOMAIN_NAME, OPT_FQDN_TYPE);
-        boost::shared_ptr<OptionCustom>
-            option_domain_name(new OptionCustom(def, Option::V4));
+        OptionCustomPtr option_domain_name(new OptionCustom(def, Option::V4));
         option_domain_name->writeFqdn("example.com");
         subnet_->addOption(option_domain_name, false, "dhcp4");
 
@@ -162,8 +163,7 @@ public:
     /// @brief checks that the response matches request
     /// @param q query (client's message)
     /// @param a answer (server's message)
-    void messageCheck(const boost::shared_ptr<Pkt4>& q,
-                      const boost::shared_ptr<Pkt4>& a) {
+    void messageCheck(const Pkt4Ptr& q, const Pkt4Ptr& a) {
         ASSERT_TRUE(q);
         ASSERT_TRUE(a);
 
@@ -493,26 +493,18 @@ public:
 TEST_F(Dhcpv4SrvTest, basic) {
 
     // Check that the base class can be instantiated
-    Dhcpv4Srv* srv = NULL;
-    ASSERT_NO_THROW({
-        srv = new Dhcpv4Srv(DHCP4_SERVER_PORT + 10000);
-    });
-    delete srv;
+    boost::scoped_ptr<Dhcpv4Srv> srv;
+    ASSERT_NO_THROW(srv.reset(new Dhcpv4Srv(DHCP4_SERVER_PORT + 10000)));
+    srv.reset();
 
     // Check that the derived class can be instantiated
-    NakedDhcpv4Srv* naked_srv = NULL;
-    ASSERT_NO_THROW({
-        naked_srv = new NakedDhcpv4Srv(DHCP4_SERVER_PORT + 10000);
-    });
+    boost::scoped_ptr<NakedDhcpv4Srv> naked_srv;
+    ASSERT_NO_THROW(
+            naked_srv.reset(new NakedDhcpv4Srv(DHCP4_SERVER_PORT + 10000)));
     EXPECT_TRUE(naked_srv->getServerID());
-    delete naked_srv;
 
-    ASSERT_NO_THROW({
-        naked_srv = new NakedDhcpv4Srv(0);
-    });
+    ASSERT_NO_THROW(naked_srv.reset(new NakedDhcpv4Srv(0)));
     EXPECT_TRUE(naked_srv->getServerID());
-
-    delete naked_srv;
 }
 
 // Verifies that DISCOVER received via relay can be processed correctly,
@@ -576,48 +568,33 @@ TEST_F(Dhcpv4SrvTest, processRequestNoClientAddr) {
 }
 
 TEST_F(Dhcpv4SrvTest, processRelease) {
-    NakedDhcpv4Srv* srv = new NakedDhcpv4Srv();
-
-    boost::shared_ptr<Pkt4> pkt(new Pkt4(DHCPRELEASE, 1234));
+    NakedDhcpv4Srv srv;
+    Pkt4Ptr pkt(new Pkt4(DHCPRELEASE, 1234));
 
     // Should not throw
-    EXPECT_NO_THROW(
-        srv->processRelease(pkt);
-    );
-
-    delete srv;
+    EXPECT_NO_THROW(srv.processRelease(pkt));
 }
 
 TEST_F(Dhcpv4SrvTest, processDecline) {
-    NakedDhcpv4Srv* srv = new NakedDhcpv4Srv();
-
-    boost::shared_ptr<Pkt4> pkt(new Pkt4(DHCPDECLINE, 1234));
+    NakedDhcpv4Srv srv;
+    Pkt4Ptr pkt(new Pkt4(DHCPDECLINE, 1234));
 
     // Should not throw
-    EXPECT_NO_THROW(
-        srv->processDecline(pkt);
-    );
-
-    delete srv;
+    EXPECT_NO_THROW(srv.processDecline(pkt));
 }
 
 TEST_F(Dhcpv4SrvTest, processInform) {
-    NakedDhcpv4Srv* srv = new NakedDhcpv4Srv();
-
-    boost::shared_ptr<Pkt4> pkt(new Pkt4(DHCPINFORM, 1234));
+    NakedDhcpv4Srv srv;
+    Pkt4Ptr pkt(new Pkt4(DHCPINFORM, 1234));
 
     // Should not throw
-    EXPECT_NO_THROW(
-        srv->processInform(pkt);
-    );
+    EXPECT_NO_THROW(srv.processInform(pkt));
 
     // Should return something
-    EXPECT_TRUE(srv->processInform(pkt));
+    EXPECT_TRUE(srv.processInform(pkt));
 
     // @todo Implement more reasonable tests before starting
     // work on processSomething() method.
-
-    delete srv;
 }
 
 TEST_F(Dhcpv4SrvTest, serverReceivedPacketName) {
diff --git a/src/bin/dhcp6/config_parser.cc b/src/bin/dhcp6/config_parser.cc
index 0c09361..f386243 100644
--- a/src/bin/dhcp6/config_parser.cc
+++ b/src/bin/dhcp6/config_parser.cc
@@ -1481,13 +1481,29 @@ private:
         std::string iface;
         try {
             iface = string_values_.getParam("interface");
-        } catch (DhcpConfigError) {
+        } catch (const DhcpConfigError&) {
             // iface not mandatory so swallow the exception
         }
 
-        /// @todo: Convert this to logger once the parser is working reliably
+        // Get interface-id option content. For now we support string
+        // represenation only
+        std::string ifaceid;
+        try {
+            ifaceid = string_values_.getParam("interface-id");
+        } catch (const DhcpConfigError&) {
+            // interface-id is not mandatory
+        }
+
+        if (!iface.empty() && !ifaceid.empty()) {
+            isc_throw(isc::dhcp::DhcpConfigError,
+                      "parser error: interface (defined for locally reachable "
+                      "subnets) and interface-id (defined for subnets reachable"
+                      " via relays) cannot be defined at the same time for "
+                      "subnet " << addr.toText() << "/" << (int)len);
+        }
+
         stringstream tmp;
-        tmp << addr.toText() << "/" << (int)len
+        tmp << addr.toText() << "/" << static_cast<int>(len)
             << " with params t1=" << t1 << ", t2=" << t2 << ", pref="
             << pref << ", valid=" << valid;
 
@@ -1512,6 +1528,13 @@ private:
             subnet_->setIface(iface);
         }
 
+        // Configure interface-id for remote interfaces, if defined
+        if (!ifaceid.empty()) {
+            OptionBuffer tmp(ifaceid.begin(), ifaceid.end());
+            OptionPtr opt(new Option(Option::V6, D6O_INTERFACE_ID, tmp));
+            subnet_->setInterfaceId(opt);
+        }
+
         // 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
@@ -1591,6 +1614,7 @@ private:
         factories["pool"] = PoolParser::factory;
         factories["option-data"] = OptionDataListParser::factory;
         factories["interface"] = StringParser::factory;
+        factories["interface-id"] = StringParser::factory;
 
         FactoryMap::iterator f = factories.find(config_id);
         if (f == factories.end()) {
diff --git a/src/bin/dhcp6/dhcp6.spec b/src/bin/dhcp6/dhcp6.spec
index 1129aec..bb5de0a 100644
--- a/src/bin/dhcp6/dhcp6.spec
+++ b/src/bin/dhcp6/dhcp6.spec
@@ -199,6 +199,12 @@
                   "item_default": ""
                 },
 
+                { "item_name": "interface-id",
+                  "item_type": "string",
+                  "item_optional": false,
+                  "item_default": ""
+                },
+
                 { "item_name": "renew-timer",
                   "item_type": "integer",
                   "item_optional": false,
diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc
index 75a5337..86f4d8f 100644
--- a/src/bin/dhcp6/dhcp6_srv.cc
+++ b/src/bin/dhcp6/dhcp6_srv.cc
@@ -403,8 +403,13 @@ Dhcpv6Srv::copyDefaultOptions(const Pkt6Ptr& question, Pkt6Ptr& answer) {
     if (clientid) {
         answer->addOption(clientid);
     }
+    /// @todo: Should throw if there is no client-id (except anonymous INF-REQUEST)
+
+    // If this is a relayed message, we need to copy relay information
+    if (!question->relay_info_.empty()) {
+        answer->copyRelayInfo(question);
+    }
 
-    // TODO: Should throw if there is no client-id (except anonymous INF-REQUEST)
 }
 
 void
@@ -523,16 +528,37 @@ Dhcpv6Srv::sanityCheck(const Pkt6Ptr& pkt, RequirementLevel clientid,
 Subnet6Ptr
 Dhcpv6Srv::selectSubnet(const Pkt6Ptr& question) {
 
-    /// @todo: pass interface information only if received direct (non-relayed) message
+    Subnet6Ptr subnet;
 
-    // Try to find a subnet if received packet from a directly connected client
-    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(question->getIface());
-    if (subnet) {
-        return (subnet);
-    }
+    if (question->relay_info_.empty()) {
+        // This is a direct (non-relayed) message
+
+        // Try to find a subnet if received packet from a directly connected client
+        subnet = CfgMgr::instance().getSubnet6(question->getIface());
+        if (!subnet) {
+            // If no subnet was found, try to find it based on remote address
+            subnet = CfgMgr::instance().getSubnet6(question->getRemoteAddr());
+        }
+    } else {
 
-    // If no subnet was found, try to find it based on remote address
-    subnet = CfgMgr::instance().getSubnet6(question->getRemoteAddr());
+        // This is a relayed message
+        OptionPtr interface_id = question->getAnyRelayOption(D6O_INTERFACE_ID,
+                                                             Pkt6::RELAY_GET_FIRST);
+        if (interface_id) {
+            subnet = CfgMgr::instance().getSubnet6(interface_id);
+        }
+
+        if (!subnet) {
+            // If no interface-id was specified (or not configured on server), let's
+            // try address matching
+            IOAddress link_addr = question->relay_info_.back().linkaddr_;
+
+            // if relay filled in link_addr field, then let's use it
+            if (link_addr != IOAddress("::")) {
+                subnet = CfgMgr::instance().getSubnet6(link_addr);
+            }
+        }
+    }
 
     return (subnet);
 }
diff --git a/src/bin/dhcp6/tests/config_parser_unittest.cc b/src/bin/dhcp6/tests/config_parser_unittest.cc
index f4ebf0c..d9c5859 100644
--- a/src/bin/dhcp6/tests/config_parser_unittest.cc
+++ b/src/bin/dhcp6/tests/config_parser_unittest.cc
@@ -277,13 +277,13 @@ public:
                             expected_data_len));
     }
 
-    int rcode_;
-    Dhcpv6Srv srv_;
+    int rcode_; ///< return core (see @ref isc::config::parseAnswer)
+    Dhcpv6Srv srv_; ///< instance of the Dhcp6Srv used during tests
 
-    ConstElementPtr comment_;
+    ConstElementPtr comment_; ///< comment (see @ref isc::config::parseAnswer)
 
-    string valid_iface_;
-    string bogus_iface_;
+    string valid_iface_; ///< name of a valid network interface (present in system)
+    string bogus_iface_; ///< name of a invalid network interface (not present in system)
 };
 
 // Goal of this test is a verification if a very simple config update
@@ -500,6 +500,104 @@ TEST_F(Dhcp6ParserTest, interfaceGlobal) {
     EXPECT_EQ(1, rcode_);
 }
 
+
+// This test checks if it is possible to define a subnet with an
+// interface-id option defined.
+TEST_F(Dhcp6ParserTest, subnetInterfaceId) {
+
+    const string valid_interface_id = "foobar";
+    const string bogus_interface_id = "blah";
+
+    // There should be at least one interface
+
+    const string config = "{ "
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"pool\": [ \"2001:db8:1::1 - 2001:db8:1::ffff\" ],"
+        "    \"interface-id\": \"" + valid_interface_id + "\","
+        "    \"subnet\": \"2001:db8:1::/64\" } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    ElementPtr json = Element::fromJSON(config);
+
+    ConstElementPtr status;
+    EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+
+    // Returned value should be 0 (configuration success)
+    ASSERT_TRUE(status);
+    comment_ = parseAnswer(rcode_, status);
+    EXPECT_EQ(0, rcode_);
+
+    // Try to get a subnet based on bogus interface-id option
+    OptionBuffer tmp(bogus_interface_id.begin(), bogus_interface_id.end());
+    OptionPtr ifaceid(new Option(Option::V6, D6O_INTERFACE_ID, tmp));
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(ifaceid);
+    EXPECT_FALSE(subnet);
+
+    // Now try to get subnet for valid interface-id value
+    tmp = OptionBuffer(valid_interface_id.begin(), valid_interface_id.end());
+    ifaceid.reset(new Option(Option::V6, D6O_INTERFACE_ID, tmp));
+    subnet = CfgMgr::instance().getSubnet6(ifaceid);
+    ASSERT_TRUE(subnet);
+    EXPECT_TRUE(ifaceid->equal(subnet->getInterfaceId()));
+}
+
+
+// This test checks if it is not allowed to define global interface
+// parameter.
+TEST_F(Dhcp6ParserTest, interfaceIdGlobal) {
+
+    const string config = "{ \"interface\": [ \"all\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"interface-id\": \"foobar\"," // Not valid. Can be defined in subnet only
+        "\"subnet6\": [ { "
+        "    \"pool\": [ \"2001:db8:1::1 - 2001:db8:1::ffff\" ],"
+        "    \"subnet\": \"2001:db8:1::/64\" } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    ElementPtr json = Element::fromJSON(config);
+
+    ConstElementPtr status;
+    EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+
+    // Returned value should be 1 (parse error)
+    ASSERT_TRUE(status);
+    comment_ = parseAnswer(rcode_, status);
+    EXPECT_EQ(1, rcode_);
+}
+
+// This test checks if it is not possible to define a subnet with an
+// interface (i.e. local subnet) and interface-id (remote subnet) defined.
+TEST_F(Dhcp6ParserTest, subnetInterfaceAndInterfaceId) {
+
+    const string config = "{ \"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"pool\": [ \"2001:db8:1::1 - 2001:db8:1::ffff\" ],"
+        "    \"interface\": \"" + valid_iface_ + "\","
+        "    \"interface-id\": \"foobar\","
+        "    \"subnet\": \"2001:db8:1::/64\" } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    ElementPtr json = Element::fromJSON(config);
+
+    ConstElementPtr status;
+    EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+
+    // Returned value should be 1 (configuration error)
+    ASSERT_TRUE(status);
+    comment_ = parseAnswer(rcode_, status);
+    EXPECT_EQ(1, rcode_);
+
+}
+
+
+
 // Test verifies that a subnet with pool values that do not belong to that
 // pool are rejected.
 TEST_F(Dhcp6ParserTest, poolOutOfSubnet) {
diff --git a/src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc b/src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc
index ea359fc..9209de6 100644
--- a/src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc
+++ b/src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -18,6 +18,7 @@
 #include <dhcp6/ctrl_dhcp6_srv.h>
 #include <config/ccsession.h>
 
+#include <boost/scoped_ptr.hpp>
 #include <gtest/gtest.h>
 
 #include <iostream>
@@ -52,12 +53,12 @@ public:
 
 TEST_F(CtrlDhcpv6SrvTest, commands) {
 
-    ControlledDhcpv6Srv* srv = NULL;
-    ASSERT_NO_THROW({
-        srv = new ControlledDhcpv6Srv(DHCP6_SERVER_PORT + 10000);
-    });
+    boost::scoped_ptr<ControlledDhcpv6Srv> srv;
+    ASSERT_NO_THROW(
+        srv.reset(new ControlledDhcpv6Srv(DHCP6_SERVER_PORT + 10000))
+    );
 
-    // use empty parameters list
+    // Use empty parameters list
     ElementPtr params(new isc::data::MapElement());
     int rcode = -1;
 
@@ -78,10 +79,7 @@ TEST_F(CtrlDhcpv6SrvTest, commands) {
     // case 3: send shutdown command with 1 parameter: pid
     result = ControlledDhcpv6Srv::execDhcpv6ServerCommand("shutdown", params);
     comment = parseAnswer(rcode, result);
-    EXPECT_EQ(0, rcode); // expect success
-
-
-    delete srv;
+    EXPECT_EQ(0, rcode); // Expect success
 }
 
 } // end of anonymous namespace
diff --git a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
index 03b2c0c..328a3c5 100644
--- a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
+++ b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
@@ -80,7 +80,7 @@ public:
 static const char* DUID_FILE = "server-id-test.txt";
 
 // test fixture for any tests requiring blank/empty configuration
-// serves as base class for additional tests 
+// serves as base class for additional tests
 class NakedDhcpv6SrvTest : public ::testing::Test {
 public:
 
@@ -98,6 +98,16 @@ public:
         return (ia);
     }
 
+    /// @brief generates interface-id option, based on text
+    ///
+    /// @param iface_id textual representation of the interface-id content
+    ///
+    /// @return pointer to the option object
+    OptionPtr generateInterfaceId(const string& iface_id) {
+        OptionBuffer tmp(iface_id.begin(), iface_id.end());
+        return OptionPtr(new Option(Option::V6, D6O_INTERFACE_ID, tmp));
+    }
+
     // Generate client-id option
     OptionPtr generateClientId(size_t duid_size = 32) {
 
@@ -136,12 +146,12 @@ public:
 
     // Checks if server response is a NAK
     void checkNakResponse(const Pkt6Ptr& rsp, uint8_t expected_message_type,
-                          uint32_t expected_transid, 
+                          uint32_t expected_transid,
                           uint16_t expected_status_code) {
         // Check if we get response at all
         checkResponse(rsp, expected_message_type, expected_transid);
 
-        // Check that IA_NA was returned 
+        // Check that IA_NA was returned
         OptionPtr option_ia_na = rsp->getOption(D6O_IA_NA);
         ASSERT_TRUE(option_ia_na);
 
@@ -169,7 +179,7 @@ public:
         EXPECT_EQ(0, ia->getT1());
         EXPECT_EQ(0, ia->getT2());
 
-        boost::shared_ptr<OptionCustom> status =
+        OptionCustomPtr status =
             boost::dynamic_pointer_cast<OptionCustom>(ia->getOption(D6O_STATUS_CODE));
 
         // It is ok to not include status success as this is the default behavior
@@ -189,7 +199,7 @@ public:
     }
 
     void checkMsgStatusCode(const Pkt6Ptr& msg, uint16_t expected_status) {
-        boost::shared_ptr<OptionCustom> status =
+        OptionCustomPtr status =
             boost::dynamic_pointer_cast<OptionCustom>(msg->getOption(D6O_STATUS_CODE));
 
         // It is ok to not include status success as this is the default behavior
@@ -227,7 +237,7 @@ public:
     ConstElementPtr comment_;
 };
 
-// Provides suport for tests against a preconfigured subnet6                       
+// Provides suport for tests against a preconfigured subnet6
 // extends upon NakedDhcp6SrvTest
 class Dhcpv6SrvTest : public NakedDhcpv6SrvTest {
 public:
@@ -254,7 +264,7 @@ public:
             ADD_FAILURE() << "IA_NA option not present in response";
             return (boost::shared_ptr<Option6IAAddr>());
         }
- 
+
         boost::shared_ptr<Option6IA> ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
         if (!ia) {
             ADD_FAILURE() << "IA_NA cannot convert option ptr to Option6";
@@ -264,7 +274,7 @@ public:
         EXPECT_EQ(expected_iaid, ia->getIAID());
         EXPECT_EQ(expected_t1, ia->getT1());
         EXPECT_EQ(expected_t2, ia->getT2());
- 
+
         tmp = ia->getOption(D6O_IAADDR);
         boost::shared_ptr<Option6IAAddr> addr = boost::dynamic_pointer_cast<Option6IAAddr>(tmp);
         return (addr);
@@ -320,10 +330,10 @@ public:
 };
 
 // This test verifies that incoming SOLICIT can be handled properly when
-// there are no subnets configured. 
+// there are no subnets configured.
 //
-// This test sends a SOLICIT and the expected response 
-// is an ADVERTISE with STATUS_NoAddrsAvail and no address provided in the 
+// This test sends a SOLICIT and the expected response
+// is an ADVERTISE with STATUS_NoAddrsAvail and no address provided in the
 // response
 TEST_F(NakedDhcpv6SrvTest, SolicitNoSubnet) {
     NakedDhcpv6Srv srv(0);
@@ -342,10 +352,10 @@ TEST_F(NakedDhcpv6SrvTest, SolicitNoSubnet) {
 }
 
 // This test verifies that incoming REQUEST can be handled properly when
-// there are no subnets configured. 
+// there are no subnets configured.
 //
-// This test sends a REQUEST and the expected response 
-// is an REPLY with STATUS_NoAddrsAvail and no address provided in the 
+// This test sends a REQUEST and the expected response
+// is an REPLY with STATUS_NoAddrsAvail and no address provided in the
 // response
 TEST_F(NakedDhcpv6SrvTest, RequestNoSubnet) {
     NakedDhcpv6Srv srv(0);
@@ -376,8 +386,8 @@ TEST_F(NakedDhcpv6SrvTest, RequestNoSubnet) {
 // This test verifies that incoming RENEW can be handled properly, even when
 // no subnets are configured.
 //
-// This test sends a RENEW and the expected response 
-// is an REPLY with STATUS_NoBinding and no address provided in the 
+// This test sends a RENEW and the expected response
+// is an REPLY with STATUS_NoBinding and no address provided in the
 // response
 TEST_F(NakedDhcpv6SrvTest, RenewNoSubnet) {
     NakedDhcpv6Srv srv(0);
@@ -411,8 +421,8 @@ TEST_F(NakedDhcpv6SrvTest, RenewNoSubnet) {
 // This test verifies that incoming RELEASE can be handled properly, even when
 // no subnets are configured.
 //
-// This test sends a RELEASE and the expected response 
-// is an REPLY with STATUS_NoBinding and no address provided in the 
+// This test sends a RELEASE and the expected response
+// is an REPLY with STATUS_NoBinding and no address provided in the
 // response
 TEST_F(NakedDhcpv6SrvTest, ReleaseNoSubnet) {
     NakedDhcpv6Srv srv(0);
@@ -586,7 +596,7 @@ TEST_F(Dhcpv6SrvTest, advertiseOptions) {
     sol->addOption(clientid);
 
     // Pass it to the server and get an advertise
-    boost::shared_ptr<Pkt6> adv = srv.processSolicit(sol);
+    Pkt6Ptr adv = srv.processSolicit(sol);
 
     // check if we get response at all
     ASSERT_TRUE(adv);
@@ -678,6 +688,7 @@ TEST_F(Dhcpv6SrvTest, SolicitBasic) {
     // check that IA_NA was returned and that there's an address included
     boost::shared_ptr<Option6IAAddr> addr = checkIA_NA(reply, 234, subnet_->getT1(),
                                                 subnet_->getT2());
+    ASSERT_TRUE(addr);
 
     // Check that the assigned address is indeed from the configured pool
     checkIAAddr(addr, addr->getAddress(), subnet_->getPreferred(), subnet_->getValid());
@@ -731,6 +742,7 @@ TEST_F(Dhcpv6SrvTest, SolicitHint) {
     // check that IA_NA was returned and that there's an address included
     boost::shared_ptr<Option6IAAddr> addr = checkIA_NA(reply, 234, subnet_->getT1(),
                                                 subnet_->getT2());
+    ASSERT_TRUE(addr);
 
     // check that we've got the address we requested
     checkIAAddr(addr, hint, subnet_->getPreferred(), subnet_->getValid());
@@ -779,6 +791,7 @@ TEST_F(Dhcpv6SrvTest, SolicitInvalidHint) {
     // check that IA_NA was returned and that there's an address included
     boost::shared_ptr<Option6IAAddr> addr = checkIA_NA(reply, 234, subnet_->getT1(),
                                                 subnet_->getT2());
+    ASSERT_TRUE(addr);
 
     // Check that the assigned address is indeed from the configured pool
     checkIAAddr(addr, addr->getAddress(), subnet_->getPreferred(), subnet_->getValid());
@@ -840,6 +853,9 @@ TEST_F(Dhcpv6SrvTest, ManySolicits) {
                                                 subnet_->getT2());
     boost::shared_ptr<Option6IAAddr> addr3 = checkIA_NA(reply3, 3, subnet_->getT1(),
                                                 subnet_->getT2());
+    ASSERT_TRUE(addr1);
+    ASSERT_TRUE(addr2);
+    ASSERT_TRUE(addr3);
 
     // Check that the assigned address is indeed from the configured pool
     checkIAAddr(addr1, addr1->getAddress(), subnet_->getPreferred(), subnet_->getValid());
@@ -910,6 +926,7 @@ TEST_F(Dhcpv6SrvTest, RequestBasic) {
     // check that IA_NA was returned and that there's an address included
     boost::shared_ptr<Option6IAAddr> addr = checkIA_NA(reply, 234, subnet_->getT1(),
                                                 subnet_->getT2());
+    ASSERT_TRUE(addr);
 
     // check that we've got the address we requested
     checkIAAddr(addr, hint, subnet_->getPreferred(), subnet_->getValid());
@@ -934,6 +951,8 @@ TEST_F(Dhcpv6SrvTest, RequestBasic) {
 TEST_F(Dhcpv6SrvTest, ManyRequests) {
     NakedDhcpv6Srv srv(0);
 
+    ASSERT_TRUE(subnet_);
+
     Pkt6Ptr req1 = Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234));
     Pkt6Ptr req2 = Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 2345));
     Pkt6Ptr req3 = Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 3456));
@@ -978,6 +997,10 @@ TEST_F(Dhcpv6SrvTest, ManyRequests) {
     boost::shared_ptr<Option6IAAddr> addr3 = checkIA_NA(reply3, 3, subnet_->getT1(),
                                                 subnet_->getT2());
 
+    ASSERT_TRUE(addr1);
+    ASSERT_TRUE(addr2);
+    ASSERT_TRUE(addr3);
+
     // Check that the assigned address is indeed from the configured pool
     checkIAAddr(addr1, addr1->getAddress(), subnet_->getPreferred(), subnet_->getValid());
     checkIAAddr(addr2, addr2->getAddress(), subnet_->getPreferred(), subnet_->getValid());
@@ -1066,6 +1089,8 @@ TEST_F(Dhcpv6SrvTest, RenewBasic) {
     boost::shared_ptr<Option6IAAddr> addr_opt = checkIA_NA(reply, 234, subnet_->getT1(),
                                                            subnet_->getT2());
 
+    ASSERT_TRUE(addr_opt);
+
     // Check that we've got the address we requested
     checkIAAddr(addr_opt, addr, subnet_->getPreferred(), subnet_->getValid());
 
@@ -1592,6 +1617,113 @@ TEST_F(Dhcpv6SrvTest, selectSubnetIface) {
     EXPECT_EQ(subnet3, srv.selectSubnet(pkt));
 }
 
+// This test verifies if selectSubnet() selects proper subnet for a given
+// linkaddr in RELAY-FORW message
+TEST_F(Dhcpv6SrvTest, selectSubnetRelayLinkaddr) {
+    NakedDhcpv6Srv srv(0);
+
+    Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:1::"), 48, 1, 2, 3, 4));
+    Subnet6Ptr subnet2(new Subnet6(IOAddress("2001:db8:2::"), 48, 1, 2, 3, 4));
+    Subnet6Ptr subnet3(new Subnet6(IOAddress("2001:db8:3::"), 48, 1, 2, 3, 4));
+
+    Pkt6::RelayInfo relay;
+    relay.linkaddr_ = IOAddress("2001:db8:2::1234");
+    relay.peeraddr_ = IOAddress("fe80::1");
+
+    // CASE 1: We have only one subnet defined and we received relayed traffic.
+    // The only available subnet should NOT be selected.
+    CfgMgr::instance().deleteSubnets6();
+    CfgMgr::instance().addSubnet6(subnet1); // just a single subnet
+
+    Pkt6Ptr pkt = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+    pkt->relay_info_.push_back(relay);
+
+    Subnet6Ptr selected = srv.selectSubnet(pkt);
+    EXPECT_FALSE(selected);
+
+    // CASE 2: We have three subnets defined and we received relayed traffic.
+    // Nothing should be selected.
+    CfgMgr::instance().deleteSubnets6();
+    CfgMgr::instance().addSubnet6(subnet1);
+    CfgMgr::instance().addSubnet6(subnet2);
+    CfgMgr::instance().addSubnet6(subnet3);
+    selected = srv.selectSubnet(pkt);
+    EXPECT_EQ(selected, subnet2);
+
+    // CASE 3: We have three subnets defined and we received relayed traffic
+    // that came out of subnet 2. We should select subnet2 then
+    CfgMgr::instance().deleteSubnets6();
+    CfgMgr::instance().addSubnet6(subnet1);
+    CfgMgr::instance().addSubnet6(subnet2);
+    CfgMgr::instance().addSubnet6(subnet3);
+
+    // Source of the packet should have no meaning. Selection is based
+    // on linkaddr field in the relay
+    pkt->setRemoteAddr(IOAddress("2001:db8:1::baca"));
+    selected = srv.selectSubnet(pkt);
+    EXPECT_EQ(selected, subnet2);
+
+    // CASE 4: We have three subnets defined and we received relayed traffic
+    // that came out of undefined subnet. We should select nothing
+    CfgMgr::instance().deleteSubnets6();
+    CfgMgr::instance().addSubnet6(subnet1);
+    CfgMgr::instance().addSubnet6(subnet2);
+    CfgMgr::instance().addSubnet6(subnet3);
+    pkt->relay_info_.clear();
+    relay.linkaddr_ = IOAddress("2001:db8:4::1234");
+    pkt->relay_info_.push_back(relay);
+    selected = srv.selectSubnet(pkt);
+    EXPECT_FALSE(selected);
+
+}
+
+// This test verifies if selectSubnet() selects proper subnet for a given
+// interface-id option
+TEST_F(Dhcpv6SrvTest, selectSubnetRelayInterfaceId) {
+    NakedDhcpv6Srv srv(0);
+
+    Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:1::"), 48, 1, 2, 3, 4));
+    Subnet6Ptr subnet2(new Subnet6(IOAddress("2001:db8:2::"), 48, 1, 2, 3, 4));
+    Subnet6Ptr subnet3(new Subnet6(IOAddress("2001:db8:3::"), 48, 1, 2, 3, 4));
+
+    subnet1->setInterfaceId(generateInterfaceId("relay1"));
+    subnet2->setInterfaceId(generateInterfaceId("relay2"));
+
+    // CASE 1: We have only one subnet defined and it is for interface-id "relay1"
+    // Packet came with interface-id "relay2". We should not select subnet1
+    CfgMgr::instance().deleteSubnets6();
+    CfgMgr::instance().addSubnet6(subnet1); // just a single subnet
+
+    Pkt6Ptr pkt = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+    Pkt6::RelayInfo relay;
+    relay.linkaddr_ = IOAddress("2001:db8:2::1234");
+    relay.peeraddr_ = IOAddress("fe80::1");
+    OptionPtr opt = generateInterfaceId("relay2");
+    relay.options_.insert(make_pair(opt->getType(), opt));
+    pkt->relay_info_.push_back(relay);
+
+    // There is only one subnet configured and we are outside of that subnet
+    Subnet6Ptr selected = srv.selectSubnet(pkt);
+    EXPECT_FALSE(selected);
+
+    // CASE 2: We have only one subnet defined and it is for interface-id "relay2"
+    // Packet came with interface-id "relay2". We should select it
+    CfgMgr::instance().deleteSubnets6();
+    CfgMgr::instance().addSubnet6(subnet2); // just a single subnet
+    selected = srv.selectSubnet(pkt);
+    EXPECT_EQ(selected, subnet2);
+
+    // CASE 3: We have only 3 subnets defined: one remote for interface-id "relay1",
+    // one remote for interface-id "relay2" and third local
+    // packet comes with interface-id "relay2". We should select subnet2
+    CfgMgr::instance().deleteSubnets6();
+    CfgMgr::instance().addSubnet6(subnet1);
+    CfgMgr::instance().addSubnet6(subnet2);
+    CfgMgr::instance().addSubnet6(subnet3);
+
+    EXPECT_EQ(subnet2, srv.selectSubnet(pkt));
+}
+
 // This test verifies if the server-id disk operations (read, write) are
 // working properly.
 TEST_F(Dhcpv6SrvTest, ServerID) {
diff --git a/src/bin/resolver/bench/fake_resolution.cc b/src/bin/resolver/bench/fake_resolution.cc
index c08e45a..e3d54a9 100644
--- a/src/bin/resolver/bench/fake_resolution.cc
+++ b/src/bin/resolver/bench/fake_resolution.cc
@@ -20,7 +20,7 @@
 #include <boost/bind.hpp>
 #include <boost/foreach.hpp>
 #include <algorithm>
-#include <cstdlib>
+#include <stdlib.h> // not cstdlib, which doesn't officially have random()
 
 namespace isc {
 namespace resolver {
@@ -144,7 +144,7 @@ public:
         timer_(timer)
     {}
     void trigger() {
-        query_->outstanding_ = false;
+        query_->answerReceived();
         callback_();
         // We are not needed any more.
         delete this;
diff --git a/src/bin/resolver/bench/fake_resolution.h b/src/bin/resolver/bench/fake_resolution.h
index c2011d3..cf2219c 100644
--- a/src/bin/resolver/bench/fake_resolution.h
+++ b/src/bin/resolver/bench/fake_resolution.h
@@ -142,6 +142,13 @@ public:
         }
         interface_ = &dst_interface;
     }
+    /// \brief The answer for upstream query was received
+    ///
+    /// This should be called from within the FakeInterface only.
+    /// It marks that the query from upstream was answered.
+    void answerReceived() {
+        outstanding_ = false;
+    }
 private:
     // The scheduled steps for this task.
     typedef std::pair<Task, size_t> Step;
diff --git a/src/bin/stats/tests/Makefile.am b/src/bin/stats/tests/Makefile.am
index a5ff4e5..06d11a1 100644
--- a/src/bin/stats/tests/Makefile.am
+++ b/src/bin/stats/tests/Makefile.am
@@ -1,7 +1,7 @@
 SUBDIRS = testdata .
 
 PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
-PYTESTS = b10-stats_test.py b10-stats-httpd_test.py
+PYTESTS = stats_test.py stats-httpd_test.py
 EXTRA_DIST = $(PYTESTS) test_utils.py
 CLEANFILES = test_utils.pyc
 
diff --git a/src/bin/stats/tests/b10-stats-httpd_test.py b/src/bin/stats/tests/b10-stats-httpd_test.py
deleted file mode 100644
index 9a463ab..0000000
--- a/src/bin/stats/tests/b10-stats-httpd_test.py
+++ /dev/null
@@ -1,1082 +0,0 @@
-# Copyright (C) 2011-2012  Internet Systems Consortium.
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
-# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
-# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-"""
-In each of these tests we start several virtual components. They are
-not the real components, no external processes are started. They are
-just simple mock objects running each in its own thread and pretending
-to be bind10 modules. This helps testing the stats http server in a
-close to real environment.
-"""
-
-import unittest
-import os
-import imp
-import socket
-import errno
-import select
-import string
-import time
-import threading
-import http.client
-import xml.etree.ElementTree
-import random
-import urllib.parse
-import sys
-# load this module for xml validation with xsd. For this test, an
-# installation of lxml is required in advance. See http://lxml.de/.
-try:
-    from lxml import etree as lxml_etree
-except ImportError:
-    lxml_etree = None
-
-import isc
-import isc.log
-import stats_httpd
-import stats
-from test_utils import BaseModules, ThreadingServerManager, MyStats,\
-                       MyStatsHttpd, SignalHandler,\
-                       send_command, CONST_BASETIME
-from isc.testutils.ccsession_mock import MockModuleCCSession
-
-# This test suite uses xml.etree.ElementTree.XMLParser via
-# xml.etree.ElementTree.parse. On the platform where expat isn't
-# installed, ImportError is raised and it's failed. Check expat is
-# available before the test invocation. Skip this test if it's
-# unavailable.
-try:
-    # ImportError raised if xpat is unavailable
-    xml_parser = xml.etree.ElementTree.XMLParser()
-except ImportError:
-    xml_parser = None
-
-# set XML Namespaces for testing
-XMLNS_XSL = "http://www.w3.org/1999/XSL/Transform"
-XMLNS_XHTML = "http://www.w3.org/1999/xhtml"
-XMLNS_XSD = "http://www.w3.org/2001/XMLSchema"
-XMLNS_XSI = stats_httpd.XMLNS_XSI
-
-DUMMY_DATA = {
-    'Init' : {
-        "boot_time": time.strftime('%Y-%m-%dT%H:%M:%SZ', CONST_BASETIME)
-        },
-    'Auth' : {
-        "queries.tcp": 6,
-        "queries.udp": 4,
-        "queries.perzone": [{
-                "zonename": "test1.example",
-                "queries.tcp": 10,
-                "queries.udp": 8
-                }, {
-                "zonename": "test2.example",
-                "queries.tcp": 8,
-                "queries.udp": 6
-                }],
-        "nds_queries.perzone": {
-                "test10.example": {
-                    "queries.tcp": 10,
-                    "queries.udp": 8
-                  },
-                "test20.example": {
-                    "queries.tcp": 8,
-                    "queries.udp": 6
-                  }
-                }
-        },
-    'Stats' : {
-        "report_time": time.strftime('%Y-%m-%dT%H:%M:%SZ', CONST_BASETIME),
-        "boot_time": time.strftime('%Y-%m-%dT%H:%M:%SZ', CONST_BASETIME),
-        "last_update_time": time.strftime('%Y-%m-%dT%H:%M:%SZ', CONST_BASETIME),
-        "lname": "4d70d40a_c at host",
-        "timestamp": time.mktime(CONST_BASETIME)
-        }
-    }
-
-def get_availaddr(address='127.0.0.1', port=8001):
-    """returns a tuple of address and port which is available to
-    listen on the platform. The first argument is a address for
-    search. The second argument is a port for search. If a set of
-    address and port is failed on the search for the availability, the
-    port number is increased and it goes on the next trial until the
-    available set of address and port is looked up. If the port number
-    reaches over 65535, it may stop the search and raise a
-    OverflowError exception."""
-    while True:
-        for addr in socket.getaddrinfo(
-            address, port, 0,
-            socket.SOCK_STREAM, socket.IPPROTO_TCP):
-            sock = socket.socket(addr[0], socket.SOCK_STREAM)
-            try:
-                sock.bind((address, port))
-                return (address, port)
-            except socket.error:
-                continue
-            finally:
-                if sock: sock.close()
-        # This address and port number are already in use.
-        # next port number is added
-        port = port + 1
-
-def is_ipv6_enabled(address='::1', port=8001):
-    """checks IPv6 enabled on the platform. address for check is '::1'
-    and port for check is random number between 8001 and
-    65535. Retrying is 3 times even if it fails. The built-in socket
-    module provides a 'has_ipv6' parameter, but it's not used here
-    because there may be a situation where the value is True on an
-    environment where the IPv6 config is disabled."""
-    for p in random.sample(range(port, 65535), 3):
-        try:
-            sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
-            sock.bind((address, p))
-            return True
-        except socket.error:
-            continue
-        finally:
-            if sock: sock.close()
-    return False
-
-class TestItemNameList(unittest.TestCase):
-
-    def test_item_name_list(self):
-        # for a one-element list
-        self.assertEqual(['a'],
-                         stats_httpd.item_name_list({'a':1}, 'a'))
-        # for a dict under a dict
-        self.assertEqual(['a','a/b'],
-                         stats_httpd.item_name_list({'a':{'b':1}}, 'a'))
-        self.assertEqual(['a/b'],
-                         stats_httpd.item_name_list({'a':{'b':1}}, 'a/b'))
-        self.assertEqual(['a','a/b','a/b/c'],
-                         stats_httpd.item_name_list({'a':{'b':{'c':1}}}, 'a'))
-        self.assertEqual(['a/b','a/b/c'],
-                         stats_httpd.item_name_list({'a':{'b':{'c':1}}},
-                                                    'a/b'))
-        self.assertEqual(['a/b/c'],
-                         stats_httpd.item_name_list({'a':{'b':{'c':1}}},
-                                                    'a/b/c'))
-        # for a list under a dict
-        self.assertEqual(['a[2]'],
-                         stats_httpd.item_name_list({'a':[1,2,3]}, 'a[2]'))
-        self.assertEqual(['a', 'a[0]', 'a[1]', 'a[2]'],
-                         stats_httpd.item_name_list({'a':[1,2,3]}, 'a'))
-        self.assertEqual(['a', 'a[0]', 'a[1]', 'a[2]'],
-                         stats_httpd.item_name_list({'a':[1,2,3]}, ''))
-        # for a list under a dict under a dict
-        self.assertEqual(['a', 'a/b', 'a/b[0]', 'a/b[1]', 'a/b[2]'],
-                         stats_httpd.item_name_list({'a':{'b':[1,2,3]}}, 'a'))
-        self.assertEqual(['a', 'a/b', 'a/b[0]', 'a/b[1]', 'a/b[2]'],
-                         stats_httpd.item_name_list({'a':{'b':[1,2,3]}}, ''))
-        self.assertEqual(['a/b', 'a/b[0]', 'a/b[1]', 'a/b[2]'],
-                         stats_httpd.item_name_list({'a':{'b':[1,2,3]}}, 'a/b'))
-        # for a mixed case of the above
-        self.assertEqual(['a', 'a/b', 'a/b[0]', 'a/b[1]', 'a/b[2]', 'a/c'],
-                         stats_httpd.item_name_list(
-                {'a':{'b':[1,2,3], 'c':1}}, 'a'))
-        self.assertEqual(['a/b', 'a/b[0]', 'a/b[1]', 'a/b[2]'],
-                         stats_httpd.item_name_list(
-                {'a':{'b':[1,2,3], 'c':1}}, 'a/b'))
-        self.assertEqual(['a/c'],
-                         stats_httpd.item_name_list(
-                {'a':{'b':[1,2,3], 'c':1}}, 'a/c'))
-        # for specifying a wrong identifier which is not found in
-        # element
-        self.assertRaises(isc.cc.data.DataNotFoundError,
-                         stats_httpd.item_name_list, {'x':1}, 'a')
-        # for specifying a string in element and an empty string in
-        # identifier
-        self.assertEqual([],
-                         stats_httpd.item_name_list('a', ''))
-        # for specifying empty strings in element and identifier
-        self.assertEqual([],
-                         stats_httpd.item_name_list('', ''))
-        # for specifying wrong element, which is an non-empty string,
-        # and an non-empty string in identifier
-        self.assertRaises(isc.cc.data.DataTypeError,
-                         stats_httpd.item_name_list, 'a', 'a')
-        # for specifying None in element and identifier
-        self.assertRaises(isc.cc.data.DataTypeError,
-                         stats_httpd.item_name_list, None, None)
-        # for specifying non-dict in element
-        self.assertRaises(isc.cc.data.DataTypeError,
-                         stats_httpd.item_name_list, [1,2,3], 'a')
-        self.assertRaises(isc.cc.data.DataTypeError,
-                         stats_httpd.item_name_list, [1,2,3], '')
-        # for checking key names sorted which consist of element
-        num = 11
-        keys = [ 'a', 'aa', 'b' ]
-        keys.sort(reverse=True)
-        dictlist = dict([ (k, list(range(num))) for k in keys ])
-        keys.sort()
-        ans = []
-        for k in keys:
-            ans += [k] + [ '%s[%d]' % (k, i) for i in range(num) ]
-        self.assertEqual(ans,
-                         stats_httpd.item_name_list(dictlist, ''))
-
-class TestHttpHandler(unittest.TestCase):
-    """Tests for HttpHandler class"""
-    def setUp(self):
-        # set the signal handler for deadlock
-        self.sig_handler = SignalHandler(self.fail)
-        self.base = BaseModules()
-        self.stats_server = ThreadingServerManager(MyStats)
-        self.stats = self.stats_server.server
-        DUMMY_DATA['Stats']['lname'] = self.stats.cc_session.lname
-        self.stats_server.run()
-        (self.address, self.port) = get_availaddr()
-        self.stats_httpd_server = ThreadingServerManager(MyStatsHttpd, (self.address, self.port))
-        self.stats_httpd = self.stats_httpd_server.server
-        self.stats_httpd_server.run()
-        self.client = http.client.HTTPConnection(self.address, self.port)
-        self.client._http_vsn_str = 'HTTP/1.0\n'
-        self.client.connect()
-
-    def tearDown(self):
-        self.client.close()
-        self.stats_httpd_server.shutdown()
-        self.stats_server.shutdown()
-        self.base.shutdown()
-        # reset the signal handler
-        self.sig_handler.reset()
-
-    @unittest.skipIf(sys.version_info >= (3, 3), "Unsupported in Python 3.3 or higher")
-    @unittest.skipUnless(xml_parser, "skipping the test using XMLParser")
-    def test_do_GET(self):
-        self.assertTrue(type(self.stats_httpd.httpd) is list)
-        self.assertEqual(len(self.stats_httpd.httpd), 1)
-        self.assertEqual((self.address, self.port), self.stats_httpd.http_addrs[0])
-
-        def check_XML_URL_PATH(path=''):
-            url_path = '%s/%s' % (stats_httpd.XML_URL_PATH, path)
-            url_path = urllib.parse.quote(url_path)
-            self.client.putrequest('GET', url_path)
-            self.client.endheaders()
-            response = self.client.getresponse()
-            self.assertEqual(response.getheader("Content-type"), "text/xml")
-            self.assertGreater(int(response.getheader("Content-Length")), 0)
-            self.assertEqual(response.status, 200)
-            xml_doctype = response.readline().decode()
-            xsl_doctype = response.readline().decode()
-            self.assertGreater(len(xml_doctype), 0)
-            self.assertGreater(len(xsl_doctype), 0)
-            root = xml.etree.ElementTree.parse(response).getroot()
-            self.assertGreater(root.tag.find('statistics'), 0)
-            schema_loc = '{%s}schemaLocation' % XMLNS_XSI
-            # check the path of XSD
-            self.assertEqual(root.attrib[schema_loc],
-                             stats_httpd.XSD_NAMESPACE + ' '
-                             + stats_httpd.XSD_URL_PATH)
-            # check the path of XSL
-            self.assertTrue(xsl_doctype.startswith(
-                    '<?xml-stylesheet type="text/xsl" href="' +
-                    stats_httpd.XSL_URL_PATH
-                    + '"?>'))
-            # check whether the list of 'identifier' attributes in
-            # root is same as the list of item names in DUMMY_DATA
-            id_list = [ elm.attrib['identifier'] for elm in root ]
-            item_list = [ it for it in \
-                              stats_httpd.item_name_list(DUMMY_DATA, path) \
-                              if len(it.split('/')) > 1 ]
-            self.assertEqual(id_list, item_list)
-            for elem in root:
-                attr = elem.attrib
-                value = isc.cc.data.find(DUMMY_DATA, attr['identifier'])
-                # No 'value' attribute should be found in the 'item'
-                # element when datatype of the value is list or dict.
-                if type(value) is list or type(value) is dict:
-                    self.assertFalse('value' in attr)
-                # The value of the 'value' attribute should be checked
-                # after casting it to string type if datatype of the
-                # value is int or float. Because attr['value'] returns
-                # string type even if its value is int or float.
-                elif type(value) is int or type(value) is float:
-                    self.assertEqual(attr['value'], str(value))
-                else:
-                    self.assertEqual(attr['value'], value)
-
-        # URL is '/bind10/statistics/xml'
-        check_XML_URL_PATH()
-        for path in stats_httpd.item_name_list(DUMMY_DATA, ''):
-            check_XML_URL_PATH(path)
-
-        def check_XSD_URL_PATH():
-            url_path = stats_httpd.XSD_URL_PATH
-            url_path = urllib.parse.quote(url_path)
-            self.client.putrequest('GET', url_path)
-            self.client.endheaders()
-            response = self.client.getresponse()
-            self.assertEqual(response.getheader("Content-type"), "text/xml")
-            self.assertGreater(int(response.getheader("Content-Length")), 0)
-            self.assertEqual(response.status, 200)
-            root = xml.etree.ElementTree.parse(response).getroot()
-            url_xmlschema = '{%s}' % XMLNS_XSD
-            self.assertGreater(root.tag.find('schema'), 0)
-            self.assertTrue(hasattr(root, 'attrib'))
-            self.assertTrue('targetNamespace' in root.attrib)
-            self.assertEqual(root.attrib['targetNamespace'],
-                             stats_httpd.XSD_NAMESPACE)
-
-        # URL is '/bind10/statistics/xsd'
-        check_XSD_URL_PATH()
-
-        def check_XSL_URL_PATH():
-            url_path = stats_httpd.XSL_URL_PATH
-            url_path = urllib.parse.quote(url_path)
-            self.client.putrequest('GET', url_path)
-            self.client.endheaders()
-            response = self.client.getresponse()
-            self.assertEqual(response.getheader("Content-type"), "text/xml")
-            self.assertGreater(int(response.getheader("Content-Length")), 0)
-            self.assertEqual(response.status, 200)
-            root = xml.etree.ElementTree.parse(response).getroot()
-            url_trans = '{%s}' % XMLNS_XSL
-            url_xhtml = '{%s}' % XMLNS_XHTML
-            self.assertEqual(root.tag, url_trans + 'stylesheet')
-
-        # URL is '/bind10/statistics/xsl'
-        check_XSL_URL_PATH()
-
-        # 302 redirect
-        self.client._http_vsn_str = 'HTTP/1.1'
-        self.client.putrequest('GET', '/')
-        self.client.putheader('Host', self.address)
-        self.client.endheaders()
-        response = self.client.getresponse()
-        self.assertEqual(response.status, 302)
-        self.assertEqual(response.getheader('Location'),
-                         "http://%s:%d%s/" % (self.address, self.port, stats_httpd.XML_URL_PATH))
-
-        # 404 NotFound (random path)
-        self.client._http_vsn_str = 'HTTP/1.0'
-        self.client.putrequest('GET', '/path/to/foo/bar')
-        self.client.endheaders()
-        response = self.client.getresponse()
-        self.assertEqual(response.status, 404)
-        self.client._http_vsn_str = 'HTTP/1.0'
-        self.client.putrequest('GET', '/bind10/foo')
-        self.client.endheaders()
-        response = self.client.getresponse()
-        self.assertEqual(response.status, 404)
-        self.client._http_vsn_str = 'HTTP/1.0'
-        self.client.putrequest('GET', '/bind10/statistics/foo')
-        self.client.endheaders()
-        response = self.client.getresponse()
-        self.assertEqual(response.status, 404)
-        self.client._http_vsn_str = 'HTTP/1.0'
-        self.client.putrequest('GET', stats_httpd.XML_URL_PATH + 'Auth') # with no slash
-        self.client.endheaders()
-        response = self.client.getresponse()
-        self.assertEqual(response.status, 404)
-
-        # 200 ok
-        self.client._http_vsn_str = 'HTTP/1.0'
-        self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/')
-        self.client.endheaders()
-        response = self.client.getresponse()
-        self.assertEqual(response.status, 200)
-        self.client._http_vsn_str = 'HTTP/1.0'
-        self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/#foo')
-        self.client.endheaders()
-        response = self.client.getresponse()
-        self.assertEqual(response.status, 200)
-        self.client._http_vsn_str = 'HTTP/1.0'
-        self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/?foo=bar')
-        self.client.endheaders()
-        response = self.client.getresponse()
-        self.assertEqual(response.status, 200)
-
-        # 404 NotFound (too long path)
-        self.client._http_vsn_str = 'HTTP/1.0'
-        self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/Init/boot_time/a')
-        self.client.endheaders()
-        response = self.client.getresponse()
-        self.assertEqual(response.status, 404)
-
-        # 404 NotFound (nonexistent module name)
-        self.client._http_vsn_str = 'HTTP/1.0'
-        self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/Foo')
-        self.client.endheaders()
-        response = self.client.getresponse()
-        self.assertEqual(response.status, 404)
-        self.client._http_vsn_str = 'HTTP/1.0'
-        self.client.putrequest('GET', stats_httpd.XSD_URL_PATH + '/Foo')
-        self.client.endheaders()
-        response = self.client.getresponse()
-        self.assertEqual(response.status, 404)
-        self.client._http_vsn_str = 'HTTP/1.0'
-        self.client.putrequest('GET', stats_httpd.XSL_URL_PATH + '/Foo')
-        self.client.endheaders()
-        response = self.client.getresponse()
-        self.assertEqual(response.status, 404)
-
-        # 404 NotFound (nonexistent item name)
-        self.client._http_vsn_str = 'HTTP/1.0'
-        self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/Foo/bar')
-        self.client.endheaders()
-        response = self.client.getresponse()
-        self.assertEqual(response.status, 404)
-        self.client._http_vsn_str = 'HTTP/1.0'
-        self.client.putrequest('GET', stats_httpd.XSD_URL_PATH + '/Foo/bar')
-        self.client.endheaders()
-        response = self.client.getresponse()
-        self.assertEqual(response.status, 404)
-        self.client._http_vsn_str = 'HTTP/1.0'
-        self.client.putrequest('GET', stats_httpd.XSL_URL_PATH + '/Foo/bar')
-        self.client.endheaders()
-        response = self.client.getresponse()
-        self.assertEqual(response.status, 404)
-
-        # 404 NotFound (existent module but nonexistent item name)
-        self.client._http_vsn_str = 'HTTP/1.0'
-        self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/Auth/bar')
-        self.client.endheaders()
-        response = self.client.getresponse()
-        self.assertEqual(response.status, 404)
-        self.client._http_vsn_str = 'HTTP/1.0'
-        self.client.putrequest('GET', stats_httpd.XSD_URL_PATH + '/Auth/bar')
-        self.client.endheaders()
-        response = self.client.getresponse()
-        self.assertEqual(response.status, 404)
-        self.client._http_vsn_str = 'HTTP/1.0'
-        self.client.putrequest('GET', stats_httpd.XSL_URL_PATH + '/Auth/bar')
-        self.client.endheaders()
-        response = self.client.getresponse()
-        self.assertEqual(response.status, 404)
-
-    def test_do_GET_failed1(self):
-        # checks status
-        self.assertEqual(send_command("status", "Stats"),
-                         (0, "Stats is up. (PID " + str(os.getpid()) + ")"))
-        # failure case(Stats is down)
-        self.assertTrue(self.stats.running)
-        self.assertEqual(send_command("shutdown", "Stats"),
-                         (0, None)) # Stats is down
-        self.assertFalse(self.stats.running)
-        self.stats_httpd.cc_session.set_timeout(milliseconds=100)
-
-        # request XML
-        self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/')
-        self.client.endheaders()
-        response = self.client.getresponse()
-        self.assertEqual(response.status, 500)
-
-        # request XSD
-        self.client.putrequest('GET', stats_httpd.XSD_URL_PATH)
-        self.client.endheaders()
-        response = self.client.getresponse()
-        self.assertEqual(response.status, 200)
-
-        # request XSL
-        self.client.putrequest('GET', stats_httpd.XSL_URL_PATH)
-        self.client.endheaders()
-        response = self.client.getresponse()
-        self.assertEqual(response.status, 200)
-
-    def test_do_GET_failed2(self):
-        # failure case(Stats replies an error)
-        self.stats.mccs.set_command_handler(
-            lambda cmd, args: \
-                isc.config.ccsession.create_answer(1, "specified arguments are incorrect: I have an error.")
-            )
-
-        # request XML
-        self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/')
-        self.client.endheaders()
-        response = self.client.getresponse()
-        self.assertEqual(response.status, 404)
-
-        # request XSD
-        self.client.putrequest('GET', stats_httpd.XSD_URL_PATH)
-        self.client.endheaders()
-        response = self.client.getresponse()
-        self.assertEqual(response.status, 200)
-
-        # request XSL
-        self.client.putrequest('GET', stats_httpd.XSL_URL_PATH)
-        self.client.endheaders()
-        response = self.client.getresponse()
-        self.assertEqual(response.status, 200)
-
-    def test_do_HEAD(self):
-        self.client.putrequest('HEAD', stats_httpd.XML_URL_PATH + '/')
-        self.client.endheaders()
-        response = self.client.getresponse()
-        self.assertEqual(response.status, 200)
-
-        self.client.putrequest('HEAD', '/path/to/foo/bar')
-        self.client.endheaders()
-        response = self.client.getresponse()
-        self.assertEqual(response.status, 404)
-
-    @unittest.skipUnless(lxml_etree, "skipping XML validation with XSD")
-    def test_xml_validation_with_xsd(self):
-        """Tests for XML validation with XSD. If lxml is not
-        installed, this tests would be skipped."""
-        def request_xsd():
-            url_path = stats_httpd.XSD_URL_PATH
-            url_path = urllib.parse.quote(url_path)
-            self.client.putrequest('GET', url_path)
-            self.client.endheaders()
-            xsd_doc = self.client.getresponse()
-            xsd_doc = lxml_etree.parse(xsd_doc)
-            return lxml_etree.XMLSchema(xsd_doc)
-
-        def request_xmldoc(path=''):
-            url_path = '%s/%s' % (stats_httpd.XML_URL_PATH, path)
-            url_path = urllib.parse.quote(url_path)
-            self.client.putrequest('GET', url_path)
-            self.client.endheaders()
-            xml_doc = self.client.getresponse()
-            return lxml_etree.parse(xml_doc)
-
-        # request XSD and XML
-        xsd = request_xsd()
-        xml_doc = request_xmldoc()
-        # do validation
-        self.assertTrue(xsd.validate(xml_doc))
-
-        # validate each paths in DUMMY_DATA
-        for path in stats_httpd.item_name_list(DUMMY_DATA, ''):
-            # request XML
-            xml_doc = request_xmldoc(path)
-            # do validation
-            self.assertTrue(xsd.validate(xml_doc))
-
-class TestHttpServerError(unittest.TestCase):
-    """Tests for HttpServerError exception"""
-    def test_raises(self):
-        try:
-            raise stats_httpd.HttpServerError('Nothing')
-        except stats_httpd.HttpServerError as err:
-            self.assertEqual(str(err), 'Nothing')
-
-class TestHttpServer(unittest.TestCase):
-    """Tests for HttpServer class"""
-    def setUp(self):
-        # set the signal handler for deadlock
-        self.sig_handler = SignalHandler(self.fail)
-        self.base = BaseModules()
-
-    def tearDown(self):
-        if hasattr(self, "stats_httpd"):
-            self.stats_httpd.stop()
-        self.base.shutdown()
-        # reset the signal handler
-        self.sig_handler.reset()
-
-    def test_httpserver(self):
-        self.stats_httpd = MyStatsHttpd(get_availaddr())
-        self.assertEqual(type(self.stats_httpd.httpd), list)
-        self.assertEqual(len(self.stats_httpd.httpd), 1)
-        for httpd in self.stats_httpd.httpd:
-            self.assertTrue(isinstance(httpd, stats_httpd.HttpServer))
-
-class TestStatsHttpdError(unittest.TestCase):
-    """Tests for StatsHttpdError exception"""
-
-    def test_raises1(self):
-        try:
-            raise stats_httpd.StatsHttpdError('Nothing')
-        except stats_httpd.StatsHttpdError as err:
-            self.assertEqual(str(err), 'Nothing')
-
-    def test_raises2(self):
-        try:
-            raise stats_httpd.StatsHttpdDataError('Nothing')
-        except stats_httpd.StatsHttpdDataError as err:
-            self.assertEqual(str(err), 'Nothing')
-
-class TestStatsHttpd(unittest.TestCase):
-    """Tests for StatsHttpd class"""
-
-    def setUp(self):
-        # set the signal handler for deadlock
-        self.sig_handler = SignalHandler(self.fail)
-        self.base = BaseModules()
-        self.stats_server = ThreadingServerManager(MyStats)
-        self.stats_server.run()
-        # checking IPv6 enabled on this platform
-        self.ipv6_enabled = is_ipv6_enabled()
-        # instantiation of StatsHttpd indirectly calls gethostbyaddr(), which
-        # can block for an uncontrollable period, leading many undesirable
-        # results.  We should rather eliminate the reliance, but until we
-        # can make such fundamental cleanup we replace it with a faked method;
-        # in our test scenario the return value doesn't matter.
-        self.__gethostbyaddr_orig = socket.gethostbyaddr
-        socket.gethostbyaddr = lambda x: ('test.example.', [], None)
-
-    def tearDown(self):
-        socket.gethostbyaddr = self.__gethostbyaddr_orig
-        if hasattr(self, "stats_httpd"):
-            self.stats_httpd.stop()
-        self.stats_server.shutdown()
-        self.base.shutdown()
-        # reset the signal handler
-        self.sig_handler.reset()
-
-    def test_init(self):
-        server_address = get_availaddr()
-        self.stats_httpd = MyStatsHttpd(server_address)
-        self.assertEqual(self.stats_httpd.running, False)
-        self.assertEqual(self.stats_httpd.poll_intval, 0.5)
-        self.assertNotEqual(len(self.stats_httpd.httpd), 0)
-        self.assertEqual(type(self.stats_httpd.mccs), isc.config.ModuleCCSession)
-        self.assertEqual(type(self.stats_httpd.cc_session), isc.cc.Session)
-        self.assertEqual(len(self.stats_httpd.config), 2)
-        self.assertTrue('listen_on' in self.stats_httpd.config)
-        self.assertEqual(len(self.stats_httpd.config['listen_on']), 1)
-        self.assertTrue('address' in self.stats_httpd.config['listen_on'][0])
-        self.assertTrue('port' in self.stats_httpd.config['listen_on'][0])
-        self.assertTrue(server_address in set(self.stats_httpd.http_addrs))
-        ans = send_command(
-            isc.config.ccsession.COMMAND_GET_MODULE_SPEC,
-            "ConfigManager", {"module_name":"StatsHttpd"})
-        # assert StatsHttpd is added to ConfigManager
-        self.assertNotEqual(ans, (0,{}))
-        self.assertTrue(ans[1]['module_name'], 'StatsHttpd')
-
-    def test_init_hterr(self):
-        orig_open_httpd = stats_httpd.StatsHttpd.open_httpd
-        def err_open_httpd(arg): raise stats_httpd.HttpServerError
-        stats_httpd.StatsHttpd.open_httpd = err_open_httpd
-        self.assertRaises(stats_httpd.HttpServerError, stats_httpd.StatsHttpd)
-        ans = send_command(
-            isc.config.ccsession.COMMAND_GET_MODULE_SPEC,
-            "ConfigManager", {"module_name":"StatsHttpd"})
-        # assert StatsHttpd is removed from ConfigManager
-        self.assertEqual(ans, (0,{}))
-        stats_httpd.StatsHttpd.open_httpd = orig_open_httpd
-
-    def test_openclose_mccs(self):
-        self.stats_httpd = MyStatsHttpd(get_availaddr())
-        mccs = MockModuleCCSession()
-        self.stats_httpd.mccs = mccs
-        self.assertFalse(self.stats_httpd.mccs.stopped)
-        self.assertFalse(self.stats_httpd.mccs.closed)
-        self.stats_httpd.close_mccs()
-        self.assertTrue(mccs.stopped)
-        self.assertTrue(mccs.closed)
-        self.assertEqual(self.stats_httpd.mccs, None)
-        self.stats_httpd.open_mccs()
-        self.assertIsNotNone(self.stats_httpd.mccs)
-        self.stats_httpd.mccs = None
-        self.assertEqual(self.stats_httpd.mccs, None)
-        self.assertEqual(self.stats_httpd.close_mccs(), None)
-
-    def test_mccs(self):
-        self.stats_httpd = MyStatsHttpd(get_availaddr())
-        self.assertIsNotNone(self.stats_httpd.mccs.get_socket())
-        self.assertTrue(
-            isinstance(self.stats_httpd.mccs.get_socket(), socket.socket))
-        self.assertTrue(
-            isinstance(self.stats_httpd.cc_session, isc.cc.session.Session))
-        statistics_spec = self.stats_httpd.get_stats_spec()
-        for mod in DUMMY_DATA:
-            self.assertTrue(mod in statistics_spec)
-            for cfg in statistics_spec[mod]:
-                self.assertTrue('item_name' in cfg)
-                self.assertTrue(cfg['item_name'] in DUMMY_DATA[mod])
-            self.assertTrue(len(statistics_spec[mod]), len(DUMMY_DATA[mod]))
-        self.stats_httpd.close_mccs()
-        self.assertIsNone(self.stats_httpd.mccs)
-
-    def test_httpd(self):
-        # dual stack (addresses is ipv4 and ipv6)
-        if self.ipv6_enabled:
-            server_addresses = (get_availaddr('::1'), get_availaddr())
-            self.stats_httpd = MyStatsHttpd(*server_addresses)
-            for ht in self.stats_httpd.httpd:
-                self.assertTrue(isinstance(ht, stats_httpd.HttpServer))
-                self.assertTrue(ht.address_family in set([socket.AF_INET, socket.AF_INET6]))
-                self.assertTrue(isinstance(ht.socket, socket.socket))
-
-        # dual stack (address is ipv6)
-        if self.ipv6_enabled:
-            server_addresses = get_availaddr('::1')
-            self.stats_httpd = MyStatsHttpd(server_addresses)
-            for ht in self.stats_httpd.httpd:
-                self.assertTrue(isinstance(ht, stats_httpd.HttpServer))
-                self.assertEqual(ht.address_family, socket.AF_INET6)
-                self.assertTrue(isinstance(ht.socket, socket.socket))
-
-        # dual/single stack (address is ipv4)
-        server_addresses = get_availaddr()
-        self.stats_httpd = MyStatsHttpd(server_addresses)
-        for ht in self.stats_httpd.httpd:
-            self.assertTrue(isinstance(ht, stats_httpd.HttpServer))
-            self.assertEqual(ht.address_family, socket.AF_INET)
-            self.assertTrue(isinstance(ht.socket, socket.socket))
-
-    def test_httpd_anyIPv4(self):
-        # any address (IPv4)
-        server_addresses = get_availaddr(address='0.0.0.0')
-        self.stats_httpd = MyStatsHttpd(server_addresses)
-        for ht in self.stats_httpd.httpd:
-            self.assertTrue(isinstance(ht, stats_httpd.HttpServer))
-            self.assertEqual(ht.address_family,socket.AF_INET)
-            self.assertTrue(isinstance(ht.socket, socket.socket))
-
-    def test_httpd_anyIPv6(self):
-        # any address (IPv6)
-        if self.ipv6_enabled:
-            server_addresses = get_availaddr(address='::')
-            self.stats_httpd = MyStatsHttpd(server_addresses)
-            for ht in self.stats_httpd.httpd:
-                self.assertTrue(isinstance(ht, stats_httpd.HttpServer))
-                self.assertEqual(ht.address_family,socket.AF_INET6)
-                self.assertTrue(isinstance(ht.socket, socket.socket))
-
-    def test_httpd_failed(self):
-        # existent hostname
-        self.assertRaises(stats_httpd.HttpServerError, MyStatsHttpd,
-                          get_availaddr(address='localhost'))
-
-        # nonexistent hostname
-        self.assertRaises(stats_httpd.HttpServerError, MyStatsHttpd, ('my.host.domain', 8000))
-
-        # over flow of port number
-        self.assertRaises(stats_httpd.HttpServerError, MyStatsHttpd, ('127.0.0.1', 80000))
-
-        # negative
-        self.assertRaises(stats_httpd.HttpServerError, MyStatsHttpd, ('127.0.0.1', -8000))
-
-        # alphabet
-        self.assertRaises(stats_httpd.HttpServerError, MyStatsHttpd, ('127.0.0.1', 'ABCDE'))
-
-        # Address already in use
-        server_addresses = get_availaddr()
-        self.stats_httpd_server = ThreadingServerManager(MyStatsHttpd, server_addresses)
-        self.stats_httpd_server.run()
-        self.assertRaises(stats_httpd.HttpServerError, MyStatsHttpd, server_addresses)
-        send_command("shutdown", "StatsHttpd")
-
-    def test_running(self):
-        self.stats_httpd_server = ThreadingServerManager(MyStatsHttpd, get_availaddr())
-        self.stats_httpd = self.stats_httpd_server.server
-        self.assertFalse(self.stats_httpd.running)
-        self.stats_httpd_server.run()
-        self.assertEqual(send_command("status", "StatsHttpd"),
-                         (0, "Stats Httpd is up. (PID " + str(os.getpid()) + ")"))
-        self.assertTrue(self.stats_httpd.running)
-        self.assertEqual(send_command("shutdown", "StatsHttpd"), (0, None))
-        self.assertFalse(self.stats_httpd.running)
-        self.stats_httpd_server.shutdown()
-
-        # failure case
-        self.stats_httpd = MyStatsHttpd(get_availaddr())
-        self.stats_httpd.cc_session.close()
-        self.assertRaises(ValueError, self.stats_httpd.start)
-
-    def test_failure_with_a_select_error (self):
-        """checks select.error is raised if the exception except
-        errno.EINTR is raised while it's selecting"""
-        def raise_select_except(*args):
-            raise select.error('dummy error')
-        orig_select = stats_httpd.select.select
-        stats_httpd.select.select = raise_select_except
-        self.stats_httpd = MyStatsHttpd(get_availaddr())
-        self.assertRaises(select.error, self.stats_httpd.start)
-        stats_httpd.select.select = orig_select
-
-    def test_nofailure_with_errno_EINTR(self):
-        """checks no exception is raised if errno.EINTR is raised
-        while it's selecting"""
-        def raise_select_except(*args):
-            raise select.error(errno.EINTR)
-        orig_select = stats_httpd.select.select
-        stats_httpd.select.select = raise_select_except
-        self.stats_httpd_server = ThreadingServerManager(MyStatsHttpd, get_availaddr())
-        self.stats_httpd_server.run()
-        self.stats_httpd_server.shutdown()
-        stats_httpd.select.select = orig_select
-
-    def test_open_template(self):
-        self.stats_httpd = MyStatsHttpd(get_availaddr())
-        # successful conditions
-        tmpl = self.stats_httpd.open_template(stats_httpd.XML_TEMPLATE_LOCATION)
-        self.assertTrue(isinstance(tmpl, string.Template))
-        opts = dict(
-            xml_string="<dummy></dummy>",
-            xsl_url_path="/path/to/")
-        lines = tmpl.substitute(opts)
-        for n in opts:
-            self.assertGreater(lines.find(opts[n]), 0)
-        tmpl = self.stats_httpd.open_template(stats_httpd.XSD_TEMPLATE_LOCATION)
-        self.assertTrue(isinstance(tmpl, string.Template))
-        opts = dict(xsd_namespace="http://host/path/to/")
-        lines = tmpl.substitute(opts)
-        for n in opts:
-            self.assertGreater(lines.find(opts[n]), 0)
-        tmpl = self.stats_httpd.open_template(stats_httpd.XSL_TEMPLATE_LOCATION)
-        self.assertTrue(isinstance(tmpl, string.Template))
-        opts = dict(xsd_namespace="http://host/path/to/")
-        lines = tmpl.substitute(opts)
-        for n in opts:
-            self.assertGreater(lines.find(opts[n]), 0)
-        # unsuccessful condition
-        self.assertRaises(
-            stats_httpd.StatsHttpdDataError,
-            self.stats_httpd.open_template, '/path/to/foo/bar')
-
-    def test_commands(self):
-        self.stats_httpd = MyStatsHttpd(get_availaddr())
-        self.assertEqual(self.stats_httpd.command_handler("status", None),
-                         isc.config.ccsession.create_answer(
-                0, "Stats Httpd is up. (PID " + str(os.getpid()) + ")"))
-        self.stats_httpd.running = True
-        self.assertEqual(self.stats_httpd.command_handler("shutdown", None),
-                         isc.config.ccsession.create_answer(0))
-        self.assertFalse(self.stats_httpd.running)
-        self.assertEqual(
-            self.stats_httpd.command_handler("__UNKNOWN_COMMAND__", None),
-            isc.config.ccsession.create_answer(
-                1, "Unknown command: __UNKNOWN_COMMAND__"))
-
-    def test_config(self):
-        self.stats_httpd = MyStatsHttpd(get_availaddr())
-        self.assertEqual(
-            self.stats_httpd.config_handler(dict(_UNKNOWN_KEY_=None)),
-            isc.config.ccsession.create_answer(
-                1, "unknown item _UNKNOWN_KEY_"))
-
-        addresses = get_availaddr()
-        self.assertEqual(
-            self.stats_httpd.config_handler(
-                dict(listen_on=[dict(address=addresses[0],port=addresses[1])])),
-            isc.config.ccsession.create_answer(0))
-        self.assertTrue("listen_on" in self.stats_httpd.config)
-        for addr in self.stats_httpd.config["listen_on"]:
-            self.assertTrue("address" in addr)
-            self.assertTrue("port" in addr)
-            self.assertTrue(addr["address"] == addresses[0])
-            self.assertTrue(addr["port"] == addresses[1])
-
-        if self.ipv6_enabled:
-            addresses = get_availaddr("::1")
-            self.assertEqual(
-                self.stats_httpd.config_handler(
-                dict(listen_on=[dict(address=addresses[0],port=addresses[1])])),
-                isc.config.ccsession.create_answer(0))
-            self.assertTrue("listen_on" in self.stats_httpd.config)
-            for addr in self.stats_httpd.config["listen_on"]:
-                self.assertTrue("address" in addr)
-                self.assertTrue("port" in addr)
-                self.assertTrue(addr["address"] == addresses[0])
-                self.assertTrue(addr["port"] == addresses[1])
-
-        addresses = get_availaddr()
-        self.assertEqual(
-            self.stats_httpd.config_handler(
-                dict(listen_on=[dict(address=addresses[0],port=addresses[1])])),
-            isc.config.ccsession.create_answer(0))
-        self.assertTrue("listen_on" in self.stats_httpd.config)
-        for addr in self.stats_httpd.config["listen_on"]:
-            self.assertTrue("address" in addr)
-            self.assertTrue("port" in addr)
-            self.assertTrue(addr["address"] == addresses[0])
-            self.assertTrue(addr["port"] == addresses[1])
-        (ret, arg) = isc.config.ccsession.parse_answer(
-            self.stats_httpd.config_handler(
-                dict(listen_on=[dict(address="1.2.3.4",port=543210)]))
-            )
-        self.assertEqual(ret, 1)
-
-    @unittest.skipUnless(xml_parser, "skipping the test using XMLParser")
-    def test_xml_handler(self):
-        self.stats_httpd = MyStatsHttpd(get_availaddr())
-        module_name = 'Dummy'
-        stats_spec = \
-            { module_name :
-                  [{
-                        "item_name": "foo",
-                        "item_type": "string",
-                        "item_optional": False,
-                        "item_default": "bar",
-                        "item_description": "foo is bar",
-                        "item_title": "Foo"
-                        },
-                   {
-                        "item_name": "foo2",
-                        "item_type": "list",
-                        "item_optional": False,
-                        "item_default": [
-                            {
-                                "zonename" : "test1",
-                                "queries.udp" : 1,
-                                "queries.tcp" : 2
-                                },
-                            {
-                                "zonename" : "test2",
-                                "queries.udp" : 3,
-                                "queries.tcp" : 4
-                                }
-                        ],
-                        "item_title": "Foo bar",
-                        "item_description": "Foo bar",
-                        "list_item_spec": {
-                            "item_name": "foo2-1",
-                            "item_type": "map",
-                            "item_optional": False,
-                            "item_default": {},
-                            "map_item_spec": [
-                                {
-                                    "item_name": "foo2-1-1",
-                                    "item_type": "string",
-                                    "item_optional": False,
-                                    "item_default": "",
-                                    "item_title": "Foo2 1 1",
-                                    "item_description": "Foo bar"
-                                    },
-                                {
-                                    "item_name": "foo2-1-2",
-                                    "item_type": "integer",
-                                    "item_optional": False,
-                                    "item_default": 0,
-                                    "item_title": "Foo2 1 2",
-                                    "item_description": "Foo bar"
-                                    },
-                                {
-                                    "item_name": "foo2-1-3",
-                                    "item_type": "integer",
-                                    "item_optional": False,
-                                    "item_default": 0,
-                                    "item_title": "Foo2 1 3",
-                                    "item_description": "Foo bar"
-                                    }
-                                ]
-                            }
-                        }]
-              }
-        stats_data = \
-            { module_name : { 'foo':'bar',
-                          'foo2': [
-                            {
-                                "foo2-1-1" : "bar1",
-                                "foo2-1-2" : 10,
-                                "foo2-1-3" : 9
-                                },
-                            {
-                                "foo2-1-1" : "bar2",
-                                "foo2-1-2" : 8,
-                                "foo2-1-3" : 7
-                                }
-                            ] } }
-        self.stats_httpd.get_stats_spec = lambda x,y: stats_spec
-        self.stats_httpd.get_stats_data = lambda x,y: stats_data
-        xml_string = self.stats_httpd.xml_handler()
-        stats_xml = xml.etree.ElementTree.fromstring(xml_string)
-        schema_loc = '{%s}schemaLocation' % XMLNS_XSI
-        self.assertEqual(stats_xml.attrib[schema_loc],
-                         stats_httpd.XML_ROOT_ATTRIB['xsi:schemaLocation'])
-        stats_data = stats_data[module_name]
-        stats_spec = stats_spec[module_name]
-        names = stats_httpd.item_name_list(stats_data, '')
-        for i in range(0, len(names)):
-            self.assertEqual('%s/%s' % (module_name, names[i]), stats_xml[i].attrib['identifier'])
-            value = isc.cc.data.find(stats_data, names[i])
-            if type(value) is int:
-                value = str(value)
-            if type(value) is dict or type(value) is list:
-                self.assertFalse('value' in stats_xml[i].attrib)
-            else:
-                self.assertEqual(value, stats_xml[i].attrib['value'])
-            self.assertEqual(module_name, stats_xml[i].attrib['owner'])
-            self.assertEqual(urllib.parse.quote('%s/%s/%s' % (stats_httpd.XML_URL_PATH,
-                                                              module_name, names[i])),
-                             stats_xml[i].attrib['uri'])
-            spec = isc.config.find_spec_part(stats_spec, names[i])
-            self.assertEqual(spec['item_name'], stats_xml[i].attrib['name'])
-            self.assertEqual(spec['item_type'], stats_xml[i].attrib['type'])
-            self.assertEqual(spec['item_description'], stats_xml[i].attrib['description'])
-            self.assertEqual(spec['item_title'], stats_xml[i].attrib['title'])
-            self.assertEqual(str(spec['item_optional']).lower(), stats_xml[i].attrib['optional'])
-            default = spec['item_default']
-            if type(default) is int:
-                default = str(default)
-            if type(default) is dict or type(default) is list:
-                self.assertFalse('default' in stats_xml[i].attrib)
-            else:
-                self.assertEqual(default, stats_xml[i].attrib['default'])
-            self.assertFalse('item_format' in spec)
-            self.assertFalse('format' in stats_xml[i].attrib)
-
-    @unittest.skipUnless(xml_parser, "skipping the test using XMLParser")
-    def test_xsd_handler(self):
-        self.stats_httpd = MyStatsHttpd(get_availaddr())
-        xsd_string = self.stats_httpd.xsd_handler()
-        stats_xsd = xml.etree.ElementTree.fromstring(xsd_string)
-        ns = '{%s}' % XMLNS_XSD
-        stats_xsd = stats_xsd[1].find('%scomplexType/%ssequence/%selement' % ((ns,)*3))
-        self.assertEqual('item', stats_xsd.attrib['name'])
-        stats_xsd = stats_xsd.find('%scomplexType' % ns)
-        type_types = ('boolean', 'integer', 'real', 'string', 'map', \
-                          'list', 'named_set', 'any')
-        attribs = [('identifier', 'string', 'required'),
-                   ('value', 'string', 'optional'),
-                   ('owner', 'string', 'required'),
-                   ('uri', 'anyURI', 'required'),
-                   ('name', 'string', 'required'),
-                   ('type', type_types, 'required'),
-                   ('description', 'string', 'optional'),
-                   ('title', 'string', 'optional'),
-                   ('optional', 'boolean', 'optional'),
-                   ('default', 'string', 'optional'),
-                   ('format', 'string', 'optional')]
-        for i in range(0, len(attribs)):
-            self.assertEqual(attribs[i][0], stats_xsd[i].attrib['name'])
-            if attribs[i][0] == 'type':
-                stats_xsd_types = \
-                    stats_xsd[i].find('%ssimpleType/%srestriction' % \
-                                          ((ns,)*2))
-                for j in range(0, len(attribs[i][1])):
-                    self.assertEqual(attribs[i][1][j], \
-                                         stats_xsd_types[j].attrib['value'])
-            else:
-                self.assertEqual(attribs[i][1], stats_xsd[i].attrib['type'])
-            self.assertEqual(attribs[i][2], stats_xsd[i].attrib['use'])
-
-    @unittest.skipUnless(xml_parser, "skipping the test using XMLParser")
-    def test_xsl_handler(self):
-        self.stats_httpd = MyStatsHttpd(get_availaddr())
-        xsl_string = self.stats_httpd.xsl_handler()
-        stats_xsl = xml.etree.ElementTree.fromstring(xsl_string)
-        nst = '{%s}' % XMLNS_XSL
-        nsx = '{%s}' % XMLNS_XHTML
-        self.assertEqual("bind10:statistics", stats_xsl[2].attrib['match'])
-        stats_xsl = stats_xsl[2].find('%stable' % nsx)
-        self.assertEqual('item', stats_xsl[1].attrib['select'])
-        stats_xsl = stats_xsl[1].find('%str' % nsx)
-        self.assertEqual('@uri', stats_xsl[0].find(
-                '%selement/%sattribute/%svalue-of' % ((nst,)*3)).attrib['select'])
-        self.assertEqual('@identifier', stats_xsl[0].find(
-                '%selement/%svalue-of' % ((nst,)*2)).attrib['select'])
-        self.assertEqual('@value', stats_xsl[1].find('%sif' % nst).attrib['test'])
-        self.assertEqual('@value', stats_xsl[1].find('%sif/%svalue-of' % ((nst,)*2)).attrib['select'])
-        self.assertEqual('@description', stats_xsl[2].find('%sif' % nst).attrib['test'])
-        self.assertEqual('@description', stats_xsl[2].find('%sif/%svalue-of' % ((nst,)*2)).attrib['select'])
-
-    def test_for_without_B10_FROM_SOURCE(self):
-        # just lets it go through the code without B10_FROM_SOURCE env
-        # variable
-        if "B10_FROM_SOURCE" in os.environ:
-            tmppath = os.environ["B10_FROM_SOURCE"]
-            os.environ.pop("B10_FROM_SOURCE")
-            imp.reload(stats_httpd)
-            os.environ["B10_FROM_SOURCE"] = tmppath
-            imp.reload(stats_httpd)
-
-if __name__ == "__main__":
-    isc.log.resetUnitTestRootLogger()
-    unittest.main()
diff --git a/src/bin/stats/tests/b10-stats_test.py b/src/bin/stats/tests/b10-stats_test.py
deleted file mode 100644
index 540c707..0000000
--- a/src/bin/stats/tests/b10-stats_test.py
+++ /dev/null
@@ -1,1379 +0,0 @@
-# Copyright (C) 2010, 2011, 2012  Internet Systems Consortium.
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
-# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
-# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-"""
-In each of these tests we start several virtual components. They are
-not the real components, no external processes are started. They are
-just simple mock objects running each in its own thread and pretending
-to be bind10 modules. This helps testing the stats module in a close
-to real environment.
-"""
-
-import unittest
-import os
-import threading
-import io
-import time
-import imp
-import sys
-
-import stats
-import isc.log
-import isc.cc.session
-from test_utils import BaseModules, ThreadingServerManager, MyStats, \
-    SimpleStats, SignalHandler, MyModuleCCSession, send_command
-from isc.testutils.ccsession_mock import MockModuleCCSession
-
-class TestUtilties(unittest.TestCase):
-    items = [
-        { 'item_name': 'test_int1',  'item_type': 'integer', 'item_default': 12345      },
-        { 'item_name': 'test_real1', 'item_type': 'real',    'item_default': 12345.6789 },
-        { 'item_name': 'test_bool1', 'item_type': 'boolean', 'item_default': True       },
-        { 'item_name': 'test_str1',  'item_type': 'string',  'item_default': 'ABCD'     },
-        { 'item_name': 'test_list1', 'item_type': 'list',    'item_default': [1,2,3],
-          'list_item_spec' : { 'item_name': 'number',   'item_type': 'integer' } },
-        { 'item_name': 'test_map1',  'item_type': 'map',     'item_default': {'a':1,'b':2,'c':3},
-          'map_item_spec'  : [ { 'item_name': 'a',   'item_type': 'integer'},
-                               { 'item_name': 'b',   'item_type': 'integer'},
-                               { 'item_name': 'c', 'item_type': 'integer'} ] },
-        { 'item_name': 'test_int2',  'item_type': 'integer' },
-        { 'item_name': 'test_real2', 'item_type': 'real'    },
-        { 'item_name': 'test_bool2', 'item_type': 'boolean' },
-        { 'item_name': 'test_str2',  'item_type': 'string'  },
-        { 'item_name': 'test_list2', 'item_type': 'list',
-          'list_item_spec' : { 'item_name': 'number',   'item_type': 'integer' } },
-        { 'item_name': 'test_map2',  'item_type': 'map',
-          'map_item_spec'  : [ { 'item_name': 'A', 'item_type': 'integer'},
-                               { 'item_name': 'B', 'item_type': 'integer'},
-                               { 'item_name': 'C', 'item_type': 'integer'} ] },
-        { 'item_name': 'test_none',  'item_type': 'none'    },
-        { 'item_name': 'test_list3', 'item_type': 'list',    'item_default': ["one","two","three"],
-          'list_item_spec' : { 'item_name': 'number', 'item_type': 'string' } },
-        { 'item_name': 'test_map3',  'item_type': 'map',     'item_default': {'a':'one','b':'two','c':'three'},
-          'map_item_spec'  : [ { 'item_name': 'a', 'item_type': 'string'},
-                               { 'item_name': 'b', 'item_type': 'string'},
-                               { 'item_name': 'c', 'item_type': 'string'} ] },
-        {
-          'item_name': 'test_named_set',
-          'item_type': 'named_set',
-          'item_default': { },
-          'named_set_item_spec': {
-            'item_name': 'name',
-            'item_type': 'map',
-            'item_default': { },
-            'map_item_spec': [
-              {
-                'item_name': 'number1',
-                'item_type': 'integer'
-                },
-              {
-                'item_name': 'number2',
-                'item_type': 'integer'
-                }
-              ]
-            }
-          }
-        ]
-
-    def setUp(self):
-        self.const_timestamp = 1308730448.965706
-        self.const_timetuple = (2011, 6, 22, 8, 14, 8, 2, 173, 0)
-        self.const_datetime = '2011-06-22T08:14:08Z'
-        stats.time = lambda : self.const_timestamp
-        stats.gmtime = lambda : self.const_timetuple
-
-    def test_get_spec_defaults(self):
-        self.assertEqual(
-            stats.get_spec_defaults(self.items), {
-                'test_int1'  : 12345              ,
-                'test_real1' : 12345.6789         ,
-                'test_bool1' : True               ,
-                'test_str1'  : 'ABCD'             ,
-                'test_list1' : [1,2,3]            ,
-                'test_map1'  : {'a':1,'b':2,'c':3},
-                'test_int2'  : 0 ,
-                'test_real2' : 0.0,
-                'test_bool2' : False,
-                'test_str2'  : "",
-                'test_list2' : [0],
-                'test_map2'  : { 'A' : 0, 'B' : 0, 'C' : 0 },
-                'test_none'  : None,
-                'test_list3' : [ "one", "two", "three" ],
-                'test_map3'  : { 'a' : 'one', 'b' : 'two', 'c' : 'three' },
-                'test_named_set' : {} })
-        self.assertEqual(stats.get_spec_defaults(None), {})
-        self.assertRaises(KeyError, stats.get_spec_defaults, [{'item_name':'Foo'}])
-
-    def test_get_timestamp(self):
-        self.assertEqual(stats.get_timestamp(), self.const_timestamp)
-
-    def test_get_datetime(self):
-        self.assertEqual(stats.get_datetime(), self.const_datetime)
-        self.assertNotEqual(stats.get_datetime(
-                (2011, 6, 22, 8, 23, 40, 2, 173, 0)), self.const_datetime)
-
-    def test__accum(self):
-        self.assertEqual(stats._accum(None, None), None)
-        self.assertEqual(stats._accum(None, "b"), "b")
-        self.assertEqual(stats._accum("a", None), "a")
-        self.assertEqual(stats._accum(1, 2), 3)
-        self.assertEqual(stats._accum(0.5, 0.3), 0.8)
-        self.assertEqual(stats._accum('aa','bb'), 'bb')
-        self.assertEqual(stats._accum('1970-01-01T09:00:00Z','2012-08-09T09:33:31Z'),
-                         '2012-08-09T09:33:31Z')
-        self.assertEqual(stats._accum(
-                [1, 2, 3], [4, 5]), [5, 7, 3])
-        self.assertEqual(stats._accum(
-                [4, 5], [1, 2, 3]), [5, 7, 3])
-        self.assertEqual(stats._accum(
-                [1, 2, 3], [None, 5, 6]), [1, 7, 9])
-        self.assertEqual(stats._accum(
-                [None, 5, 6], [1, 2, 3]), [1, 7, 9])
-        self.assertEqual(stats._accum(
-                [1, 2, 3], [None, None, None, None]), [1,2,3,None])
-        self.assertEqual(stats._accum(
-                [[1,2],3],[[],5,6]), [[1,2],8,6])
-        self.assertEqual(stats._accum(
-                {'one': 1, 'two': 2, 'three': 3},
-                {'one': 4, 'two': 5}),
-                         {'one': 5, 'two': 7, 'three': 3})
-        self.assertEqual(stats._accum(
-                {'one': 1, 'two': 2, 'three': 3},
-                {'four': 4, 'five': 5}),
-                         {'one': 1, 'two': 2, 'three': 3, 'four': 4, 'five': 5})
-        self.assertEqual(stats._accum(
-                {'one': [1, 2], 'two': [3, None, 5], 'three': [None, 3, None]},
-                {'one': [2], 'two': [4, 5], 'three': [None, None, None], 'four': 'FOUR'}),
-                         {'one':[3,2], 'two':[7,5,5], 'three':[None,3,None], 'four': 'FOUR'})
-        self.assertEqual(stats._accum(
-                [ {'one': 1, 'two': 2, 'three': 3}, {'four': 4, 'five': 5, 'six': 6} ],
-                [ {}, {'four': 1, 'five': 2, 'six': 3} ]),
-                [ {'one': 1, 'two': 2, 'three': 3}, {'four': 5, 'five': 7, 'six': 9} ])
-
-    def test_merge_oldnre(self):
-        self.assertEqual(stats.merge_oldnew(1, 2), 2)
-        self.assertEqual(stats.merge_oldnew(0.5, 0.3), 0.3)
-        self.assertEqual(stats.merge_oldnew('aa','bb'), 'bb')
-        self.assertEqual(stats.merge_oldnew(
-                [1, 2, 3], [4, 5]), [4, 5, 3])
-        self.assertEqual(stats.merge_oldnew(
-                [4, 5], [1, 2, 3]), [1, 2, 3])
-        self.assertEqual(stats.merge_oldnew(
-                [1, 2, 3], [None, 5, 6]), [None, 5, 6])
-        self.assertEqual(stats.merge_oldnew(
-                [None, 5, 6], [1, 2, 3]), [1, 2, 3])
-        self.assertEqual(stats.merge_oldnew(
-                [1, 2, 3], [None, None, None, None]), [None, None, None, None])
-        self.assertEqual(stats.merge_oldnew(
-                [[1,2],3],[[],5,6]), [[1,2],5,6])
-        self.assertEqual(stats.merge_oldnew(
-                {'one': 1, 'two': 2, 'three': 3},
-                {'one': 4, 'two': 5}),
-                         {'one': 4, 'two': 5, 'three': 3})
-        self.assertEqual(stats.merge_oldnew(
-                {'one': 1, 'two': 2, 'three': 3},
-                {'four': 4, 'five': 5}),
-                         {'one': 1, 'two': 2, 'three': 3, 'four': 4, 'five': 5})
-        self.assertEqual(stats.merge_oldnew(
-                {'one': [1, 2], 'two': [3, None, 5], 'three': [None, 3, None]},
-                {'one': [2], 'two': [4, 5], 'three': [None, None, None], 'four': 'FOUR'}),
-                         {'one':[2,2], 'two':[4,5,5], 'three':[None,None,None], 'four': 'FOUR'})
-        self.assertEqual(stats.merge_oldnew(
-                [ {'one': 1, 'two': 2, 'three': 3}, {'four': 4, 'five': 5, 'six': 6} ],
-                [ {}, {'four': 1, 'five': 2, 'six': 3} ]),
-                [ {'one': 1, 'two': 2, 'three': 3}, {'four': 1, 'five': 2, 'six': 3} ])
-
-class TestCallback(unittest.TestCase):
-    def setUp(self):
-        self.dummy_func = lambda *x, **y : (x, y)
-        self.dummy_args = (1,2,3)
-        self.dummy_kwargs = {'a':1,'b':2,'c':3}
-        self.cback1 = stats.Callback(
-            command=self.dummy_func,
-            args=self.dummy_args,
-            kwargs=self.dummy_kwargs
-            )
-        self.cback2 = stats.Callback(
-            args=self.dummy_args,
-            kwargs=self.dummy_kwargs
-            )
-        self.cback3 = stats.Callback(
-            command=self.dummy_func,
-            kwargs=self.dummy_kwargs
-            )
-        self.cback4 = stats.Callback(
-            command=self.dummy_func,
-            args=self.dummy_args
-            )
-
-    def test_init(self):
-        self.assertEqual((self.cback1.command, self.cback1.args, self.cback1.kwargs),
-                         (self.dummy_func, self.dummy_args, self.dummy_kwargs))
-        self.assertEqual((self.cback2.command, self.cback2.args, self.cback2.kwargs),
-                         (None, self.dummy_args, self.dummy_kwargs))
-        self.assertEqual((self.cback3.command, self.cback3.args, self.cback3.kwargs),
-                         (self.dummy_func, (), self.dummy_kwargs))
-        self.assertEqual((self.cback4.command, self.cback4.args, self.cback4.kwargs),
-                         (self.dummy_func, self.dummy_args, {}))
-
-    def test_call(self):
-        self.assertEqual(self.cback1(), (self.dummy_args, self.dummy_kwargs))
-        self.assertEqual(self.cback1(100, 200), ((100, 200), self.dummy_kwargs))
-        self.assertEqual(self.cback1(a=100, b=200), (self.dummy_args, {'a':100, 'b':200}))
-        self.assertEqual(self.cback2(), None)
-        self.assertEqual(self.cback3(), ((), self.dummy_kwargs))
-        self.assertEqual(self.cback3(100, 200), ((100, 200), self.dummy_kwargs))
-        self.assertEqual(self.cback3(a=100, b=200), ((), {'a':100, 'b':200}))
-        self.assertEqual(self.cback4(), (self.dummy_args, {}))
-        self.assertEqual(self.cback4(100, 200), ((100, 200), {}))
-        self.assertEqual(self.cback4(a=100, b=200), (self.dummy_args, {'a':100, 'b':200}))
-
-class TestStats(unittest.TestCase):
-    def setUp(self):
-        # set the signal handler for deadlock
-        self.sig_handler = SignalHandler(self.fail)
-        self.base = BaseModules()
-        self.const_timestamp = 1308730448.965706
-        self.const_datetime = '2011-06-22T08:14:08Z'
-        self.const_default_datetime = '1970-01-01T00:00:00Z'
-        # Record original module-defined functions in case we replace them
-        self.__orig_timestamp = stats.get_timestamp
-        self.__orig_get_datetime = stats.get_datetime
-
-    def tearDown(self):
-        self.base.shutdown()
-        # reset the signal handler
-        self.sig_handler.reset()
-        # restore the stored original function in case we replaced them
-        stats.get_timestamp = self.__orig_timestamp
-        stats.get_datetime = self.__orig_get_datetime
-
-    def test_init(self):
-        self.stats = stats.Stats()
-        self.assertEqual(self.stats.module_name, 'Stats')
-        self.assertFalse(self.stats.running)
-        self.assertTrue('command_show' in self.stats.callbacks)
-        self.assertTrue('command_status' in self.stats.callbacks)
-        self.assertTrue('command_shutdown' in self.stats.callbacks)
-        self.assertTrue('command_show' in self.stats.callbacks)
-        self.assertTrue('command_showschema' in self.stats.callbacks)
-        self.assertEqual(self.stats.config['poll-interval'], 60)
-
-    def test_init_undefcmd(self):
-        spec_str = """\
-{
-  "module_spec": {
-    "module_name": "Stats",
-    "module_description": "Stats daemon",
-    "config_data": [],
-    "commands": [
-      {
-        "command_name": "_undef_command_",
-        "command_description": "a undefined command in stats",
-        "command_args": []
-      }
-    ],
-    "statistics": []
-  }
-}
-"""
-        orig_spec_location = stats.SPECFILE_LOCATION
-        stats.SPECFILE_LOCATION = io.StringIO(spec_str)
-        self.assertRaises(stats.StatsError, stats.Stats)
-        stats.SPECFILE_LOCATION = orig_spec_location
-
-    def __send_command(self, stats, command_name, params=None):
-        '''Emulate a command arriving to stats by directly calling callback'''
-        return isc.config.ccsession.parse_answer(
-            stats.command_handler(command_name, params))
-
-    def test_start(self):
-        # Define a separate exception class so we can be sure that's actually
-        # the one raised in __check_start() below
-        class CheckException(Exception):
-            pass
-
-        def __check_start(tested_stats):
-            self.assertTrue(tested_stats.running)
-            raise CheckException # terminate the loop
-
-        # start without err
-        stats = SimpleStats()
-        self.assertFalse(stats.running)
-        stats._check_command = lambda: __check_start(stats)
-        # We are going to confirm start() will set running to True, avoiding
-        # to fall into a loop with the exception trick.
-        self.assertRaises(CheckException, stats.start)
-        self.assertEqual(self.__send_command(stats, "status"),
-                         (0, "Stats is up. (PID " + str(os.getpid()) + ")"))
-
-    def test_shutdown(self):
-        def __check_shutdown(tested_stats):
-            self.assertTrue(tested_stats.running)
-            self.assertEqual(self.__send_command(tested_stats, "shutdown"),
-                             (0, None))
-            self.assertFalse(tested_stats.running)
-            # override get_interval() so it won't go poll statistics
-            tested_stats.get_interval = lambda : 0
-
-        stats = SimpleStats()
-        stats._check_command = lambda: __check_shutdown(stats)
-        stats.start()
-        self.assertTrue(stats.mccs.stopped)
-
-    def test_handlers(self):
-        """Test command_handler"""
-
-        __stats = SimpleStats()
-
-        # 'show' command.  We're going to check the expected methods are
-        # called in the expected order, and check the resulting response.
-        # Details of each method are tested separately.
-        call_log = []
-        def __steal_method(fn_name, *arg):
-            call_log.append((fn_name, arg))
-            if fn_name == 'update_stat':
-                return False        # "no error"
-            if fn_name == 'showschema':
-                return isc.config.create_answer(0, 'no error')
-
-        # Fake some methods and attributes for inspection
-        __stats.do_polling = lambda: __steal_method('polling')
-        __stats.update_statistics_data = \
-            lambda x, y, z: __steal_method('update_stat', x, y, z)
-        __stats.update_modules = lambda: __steal_method('update_module')
-        __stats.mccs.lname = 'test lname'
-        __stats.statistics_data = {'Init': {'boot_time': self.const_datetime}}
-
-        # skip initial polling
-        stats.get_timestamp = lambda: 0
-        __stats._lasttime_poll = 0
-
-        stats.get_datetime = lambda: 42 # make the result predictable
-
-        # now send the command
-        self.assertEqual(
-            self.__send_command(
-                __stats, 'show',
-                params={ 'owner' : 'Init', 'name'  : 'boot_time' }),
-            (0, {'Init': {'boot_time': self.const_datetime}}))
-        # Check if expected methods are called
-        self.assertEqual([('update_stat',
-                           ('Stats', 'test lname',
-                            {'timestamp': 0,
-                             'report_time': 42})),
-                          ('update_module', ())], call_log)
-
-        # Then update faked timestamp so the initial polling will happen, and
-        # confirm that.
-        call_log = []
-        stats.get_timestamp = lambda: 10
-        self.assertEqual(
-            self.__send_command(
-                __stats, 'show',
-                params={ 'owner' : 'Init', 'name'  : 'boot_time' }),
-            (0, {'Init': {'boot_time': self.const_datetime}}))
-        self.assertEqual([('polling', ()),
-                          ('update_stat',
-                           ('Stats', 'test lname',
-                            {'timestamp': 10,
-                             'report_time': 42})),
-                          ('update_module', ())], call_log)
-
-        # 'status' command.  We can confirm the behavior without any fake
-        self.assertEqual(
-            self.__send_command(__stats, 'status'),
-            (0, "Stats is up. (PID " + str(os.getpid()) + ")"))
-
-        # 'showschema' command.  update_modules() will be called, which
-        # (implicitly) confirms the correct method is called; further details
-        # are tested separately.
-        call_log = []
-        (rcode, value) = self.__send_command(__stats, 'showschema')
-        self.assertEqual([('update_module', ())], call_log)
-
-        # Unknown command.  Error should be returned
-        self.assertEqual(
-            self.__send_command(__stats, '__UNKNOWN__'),
-            (1, "Unknown command: '__UNKNOWN__'"))
-
-    def test_update_modules(self):
-        """Confirm the behavior of Stats.update_modules().
-
-        It checks whether the expected command is sent to ConfigManager,
-        and whether the answer from ConfigManager is handled as expected.
-
-        """
-
-        def __check_rpc_call(command, group):
-            self.assertEqual('ConfigManager', group)
-            self.assertEqual(command,
-                             isc.config.ccsession.COMMAND_GET_STATISTICS_SPEC)
-            answer_value = {'Init': [{
-                        "item_name": "boot_time",
-                        "item_type": "string",
-                        "item_optional": False,
-                        # Use a different default so we can check it below
-                        "item_default": "2013-01-01T00:00:01Z",
-                        "item_title": "Boot time",
-                        "item_description": "dummy desc",
-                        "item_format": "date-time"
-                        }]}
-            return answer_value
-
-        self.stats = SimpleStats()
-        self.stats.cc_session.rpc_call = __check_rpc_call
-
-        self.stats.update_modules()
-
-        # Stats is always incorporated.  For others, only the ones returned
-        # by group_recvmsg() above is available.
-        self.assertTrue('Stats' in self.stats.modules)
-        self.assertTrue('Init' in self.stats.modules)
-        self.assertFalse('Dummy' in self.stats.modules)
-
-        my_statistics_data = stats.get_spec_defaults(
-            self.stats.modules['Stats'].get_statistics_spec())
-        self.assertTrue('report_time' in my_statistics_data)
-        self.assertTrue('boot_time' in my_statistics_data)
-        self.assertTrue('last_update_time' in my_statistics_data)
-        self.assertTrue('timestamp' in my_statistics_data)
-        self.assertTrue('lname' in my_statistics_data)
-        self.assertEqual(my_statistics_data['report_time'],
-                         self.const_default_datetime)
-        self.assertEqual(my_statistics_data['boot_time'],
-                         self.const_default_datetime)
-        self.assertEqual(my_statistics_data['last_update_time'],
-                         self.const_default_datetime)
-        self.assertEqual(my_statistics_data['timestamp'], 0.0)
-        self.assertEqual(my_statistics_data['lname'], "")
-        my_statistics_data = stats.get_spec_defaults(
-            self.stats.modules['Init'].get_statistics_spec())
-        self.assertTrue('boot_time' in my_statistics_data)
-        self.assertEqual(my_statistics_data['boot_time'],
-                         "2013-01-01T00:00:01Z")
-
-        # Error case
-        def __raise_on_rpc_call(x, y):
-            raise isc.config.RPCError(99, 'error')
-        orig_parse_answer = stats.isc.config.ccsession.parse_answer
-        self.stats.cc_session.rpc_call = __raise_on_rpc_call
-        self.assertRaises(stats.StatsError, self.stats.update_modules)
-
-    def test_get_statistics_data(self):
-        """Confirm the behavior of Stats.get_statistics_data().
-
-        It should first call update_modules(), and then retrieve the requested
-        data from statistics_data.  We confirm this by fake update_modules()
-        where we set the expected data in statistics_data.
-
-        """
-        self.stats = SimpleStats()
-        def __faked_update_modules():
-            self.stats.statistics_data = { \
-                'Stats': {
-                    'report_time': self.const_default_datetime,
-                    'boot_time': None,
-                    'last_update_time': None,
-                    'timestamp': 0.0,
-                    'lname': 'dummy name'
-                    },
-                'Init': { 'boot_time': None }
-                }
-
-        self.stats.update_modules = __faked_update_modules
-
-        my_statistics_data = self.stats.get_statistics_data()
-        self.assertTrue('Stats' in my_statistics_data)
-        self.assertTrue('Init' in my_statistics_data)
-        self.assertTrue('boot_time' in my_statistics_data['Init'])
-
-        my_statistics_data = self.stats.get_statistics_data(owner='Stats')
-        self.assertTrue('Stats' in my_statistics_data)
-        self.assertTrue('report_time' in my_statistics_data['Stats'])
-        self.assertTrue('boot_time' in my_statistics_data['Stats'])
-        self.assertTrue('last_update_time' in my_statistics_data['Stats'])
-        self.assertTrue('timestamp' in my_statistics_data['Stats'])
-        self.assertTrue('lname' in my_statistics_data['Stats'])
-        self.assertRaises(stats.StatsError, self.stats.get_statistics_data,
-                          owner='Foo')
-
-        my_statistics_data = self.stats.get_statistics_data(
-            owner='Stats', name='report_time')
-        self.assertEqual(my_statistics_data['Stats']['report_time'],
-                         self.const_default_datetime)
-
-        my_statistics_data = self.stats.get_statistics_data(
-            owner='Stats', name='boot_time')
-        self.assertTrue('boot_time' in my_statistics_data['Stats'])
-
-        my_statistics_data = self.stats.get_statistics_data(
-            owner='Stats', name='last_update_time')
-        self.assertTrue('last_update_time' in my_statistics_data['Stats'])
-
-        my_statistics_data = self.stats.get_statistics_data(
-            owner='Stats', name='timestamp')
-        self.assertEqual(my_statistics_data['Stats']['timestamp'], 0.0)
-
-        my_statistics_data = self.stats.get_statistics_data(
-            owner='Stats', name='lname')
-        self.assertTrue(len(my_statistics_data['Stats']['lname']) >0)
-        self.assertRaises(stats.StatsError, self.stats.get_statistics_data,
-                          owner='Stats', name='Bar')
-        self.assertRaises(stats.StatsError, self.stats.get_statistics_data,
-                          owner='Foo', name='Bar')
-        self.assertRaises(stats.StatsError, self.stats.get_statistics_data,
-                          name='Bar')
-
-    def test_update_statistics_data(self):
-        """test for list-type statistics"""
-        self.stats = SimpleStats()
-        _test_exp1 = {
-              'zonename': 'test1.example',
-              'queries.tcp': 5,
-              'queries.udp': 4
-            }
-        _test_exp2 = {
-              'zonename': 'test2.example',
-              'queries.tcp': 3,
-              'queries.udp': 2
-            }
-        _test_exp3 = {}
-        _test_exp4 = {
-              'queries.udp': 4
-            }
-        _test_exp5_1 = {
-              'queries.perzone': [
-                { },
-                {
-                  'queries.udp': 9876
-                }
-              ]
-            }
-        _test_exp5_2 = {
-              'queries.perzone[1]/queries.udp':
-                  isc.cc.data.find(_test_exp5_1,
-                                   'queries.perzone[1]/queries.udp')
-            }
-        # Success cases
-        self.assertEqual(self.stats.statistics_data['Stats']['lname'],
-                         self.stats.cc_session.lname)
-        self.stats.update_statistics_data(
-            'Stats', self.stats.cc_session.lname,
-            {'lname': 'foo at bar'})
-        self.assertEqual(self.stats.statistics_data['Stats']['lname'],
-                         'foo at bar')
-        self.assertIsNone(self.stats.update_statistics_data(
-            'Auth', 'foo1', {'queries.perzone': [_test_exp1]}))
-        self.assertEqual(self.stats.statistics_data_bymid['Auth']\
-                             ['foo1']['queries.perzone'],\
-                             [_test_exp1])
-        self.assertIsNone(self.stats.update_statistics_data(
-            'Auth', 'foo1', {'queries.perzone': [_test_exp2]}))
-        self.assertEqual(self.stats.statistics_data_bymid['Auth']\
-                             ['foo1']['queries.perzone'],\
-                             [_test_exp2])
-        self.assertIsNone(self.stats.update_statistics_data(
-            'Auth', 'foo1', {'queries.perzone': [_test_exp1,_test_exp2]}))
-        self.assertEqual(self.stats.statistics_data_bymid['Auth']\
-                             ['foo1']['queries.perzone'],
-                         [_test_exp1,_test_exp2])
-        # differential update
-        self.assertIsNone(self.stats.update_statistics_data(
-            'Auth', 'foo1', {'queries.perzone': [_test_exp3,_test_exp4]}))
-        _new_data = stats.merge_oldnew(_test_exp2,_test_exp4)
-        self.assertEqual(self.stats.statistics_data_bymid['Auth']\
-                             ['foo1']['queries.perzone'], \
-                             [_test_exp1,_new_data])
-        self.assertIsNone(self.stats.update_statistics_data(
-            'Auth', 'foo1', _test_exp5_2))
-        _new_data = stats.merge_oldnew(_new_data,
-                                       _test_exp5_1['queries.perzone'][1])
-        self.assertEqual(self.stats.statistics_data_bymid['Auth']\
-                             ['foo1']['queries.perzone'], \
-                             [_test_exp1,_new_data])
-        # Error cases
-        self.assertEqual(self.stats.update_statistics_data('Stats', None,
-                                                           {'lname': 0.0}),
-                         ['0.0 should be a string'])
-        self.assertEqual(self.stats.update_statistics_data('Dummy', None,
-                                                           {'foo': 'bar'}),
-                         ['unknown module name: Dummy'])
-        self.assertEqual(self.stats.update_statistics_data(
-                'Auth', 'foo1', {'queries.perzone': [None]}), ['None should be a map'])
-
-    def test_update_statistics_data_pt2(self):
-        """test for named_set-type statistics"""
-        self.stats = SimpleStats()
-        _test_exp1 = \
-            { 'test10.example': { 'queries.tcp': 5, 'queries.udp': 4 } }
-        _test_exp2 = \
-            { 'test20.example': { 'queries.tcp': 3, 'queries.udp': 2 } }
-        _test_exp3 = {}
-        _test_exp4 = { 'test20.example': { 'queries.udp': 4 } }
-        _test_exp5_1 = { 'test10.example': { 'queries.udp': 5432 } }
-        _test_exp5_2 ={
-              'nds_queries.perzone/test10.example/queries.udp':
-                  isc.cc.data.find(_test_exp5_1, 'test10.example/queries.udp')
-            }
-        _test_exp6 = { 'foo/bar':  'brabra' }
-        _test_exp7 = { 'foo[100]': 'bar' }
-        # Success cases
-        self.assertIsNone(self.stats.update_statistics_data(
-            'Auth', 'foo1', {'nds_queries.perzone': _test_exp1}))
-        self.assertEqual(self.stats.statistics_data_bymid['Auth']\
-                             ['foo1']['nds_queries.perzone'],\
-                             _test_exp1)
-        self.assertIsNone(self.stats.update_statistics_data(
-            'Auth', 'foo1', {'nds_queries.perzone': _test_exp2}))
-        self.assertEqual(self.stats.statistics_data_bymid['Auth']\
-                             ['foo1']['nds_queries.perzone'],\
-                         dict(_test_exp1,**_test_exp2))
-        self.assertIsNone(self.stats.update_statistics_data(
-            'Auth', 'foo1', {'nds_queries.perzone':
-                                 dict(_test_exp1, **_test_exp2)}))
-        self.assertEqual(self.stats.statistics_data_bymid['Auth']\
-                             ['foo1']['nds_queries.perzone'],
-                         dict(_test_exp1, **_test_exp2))
-        # differential update
-        self.assertIsNone(self.stats.update_statistics_data(
-            'Auth', 'foo1', {'nds_queries.perzone':
-                                 dict(_test_exp3, **_test_exp4)}))
-        _new_val = dict(_test_exp1,
-                        **stats.merge_oldnew(_test_exp2,_test_exp4))
-        self.assertEqual(self.stats.statistics_data_bymid['Auth']\
-                             ['foo1']['nds_queries.perzone'],\
-                             _new_val)
-        self.assertIsNone(self.stats.update_statistics_data(
-            'Auth', 'foo1', _test_exp5_2))
-        _new_val = stats.merge_oldnew(_new_val, _test_exp5_1)
-        self.assertEqual(self.stats.statistics_data_bymid['Auth']\
-                             ['foo1']['nds_queries.perzone'],\
-                             _new_val)
-        self.assertIsNone(self.stats.update_statistics_data(
-            'Auth', 'foo2', _test_exp5_2))
-        _new_val = stats.merge_oldnew(_new_val, _test_exp5_1)
-        self.assertEqual(self.stats.statistics_data_bymid['Auth']\
-                             ['foo2']['nds_queries.perzone'],\
-                             _test_exp5_1)
-        # Error cases
-        self.assertEqual(self.stats.update_statistics_data(
-                'Auth', 'foo1', {'nds_queries.perzone': None}),
-                         ['None should be a map'])
-        self.assertEqual(self.stats.statistics_data_bymid['Auth']\
-                             ['foo1']['nds_queries.perzone'],\
-                             _new_val)
-        self.assertEqual(self.stats.update_statistics_data(
-                'Auth', 'foo1', _test_exp6), ['unknown item foo'])
-        self.assertEqual(self.stats.statistics_data_bymid['Auth']\
-                             ['foo1']['nds_queries.perzone'],\
-                             _new_val)
-        self.assertEqual(self.stats.update_statistics_data(
-                'Init', 'bar1', _test_exp7), ["KeyError: 'foo'"])
-        self.assertEqual(self.stats.update_statistics_data(
-                'Foo', 'foo1', _test_exp6), ['unknown module name: Foo'])
-
-    def test_update_statistics_data_withmid(self):
-        self.stats = SimpleStats()
-
-        # This test relies on existing statistics data at the Stats object.
-        # This version of test prepares the data using the do_polling() method;
-        # that's a bad practice because a unittest for a method
-        # (update_statistics_data) would heavily depend on details of another
-        # method (do_polling).  However, there's currently no direct test
-        # for do_polling (which is also bad), so we still keep that approach,
-        # partly for testing do_polling indirectly.  #2781 should provide
-        # direct test for do_polling, with which this test scenario should
-        # also be changed to be more stand-alone.
-
-        # We use the knowledge of what kind of messages are sent via
-        # do_polling, and return the following faked answer directly.
-        create_answer = isc.config.ccsession.create_answer # shortcut
-        self.stats._answers = [\
-            # Answer for "show_processes"
-            (create_answer(0, [[1034, 'b10-auth-1', 'Auth'],
-                               [1035, 'b10-auth-2', 'Auth']]),  None),
-            # Answers for "getstats".  2 for Auth instances and 1 for Init.
-            # we return some bogus values for Init, but the rest of the test
-            # doesn't need it, so it's okay.
-            (create_answer(0, self.stats._auth_sdata), {'from': 'auth1'}),
-            (create_answer(0, self.stats._auth_sdata), {'from': 'auth2'}),
-            (create_answer(0, self.stats._auth_sdata), {'from': 'auth3'})
-            ]
-        # do_polling calls update_modules internally; in our scenario there's
-        # no change in modules, so we make it no-op.
-        self.stats.update_modules = lambda: None
-        # Now call do_polling.
-        self.stats.do_polling()
-
-        # samples of query number
-        bar1_tcp = 1001
-        bar2_tcp = 2001
-        bar3_tcp = 1002
-        bar3_udp = 1003
-        # two auth instances invoked, so we double the pre-set stat values
-        sum_qtcp = self.stats._queries_tcp * 2
-        sum_qudp = self.stats._queries_udp * 2
-        self.stats.update_statistics_data('Auth', "bar1 at foo",
-                                          {'queries.tcp': bar1_tcp})
-        self.assertTrue('Auth' in self.stats.statistics_data)
-        self.assertTrue('queries.tcp' in self.stats.statistics_data['Auth'])
-        self.assertEqual(self.stats.statistics_data['Auth']['queries.tcp'],
-                         bar1_tcp + sum_qtcp)
-        self.assertTrue('Auth' in self.stats.statistics_data_bymid)
-        self.assertTrue('bar1 at foo' in self.stats.statistics_data_bymid['Auth'])
-        self.assertTrue('queries.tcp' in self.stats.statistics_data_bymid
-                        ['Auth']['bar1 at foo'])
-        self.assertEqual(self.stats.statistics_data_bymid['Auth']['bar1 at foo'],
-                         {'queries.tcp': bar1_tcp})
-        # check consolidation of statistics data even if there is
-        # non-existent mid of Auth
-        self.stats.update_statistics_data('Auth', "bar2 at foo",
-                                          {'queries.tcp': bar2_tcp})
-        self.assertTrue('Auth' in self.stats.statistics_data)
-        self.assertTrue('queries.tcp' in self.stats.statistics_data['Auth'])
-        self.assertEqual(self.stats.statistics_data['Auth']['queries.tcp'],
-                         bar1_tcp + bar2_tcp + sum_qtcp)
-        self.assertTrue('Auth' in self.stats.statistics_data_bymid)
-        self.assertTrue('bar1 at foo' in self.stats.statistics_data_bymid['Auth'])
-        self.assertTrue('queries.tcp' in self.stats.statistics_data_bymid['Auth']['bar1 at foo'])
-        self.assertEqual(self.stats.statistics_data_bymid['Auth']['bar1 at foo'],
-                         {'queries.tcp': bar1_tcp})
-        self.assertEqual(self.stats.statistics_data_bymid['Auth']['bar2 at foo'],
-                         {'queries.tcp': bar2_tcp})
-        # kill running Auth but the statistics data doesn't change
-        self.base.auth2.server.shutdown()
-        self.stats.update_statistics_data()
-        self.assertTrue('Auth' in self.stats.statistics_data)
-        self.assertTrue('queries.tcp' in self.stats.statistics_data['Auth'])
-        self.assertTrue('queries.udp' in self.stats.statistics_data['Auth'])
-        self.assertEqual(self.stats.statistics_data['Auth']['queries.tcp'],
-                         bar1_tcp + bar2_tcp + sum_qtcp)
-        self.assertEqual(self.stats.statistics_data['Auth']['queries.udp'],
-                         sum_qudp)
-        self.assertTrue('Auth' in self.stats.statistics_data_bymid)
-        # restore statistics data of killed auth
-        # self.base.b10_init.server.pid_list = [ killed ] + self.base.b10_init.server.pid_list[:]
-        self.stats.update_statistics_data('Auth',
-                                          "bar1 at foo",
-                                          {'queries.tcp': bar1_tcp})
-        # set another mid of Auth
-        self.stats.update_statistics_data('Auth',
-                                          "bar3 at foo",
-                                          {'queries.tcp':bar3_tcp,
-                                           'queries.udp':bar3_udp})
-        self.assertTrue('Auth' in self.stats.statistics_data)
-        self.assertTrue('queries.tcp' in self.stats.statistics_data['Auth'])
-        self.assertTrue('queries.udp' in self.stats.statistics_data['Auth'])
-        self.assertEqual(self.stats.statistics_data['Auth']['queries.tcp'],
-                         bar1_tcp + bar2_tcp + bar3_tcp + sum_qtcp)
-        self.assertEqual(self.stats.statistics_data['Auth']['queries.udp'],
-                         bar3_udp + sum_qudp)
-        self.assertTrue('Auth' in self.stats.statistics_data_bymid)
-        self.assertTrue('bar1 at foo' in self.stats.statistics_data_bymid['Auth'])
-        self.assertTrue('bar3 at foo' in self.stats.statistics_data_bymid['Auth'])
-        self.assertTrue('queries.tcp' in self.stats.statistics_data_bymid['Auth']['bar1 at foo'])
-        self.assertTrue('queries.udp' in self.stats.statistics_data_bymid['Auth']['bar3 at foo'])
-        self.assertTrue('queries.udp' in self.stats.statistics_data_bymid['Auth']['bar3 at foo'])
-        self.assertEqual(self.stats.statistics_data_bymid['Auth']['bar1 at foo']['queries.tcp'], bar1_tcp)
-        self.assertEqual(self.stats.statistics_data_bymid['Auth']['bar3 at foo']['queries.tcp'], bar3_tcp)
-        self.assertEqual(self.stats.statistics_data_bymid['Auth']['bar3 at foo']['queries.udp'], bar3_udp)
-
-    def test_config(self):
-        orig_get_timestamp = stats.get_timestamp
-        stats.get_timestamp = lambda : self.const_timestamp
-        stat = SimpleStats()
-
-        # test updating poll-interval
-        self.assertEqual(stat.config['poll-interval'], 60)
-        self.assertEqual(stat.get_interval(), 60)
-        self.assertEqual(stat.next_polltime, self.const_timestamp + 60)
-        self.assertEqual(stat.config_handler({'poll-interval': 120}),
-                         isc.config.create_answer(0))
-        self.assertEqual(stat.config['poll-interval'], 120)
-        self.assertEqual(stat.get_interval(), 120)
-        self.assertEqual(stat.next_polltime, self.const_timestamp + 120)
-        stats.get_timestamp = orig_get_timestamp
-        self.assertEqual(stat.config_handler({'poll-interval': "foo"}),
-                         isc.config.create_answer(1, 'foo should be an integer'))
-        self.assertEqual(stat.config_handler({'poll-interval': -1}),
-                         isc.config.create_answer(1, 'Negative integer ignored'))
-        # unknown item
-        self.assertEqual(
-            stat.config_handler({'_UNKNOWN_KEY_': None}),
-            isc.config.ccsession.create_answer(
-                1, "unknown item _UNKNOWN_KEY_"))
-        # test no change if zero interval time
-        self.assertEqual(stat.config_handler({'poll-interval': 0}),
-                         isc.config.create_answer(0))
-        self.assertEqual(stat.config['poll-interval'], 0)
-
-        # see the comment for test_update_statistics_data_withmid.  We abuse
-        # do_polling here, too.  With #2781 we should make it more direct.
-        create_answer = isc.config.ccsession.create_answer # shortcut
-        stat._answers = [\
-            # Answer for "show_processes"
-            (create_answer(0, []),  None),
-            # Answers for "getstats" for Init (the other one for Auth, but
-            # that doesn't matter for this test)
-            (create_answer(0, stat._init_sdata), {'from': 'init'}),
-            (create_answer(0, stat._init_sdata), {'from': 'init'})
-            ]
-        stat.update_modules = lambda: None
-
-        self.assertEqual(
-            self.__send_command(
-                stat, 'show',
-                params={ 'owner' : 'Init', 'name'  : 'boot_time' }),
-            (0, {'Init': {'boot_time': self.const_datetime}}))
-
-    def test_commands(self):
-        self.stats = stats.Stats()
-
-        # status
-        self.assertEqual(self.stats.command_status(),
-                isc.config.create_answer(
-                0, "Stats is up. (PID " + str(os.getpid()) + ")"))
-
-        # shutdown
-        self.stats.running = True
-        self.assertEqual(self.stats.command_shutdown(),
-                         isc.config.create_answer(0))
-        self.assertFalse(self.stats.running)
-
-    @unittest.skipIf(sys.version_info >= (3, 3), "Unsupported in Python 3.3 or higher")
-    def test_command_show(self):
-        # two auth instances invoked
-        list_auth = [ self.base.auth.server,
-                      self.base.auth2.server ]
-        sum_qtcp = 0
-        sum_qudp = 0
-        sum_qtcp_perzone1 = 0
-        sum_qudp_perzone1 = 0
-        sum_qtcp_perzone2 = 4 * len(list_auth)
-        sum_qudp_perzone2 = 3 * len(list_auth)
-        sum_qtcp_nds_perzone10 = 0
-        sum_qudp_nds_perzone10 = 0
-        sum_qtcp_nds_perzone20 = 4 * len(list_auth)
-        sum_qudp_nds_perzone20 = 3 * len(list_auth)
-        self.stats = stats.Stats()
-        self.assertEqual(self.stats.command_show(owner='Foo', name=None),
-                         isc.config.create_answer(
-                1, "specified arguments are incorrect: owner: Foo, name: None"))
-        self.assertEqual(self.stats.command_show(owner='Foo', name='_bar_'),
-                         isc.config.create_answer(
-                1, "specified arguments are incorrect: owner: Foo, name: _bar_"))
-        self.assertEqual(self.stats.command_show(owner='Foo', name='bar'),
-                         isc.config.create_answer(
-                1, "specified arguments are incorrect: owner: Foo, name: bar"))
-
-        for a in list_auth:
-            sum_qtcp += a.queries_tcp
-            sum_qudp += a.queries_udp
-            sum_qtcp_perzone1 += a.queries_per_zone[0]['queries.tcp']
-            sum_qudp_perzone1 += a.queries_per_zone[0]['queries.udp']
-            sum_qtcp_nds_perzone10 += a.nds_queries_per_zone['test10.example']['queries.tcp']
-            sum_qudp_nds_perzone10 += a.nds_queries_per_zone['test10.example']['queries.udp']
-
-        self.assertEqual(self.stats.command_show(owner='Auth'),
-                         isc.config.create_answer(
-                0, {'Auth':{ 'queries.udp': sum_qudp,
-                     'queries.tcp': sum_qtcp,
-                     'queries.perzone': [{ 'zonename': 'test1.example',
-                                           'queries.udp': sum_qudp_perzone1,
-                                           'queries.tcp': sum_qtcp_perzone1 },
-                                         { 'zonename': 'test2.example',
-                                           'queries.udp': sum_qudp_perzone2,
-                                           'queries.tcp': sum_qtcp_perzone2 }
-                                         ],
-                     'nds_queries.perzone': { 'test10.example' : {
-                                              'queries.udp': sum_qudp_nds_perzone10,
-                                              'queries.tcp': sum_qtcp_nds_perzone10 },
-                                              'test20.example' : {
-                                              'queries.udp': sum_qudp_nds_perzone20,
-                                              'queries.tcp': sum_qtcp_nds_perzone20 }
-                             }}}))
-        self.assertEqual(self.stats.command_show(owner='Auth', name='queries.udp'),
-                         isc.config.create_answer(
-                0, {'Auth': {'queries.udp': sum_qudp}}))
-        self.assertEqual(self.stats.command_show(owner='Auth', name='queries.perzone'),
-                         isc.config.create_answer(
-                0, {'Auth': {'queries.perzone': [
-                            { 'zonename': 'test1.example',
-                              'queries.udp': sum_qudp_perzone1,
-                              'queries.tcp': sum_qtcp_perzone1 },
-                            { 'zonename': 'test2.example',
-                              'queries.udp': sum_qudp_perzone2,
-                              'queries.tcp': sum_qtcp_perzone2 }]}}))
-        self.assertEqual(self.stats.command_show(owner='Auth', name='nds_queries.perzone'),
-                         isc.config.create_answer(
-                0, {'Auth': {'nds_queries.perzone': {
-                            'test10.example': {
-                                'queries.udp': sum_qudp_nds_perzone10,
-                                'queries.tcp': sum_qtcp_nds_perzone10 },
-                            'test20.example': {
-                                'queries.udp': sum_qudp_nds_perzone20,
-                                'queries.tcp': sum_qtcp_nds_perzone20 }}}}))
-        orig_get_datetime = stats.get_datetime
-        orig_get_timestamp = stats.get_timestamp
-        stats.get_datetime = lambda x=None: self.const_datetime
-        stats.get_timestamp = lambda : self.const_timestamp
-        self.assertEqual(self.stats.command_show(owner='Stats', name='report_time'),
-                         isc.config.create_answer(
-                0, {'Stats': {'report_time':self.const_datetime}}))
-        self.assertEqual(self.stats.command_show(owner='Stats', name='timestamp'),
-                         isc.config.create_answer(
-                0, {'Stats': {'timestamp':self.const_timestamp}}))
-        stats.get_datetime = orig_get_datetime
-        stats.get_timestamp = orig_get_timestamp
-        self.stats.modules[self.stats.module_name] = isc.config.module_spec.ModuleSpec(
-            { "module_name": self.stats.module_name,
-              "statistics": [] } )
-        self.assertRaises(
-            stats.StatsError, self.stats.command_show, owner=self.stats.module_name, name='bar')
-
-    def test_command_showchema(self):
-        self.stats = stats.Stats()
-        (rcode, value) = isc.config.ccsession.parse_answer(
-            self.stats.command_showschema())
-        self.assertEqual(rcode, 0)
-        self.assertEqual(len(value), 3)
-        self.assertTrue('Stats' in value)
-        self.assertTrue('Init' in value)
-        self.assertTrue('Auth' in value)
-        self.assertFalse('__Dummy__' in value)
-        schema = value['Stats']
-        self.assertEqual(len(schema), 5)
-        for item in schema:
-            self.assertTrue(len(item) == 6 or len(item) == 7)
-            self.assertTrue('item_name' in item)
-            self.assertTrue('item_type' in item)
-            self.assertTrue('item_optional' in item)
-            self.assertTrue('item_default' in item)
-            self.assertTrue('item_title' in item)
-            self.assertTrue('item_description' in item)
-            if len(item) == 7:
-                self.assertTrue('item_format' in item)
-
-        schema = value['Init']
-        self.assertEqual(len(schema), 1)
-        for item in schema:
-            self.assertTrue(len(item) == 7)
-            self.assertTrue('item_name' in item)
-            self.assertTrue('item_type' in item)
-            self.assertTrue('item_optional' in item)
-            self.assertTrue('item_default' in item)
-            self.assertTrue('item_title' in item)
-            self.assertTrue('item_description' in item)
-            self.assertTrue('item_format' in item)
-
-        schema = value['Auth']
-        self.assertEqual(len(schema), 4)
-        for item in schema:
-            if item['item_type'] == 'list' or item['item_type'] == 'named_set':
-                self.assertEqual(len(item), 7)
-            else:
-                self.assertEqual(len(item), 6)
-            self.assertTrue('item_name' in item)
-            self.assertTrue('item_type' in item)
-            self.assertTrue('item_optional' in item)
-            self.assertTrue('item_default' in item)
-            self.assertTrue('item_title' in item)
-            self.assertTrue('item_description' in item)
-
-        (rcode, value) = isc.config.ccsession.parse_answer(
-            self.stats.command_showschema(owner='Stats'))
-        self.assertEqual(rcode, 0)
-        self.assertTrue('Stats' in value)
-        self.assertFalse('Init' in value)
-        self.assertFalse('Auth' in value)
-        for item in value['Stats']:
-            self.assertTrue(len(item) == 6 or len(item) == 7)
-            self.assertTrue('item_name' in item)
-            self.assertTrue('item_type' in item)
-            self.assertTrue('item_optional' in item)
-            self.assertTrue('item_default' in item)
-            self.assertTrue('item_title' in item)
-            self.assertTrue('item_description' in item)
-            if len(item) == 7:
-                self.assertTrue('item_format' in item)
-
-        (rcode, value) = isc.config.ccsession.parse_answer(
-            self.stats.command_showschema(owner='Stats', name='report_time'))
-        self.assertEqual(rcode, 0)
-        self.assertTrue('Stats' in value)
-        self.assertFalse('Init' in value)
-        self.assertFalse('Auth' in value)
-        self.assertEqual(len(value['Stats'][0]), 7)
-        self.assertTrue('item_name' in value['Stats'][0])
-        self.assertTrue('item_type' in value['Stats'][0])
-        self.assertTrue('item_optional' in value['Stats'][0])
-        self.assertTrue('item_default' in value['Stats'][0])
-        self.assertTrue('item_title' in value['Stats'][0])
-        self.assertTrue('item_description' in value['Stats'][0])
-        self.assertTrue('item_format' in value['Stats'][0])
-        self.assertEqual(value['Stats'][0]['item_name'], 'report_time')
-        self.assertEqual(value['Stats'][0]['item_format'], 'date-time')
-
-        self.assertEqual(self.stats.command_showschema(owner='Foo'),
-                         isc.config.create_answer(
-                1, "specified arguments are incorrect: owner: Foo, name: None"))
-        self.assertEqual(self.stats.command_showschema(owner='Foo', name='bar'),
-                         isc.config.create_answer(
-                1, "specified arguments are incorrect: owner: Foo, name: bar"))
-        self.assertEqual(self.stats.command_showschema(owner='Auth'),
-                         isc.config.create_answer(
-                0, {'Auth': [{
-                        "item_default": 0,
-                        "item_description": "A number of total query counts which all auth servers receive over TCP since they started initially",
-                        "item_name": "queries.tcp",
-                        "item_optional": False,
-                        "item_title": "Queries TCP",
-                        "item_type": "integer"
-                        },
-                    {
-                        "item_default": 0,
-                        "item_description": "A number of total query counts which all auth servers receive over UDP since they started initially",
-                        "item_name": "queries.udp",
-                        "item_optional": False,
-                        "item_title": "Queries UDP",
-                        "item_type": "integer"
-                        },
-                    {
-                        "item_name": "queries.perzone",
-                        "item_type": "list",
-                        "item_optional": False,
-                        "item_default": [
-                            {
-                                "zonename" : "test1.example",
-                                "queries.udp" : 1,
-                                "queries.tcp" : 2
-                                },
-                            {
-                                "zonename" : "test2.example",
-                                "queries.udp" : 3,
-                                "queries.tcp" : 4
-                                }
-                        ],
-                        "item_title": "Queries per zone",
-                        "item_description": "Queries per zone",
-                        "list_item_spec": {
-                            "item_name": "zones",
-                            "item_type": "map",
-                            "item_optional": False,
-                            "item_default": {},
-                            "map_item_spec": [
-                                {
-                                    "item_name": "zonename",
-                                    "item_type": "string",
-                                    "item_optional": False,
-                                    "item_default": "",
-                                    "item_title": "Zonename",
-                                    "item_description": "Zonename"
-                                    },
-                                {
-                                    "item_name": "queries.udp",
-                                    "item_type": "integer",
-                                    "item_optional": False,
-                                    "item_default": 0,
-                                    "item_title": "Queries UDP per zone",
-                                    "item_description": "A number of UDP query counts per zone"
-                                    },
-                                {
-                                    "item_name": "queries.tcp",
-                                    "item_type": "integer",
-                                    "item_optional": False,
-                                    "item_default": 0,
-                                    "item_title": "Queries TCP per zone",
-                                    "item_description": "A number of TCP query counts per zone"
-                                    }
-                                ]
-                            }
-                        },
-                    {
-                        "item_name": "nds_queries.perzone",
-                        "item_type": "named_set",
-                        "item_optional": False,
-                        "item_default": {
-                            "test10.example" : {
-                                "queries.udp" : 1,
-                                "queries.tcp" : 2
-                            },
-                            "test20.example" : {
-                                "queries.udp" : 3,
-                                "queries.tcp" : 4
-                            }
-                        },
-                        "item_title": "Queries per zone",
-                        "item_description": "Queries per zone",
-                        "named_set_item_spec": {
-                            "item_name": "zonename",
-                            "item_type": "map",
-                            "item_optional": False,
-                            "item_default": {},
-                            "item_title": "Zonename",
-                            "item_description": "Zonename",
-                            "map_item_spec": [
-                                {
-                                    "item_name": "queries.udp",
-                                    "item_type": "integer",
-                                    "item_optional": False,
-                                    "item_default": 0,
-                                    "item_title": "Queries UDP per zone",
-                                    "item_description": "A number of UDP query counts per zone"
-                                    },
-                                {
-                                    "item_name": "queries.tcp",
-                                    "item_type": "integer",
-                                    "item_optional": False,
-                                    "item_default": 0,
-                                    "item_title": "Queries TCP per zone",
-                                    "item_description": "A number of TCP query counts per zone"
-                                    }
-                                ]
-                            }
-                        }]}))
-        self.assertEqual(self.stats.command_showschema(owner='Auth', name='queries.tcp'),
-                         isc.config.create_answer(
-                0, {'Auth': [{
-                    "item_default": 0,
-                    "item_description": "A number of total query counts which all auth servers receive over TCP since they started initially",
-                    "item_name": "queries.tcp",
-                    "item_optional": False,
-                    "item_title": "Queries TCP",
-                    "item_type": "integer"
-                    }]}))
-        self.assertEqual(self.stats.command_showschema(owner='Auth', name='queries.perzone'),
-                         isc.config.create_answer(
-                0, {'Auth':[{
-                    "item_name": "queries.perzone",
-                    "item_type": "list",
-                    "item_optional": False,
-                    "item_default": [
-                        {
-                            "zonename" : "test1.example",
-                            "queries.udp" : 1,
-                            "queries.tcp" : 2
-                            },
-                        {
-                            "zonename" : "test2.example",
-                            "queries.udp" : 3,
-                            "queries.tcp" : 4
-                            }
-                    ],
-                    "item_title": "Queries per zone",
-                    "item_description": "Queries per zone",
-                    "list_item_spec": {
-                        "item_name": "zones",
-                        "item_type": "map",
-                        "item_optional": False,
-                        "item_default": {},
-                        "map_item_spec": [
-                            {
-                                "item_name": "zonename",
-                                "item_type": "string",
-                                "item_optional": False,
-                                "item_default": "",
-                                "item_title": "Zonename",
-                                "item_description": "Zonename"
-                                },
-                            {
-                                "item_name": "queries.udp",
-                                "item_type": "integer",
-                                "item_optional": False,
-                                "item_default": 0,
-                                "item_title": "Queries UDP per zone",
-                                "item_description": "A number of UDP query counts per zone"
-                                },
-                            {
-                                "item_name": "queries.tcp",
-                                "item_type": "integer",
-                                "item_optional": False,
-                                "item_default": 0,
-                                "item_title": "Queries TCP per zone",
-                                "item_description": "A number of TCP query counts per zone"
-                                }
-                            ]
-                         }
-                     }]}))
-        self.assertEqual(self.stats.command_showschema(owner='Auth', name='nds_queries.perzone'),
-                         isc.config.create_answer(
-                0, {'Auth':[{
-                    "item_name": "nds_queries.perzone",
-                    "item_type": "named_set",
-                    "item_optional": False,
-                    "item_default": {
-                        "test10.example" : {
-                            "queries.udp" : 1,
-                            "queries.tcp" : 2
-                        },
-                        "test20.example" : {
-                            "queries.udp" : 3,
-                            "queries.tcp" : 4
-                        }
-                    },
-                    "item_title": "Queries per zone",
-                    "item_description": "Queries per zone",
-                    "named_set_item_spec": {
-                        "item_name": "zonename",
-                        "item_type": "map",
-                        "item_optional": False,
-                        "item_default": {},
-                        "item_title": "Zonename",
-                        "item_description": "Zonename",
-                        "map_item_spec": [
-                            {
-                                "item_name": "queries.udp",
-                                "item_type": "integer",
-                                "item_optional": False,
-                                "item_default": 0,
-                                "item_title": "Queries UDP per zone",
-                                "item_description": "A number of UDP query counts per zone"
-                                },
-                            {
-                                "item_name": "queries.tcp",
-                                "item_type": "integer",
-                                "item_optional": False,
-                                "item_default": 0,
-                                "item_title": "Queries TCP per zone",
-                                "item_description": "A number of TCP query counts per zone"
-                                }
-                            ]
-                        }
-                    }]}))
-
-        self.assertEqual(self.stats.command_showschema(owner='Stats', name='bar'),
-                         isc.config.create_answer(
-                1, "specified arguments are incorrect: owner: Stats, name: bar"))
-        self.assertEqual(self.stats.command_showschema(name='bar'),
-                         isc.config.create_answer(
-                1, "module name is not specified"))
-
-    @unittest.skipIf(sys.version_info >= (3, 3), "Unsupported in Python 3.3 or higher")
-    def test_polling(self):
-        stats_server = ThreadingServerManager(MyStats)
-        stat = stats_server.server
-        stats_server.run()
-        self.assertEqual(
-            send_command('show', 'Stats'),
-            (0, stat.statistics_data))
-        # check statistics data of 'Init'
-        b10_init = self.base.b10_init.server
-        self.assertEqual(
-            stat.statistics_data_bymid['Init'][b10_init.cc_session.lname],
-            {'boot_time': self.const_datetime})
-        self.assertEqual(
-            len(stat.statistics_data_bymid['Init']), 1)
-        self.assertEqual(
-            stat.statistics_data['Init'],
-            {'boot_time': self.const_datetime})
-        # check statistics data of each 'Auth' instances
-        list_auth = ['', '2']
-        for i in list_auth:
-            auth = getattr(self.base,"auth"+i).server
-            for s in stat.statistics_data_bymid['Auth'].values():
-                self.assertEqual(
-                    s, {'queries.perzone': auth.queries_per_zone,
-                        'nds_queries.perzone': auth.nds_queries_per_zone,
-                        'queries.tcp': auth.queries_tcp,
-                        'queries.udp': auth.queries_udp})
-            n = len(stat.statistics_data_bymid['Auth'])
-            self.assertEqual(n, len(list_auth))
-            # check consolidation of statistics data of the auth
-            # instances
-            self.assertEqual(
-                stat.statistics_data['Auth'],
-                {'queries.perzone': [
-                        {'zonename':
-                             auth.queries_per_zone[0]['zonename'],
-                         'queries.tcp':
-                             auth.queries_per_zone[0]['queries.tcp']*n,
-                         'queries.udp':
-                             auth.queries_per_zone[0]['queries.udp']*n},
-                        {'zonename': "test2.example",
-                         'queries.tcp': 4*n,
-                         'queries.udp': 3*n },
-                        ],
-                 'nds_queries.perzone': {
-                         'test10.example': {
-                             'queries.tcp':
-                                 auth.nds_queries_per_zone['test10.example']['queries.tcp']*n,
-                             'queries.udp':
-                                 auth.nds_queries_per_zone['test10.example']['queries.udp']*n},
-                         'test20.example': {
-                             'queries.tcp':
-                                 4*n,
-                             'queries.udp':
-                                 3*n},
-                         },
-                 'queries.tcp': auth.queries_tcp*n,
-                 'queries.udp': auth.queries_udp*n})
-        # check statistics data of 'Stats'
-        self.assertEqual(
-            len(stat.statistics_data['Stats']), 5)
-        self.assertTrue('boot_time' in
-            stat.statistics_data['Stats'])
-        self.assertTrue('last_update_time' in
-            stat.statistics_data['Stats'])
-        self.assertTrue('report_time' in
-            stat.statistics_data['Stats'])
-        self.assertTrue('timestamp' in
-            stat.statistics_data['Stats'])
-        self.assertEqual(
-            stat.statistics_data['Stats']['lname'],
-            stat.mccs._session.lname)
-        stats_server.shutdown()
-
-    def test_polling2(self):
-        # set invalid statistics
-        b10_init = self.base.b10_init.server
-        b10_init.statistics_data = {'boot_time':1}
-        stats_server = ThreadingServerManager(MyStats)
-        stat = stats_server.server
-        stats_server.run()
-        self.assertEqual(
-            send_command('status', 'Stats'),
-            (0, "Stats is up. (PID " + str(os.getpid()) + ")"))
-        # check default statistics data of 'Init'
-        self.assertEqual(
-            stat.statistics_data['Init'],
-            {'boot_time': self.const_default_datetime})
-        stats_server.shutdown()
-
-class TestOSEnv(unittest.TestCase):
-    def test_osenv(self):
-        """
-        test for the environ variable "B10_FROM_SOURCE"
-        "B10_FROM_SOURCE" is set in Makefile
-        """
-        # test case having B10_FROM_SOURCE
-        self.assertTrue("B10_FROM_SOURCE" in os.environ)
-        self.assertEqual(stats.SPECFILE_LOCATION, \
-                             os.environ["B10_FROM_SOURCE"] + os.sep + \
-                             "src" + os.sep + "bin" + os.sep + "stats" + \
-                             os.sep + "stats.spec")
-        # test case not having B10_FROM_SOURCE
-        path = os.environ["B10_FROM_SOURCE"]
-        os.environ.pop("B10_FROM_SOURCE")
-        self.assertFalse("B10_FROM_SOURCE" in os.environ)
-        # import stats again
-        imp.reload(stats)
-        # revert the changes
-        os.environ["B10_FROM_SOURCE"] = path
-        imp.reload(stats)
-
-if __name__ == "__main__":
-    isc.log.resetUnitTestRootLogger()
-    unittest.main()
diff --git a/src/bin/stats/tests/stats-httpd_test.py b/src/bin/stats/tests/stats-httpd_test.py
new file mode 100644
index 0000000..a61ee47
--- /dev/null
+++ b/src/bin/stats/tests/stats-httpd_test.py
@@ -0,0 +1,1127 @@
+# Copyright (C) 2011-2012  Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+"""
+In each of these tests we start several virtual components. They are
+not the real components, no external processes are started. They are
+just simple mock objects running each in its own thread and pretending
+to be bind10 modules. This helps testing the stats http server in a
+close to real environment.
+"""
+
+import unittest
+import os
+import imp
+import socket
+import errno
+import select
+import string
+import time
+import threading
+import http.client
+import xml.etree.ElementTree
+import random
+import urllib.parse
+import sys
+# load this module for xml validation with xsd. For this test, an
+# installation of lxml is required in advance. See http://lxml.de/.
+try:
+    from lxml import etree as lxml_etree
+except ImportError:
+    lxml_etree = None
+
+import isc
+import isc.log
+import stats_httpd
+import stats
+from test_utils import ThreadingServerManager, SignalHandler, \
+    MyStatsHttpd, CONST_BASETIME
+from isc.testutils.ccsession_mock import MockModuleCCSession
+from isc.config import RPCRecipientMissing, RPCError
+
+# This test suite uses xml.etree.ElementTree.XMLParser via
+# xml.etree.ElementTree.parse. On the platform where expat isn't
+# installed, ImportError is raised and it's failed. Check expat is
+# available before the test invocation. Skip this test if it's
+# unavailable.
+try:
+    # ImportError raised if xpat is unavailable
+    xml_parser = xml.etree.ElementTree.XMLParser()
+except ImportError:
+    xml_parser = None
+
+# set XML Namespaces for testing
+XMLNS_XSL = "http://www.w3.org/1999/XSL/Transform"
+XMLNS_XHTML = "http://www.w3.org/1999/xhtml"
+XMLNS_XSD = "http://www.w3.org/2001/XMLSchema"
+XMLNS_XSI = stats_httpd.XMLNS_XSI
+
+DUMMY_DATA = {
+    'Init' : {
+        "boot_time": time.strftime('%Y-%m-%dT%H:%M:%SZ', CONST_BASETIME)
+        },
+    'Auth' : {
+        "queries.tcp": 6,
+        "queries.udp": 4,
+        "queries.perzone": [{
+                "zonename": "test1.example",
+                "queries.tcp": 10,
+                "queries.udp": 8
+                }, {
+                "zonename": "test2.example",
+                "queries.tcp": 8,
+                "queries.udp": 6
+                }],
+        "nds_queries.perzone": {
+                "test10.example": {
+                    "queries.tcp": 10,
+                    "queries.udp": 8
+                  },
+                "test20.example": {
+                    "queries.tcp": 8,
+                    "queries.udp": 6
+                  }
+                }
+        },
+    'Stats' : {
+        "report_time": time.strftime('%Y-%m-%dT%H:%M:%SZ', CONST_BASETIME),
+        "boot_time": time.strftime('%Y-%m-%dT%H:%M:%SZ', CONST_BASETIME),
+        "last_update_time": time.strftime('%Y-%m-%dT%H:%M:%SZ', CONST_BASETIME),
+        "lname": "4d70d40a_c at host",
+        "timestamp": time.mktime(CONST_BASETIME)
+        }
+    }
+
+# Bad practice: this should be localized
+stats._BASETIME = CONST_BASETIME
+stats.get_timestamp = lambda: time.mktime(CONST_BASETIME)
+stats.get_datetime = lambda x=None: time.strftime("%Y-%m-%dT%H:%M:%SZ", CONST_BASETIME)
+
+def get_availaddr(address='127.0.0.1', port=8001):
+    """returns a tuple of address and port which is available to
+    listen on the platform. The first argument is a address for
+    search. The second argument is a port for search. If a set of
+    address and port is failed on the search for the availability, the
+    port number is increased and it goes on the next trial until the
+    available set of address and port is looked up. If the port number
+    reaches over 65535, it may stop the search and raise a
+    OverflowError exception."""
+    while True:
+        for addr in socket.getaddrinfo(
+            address, port, 0,
+            socket.SOCK_STREAM, socket.IPPROTO_TCP):
+            sock = socket.socket(addr[0], socket.SOCK_STREAM)
+            try:
+                sock.bind((address, port))
+                return (address, port)
+            except socket.error:
+                continue
+            finally:
+                if sock: sock.close()
+        # This address and port number are already in use.
+        # next port number is added
+        port = port + 1
+
+def is_ipv6_enabled(address='::1', port=8001):
+    """checks IPv6 enabled on the platform. address for check is '::1'
+    and port for check is random number between 8001 and
+    65535. Retrying is 3 times even if it fails. The built-in socket
+    module provides a 'has_ipv6' parameter, but it's not used here
+    because there may be a situation where the value is True on an
+    environment where the IPv6 config is disabled."""
+    for p in random.sample(range(port, 65535), 3):
+        try:
+            sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
+            sock.bind((address, p))
+            return True
+        except socket.error:
+            continue
+        finally:
+            if sock: sock.close()
+    return False
+
+class TestItemNameList(unittest.TestCase):
+
+    def test_item_name_list(self):
+        # for a one-element list
+        self.assertEqual(['a'],
+                         stats_httpd.item_name_list({'a':1}, 'a'))
+        # for a dict under a dict
+        self.assertEqual(['a','a/b'],
+                         stats_httpd.item_name_list({'a':{'b':1}}, 'a'))
+        self.assertEqual(['a/b'],
+                         stats_httpd.item_name_list({'a':{'b':1}}, 'a/b'))
+        self.assertEqual(['a','a/b','a/b/c'],
+                         stats_httpd.item_name_list({'a':{'b':{'c':1}}}, 'a'))
+        self.assertEqual(['a/b','a/b/c'],
+                         stats_httpd.item_name_list({'a':{'b':{'c':1}}},
+                                                    'a/b'))
+        self.assertEqual(['a/b/c'],
+                         stats_httpd.item_name_list({'a':{'b':{'c':1}}},
+                                                    'a/b/c'))
+        # for a list under a dict
+        self.assertEqual(['a[2]'],
+                         stats_httpd.item_name_list({'a':[1,2,3]}, 'a[2]'))
+        self.assertEqual(['a', 'a[0]', 'a[1]', 'a[2]'],
+                         stats_httpd.item_name_list({'a':[1,2,3]}, 'a'))
+        self.assertEqual(['a', 'a[0]', 'a[1]', 'a[2]'],
+                         stats_httpd.item_name_list({'a':[1,2,3]}, ''))
+        # for a list under a dict under a dict
+        self.assertEqual(['a', 'a/b', 'a/b[0]', 'a/b[1]', 'a/b[2]'],
+                         stats_httpd.item_name_list({'a':{'b':[1,2,3]}}, 'a'))
+        self.assertEqual(['a', 'a/b', 'a/b[0]', 'a/b[1]', 'a/b[2]'],
+                         stats_httpd.item_name_list({'a':{'b':[1,2,3]}}, ''))
+        self.assertEqual(['a/b', 'a/b[0]', 'a/b[1]', 'a/b[2]'],
+                         stats_httpd.item_name_list({'a':{'b':[1,2,3]}}, 'a/b'))
+        # for a mixed case of the above
+        self.assertEqual(['a', 'a/b', 'a/b[0]', 'a/b[1]', 'a/b[2]', 'a/c'],
+                         stats_httpd.item_name_list(
+                {'a':{'b':[1,2,3], 'c':1}}, 'a'))
+        self.assertEqual(['a/b', 'a/b[0]', 'a/b[1]', 'a/b[2]'],
+                         stats_httpd.item_name_list(
+                {'a':{'b':[1,2,3], 'c':1}}, 'a/b'))
+        self.assertEqual(['a/c'],
+                         stats_httpd.item_name_list(
+                {'a':{'b':[1,2,3], 'c':1}}, 'a/c'))
+        # for specifying a wrong identifier which is not found in
+        # element
+        self.assertRaises(isc.cc.data.DataNotFoundError,
+                         stats_httpd.item_name_list, {'x':1}, 'a')
+        # for specifying a string in element and an empty string in
+        # identifier
+        self.assertEqual([],
+                         stats_httpd.item_name_list('a', ''))
+        # for specifying empty strings in element and identifier
+        self.assertEqual([],
+                         stats_httpd.item_name_list('', ''))
+        # for specifying wrong element, which is an non-empty string,
+        # and an non-empty string in identifier
+        self.assertRaises(isc.cc.data.DataTypeError,
+                         stats_httpd.item_name_list, 'a', 'a')
+        # for specifying None in element and identifier
+        self.assertRaises(isc.cc.data.DataTypeError,
+                         stats_httpd.item_name_list, None, None)
+        # for specifying non-dict in element
+        self.assertRaises(isc.cc.data.DataTypeError,
+                         stats_httpd.item_name_list, [1,2,3], 'a')
+        self.assertRaises(isc.cc.data.DataTypeError,
+                         stats_httpd.item_name_list, [1,2,3], '')
+        # for checking key names sorted which consist of element
+        num = 11
+        keys = [ 'a', 'aa', 'b' ]
+        keys.sort(reverse=True)
+        dictlist = dict([ (k, list(range(num))) for k in keys ])
+        keys.sort()
+        ans = []
+        for k in keys:
+            ans += [k] + [ '%s[%d]' % (k, i) for i in range(num) ]
+        self.assertEqual(ans,
+                         stats_httpd.item_name_list(dictlist, ''))
+
+class TestHttpHandler(unittest.TestCase):
+    """Tests for HttpHandler class"""
+    def setUp(self):
+        # set the signal handler for deadlock
+        self.sig_handler = SignalHandler(self.fail)
+        DUMMY_DATA['Stats']['lname'] = 'test-lname'
+        (self.address, self.port) = get_availaddr()
+        self.stats_httpd_server = ThreadingServerManager(MyStatsHttpd,
+                                                         (self.address,
+                                                          self.port))
+        self.stats_httpd = self.stats_httpd_server.server
+        self.stats_httpd_server.run()
+        self.client = http.client.HTTPConnection(self.address, self.port)
+        self.client._http_vsn_str = 'HTTP/1.0\n'
+        self.client.connect()
+
+    def tearDown(self):
+        self.client.close()
+        # reset the signal handler
+        self.sig_handler.reset()
+
+    @unittest.skipUnless(xml_parser, "skipping the test using XMLParser")
+    def test_do_GET(self):
+        self.assertTrue(type(self.stats_httpd.httpd) is list)
+        self.assertEqual(len(self.stats_httpd.httpd), 1)
+        self.assertEqual((self.address, self.port), self.stats_httpd.http_addrs[0])
+
+        def check_XML_URL_PATH(path=''):
+            url_path = '%s/%s' % (stats_httpd.XML_URL_PATH, path)
+            url_path = urllib.parse.quote(url_path)
+            self.client.putrequest('GET', url_path)
+            self.client.endheaders()
+            response = self.client.getresponse()
+            self.assertEqual(response.getheader("Content-type"), "text/xml")
+            self.assertGreater(int(response.getheader("Content-Length")), 0)
+            self.assertEqual(response.status, 200)
+            xml_doctype = response.readline().decode()
+            xsl_doctype = response.readline().decode()
+            self.assertGreater(len(xml_doctype), 0)
+            self.assertGreater(len(xsl_doctype), 0)
+            root = xml.etree.ElementTree.parse(response).getroot()
+            self.assertGreater(root.tag.find('statistics'), 0)
+            schema_loc = '{%s}schemaLocation' % XMLNS_XSI
+            # check the path of XSD
+            self.assertEqual(root.attrib[schema_loc],
+                             stats_httpd.XSD_NAMESPACE + ' '
+                             + stats_httpd.XSD_URL_PATH)
+            # check the path of XSL
+            self.assertTrue(xsl_doctype.startswith(
+                    '<?xml-stylesheet type="text/xsl" href="' +
+                    stats_httpd.XSL_URL_PATH
+                    + '"?>'))
+            # check whether the list of 'identifier' attributes in
+            # root is same as the list of item names in DUMMY_DATA
+            id_list = [ elm.attrib['identifier'] for elm in root ]
+            item_list = [ it for it in \
+                              stats_httpd.item_name_list(DUMMY_DATA, path) \
+                              if len(it.split('/')) > 1 ]
+            self.assertEqual(id_list, item_list)
+            for elem in root:
+                attr = elem.attrib
+                value = isc.cc.data.find(DUMMY_DATA, attr['identifier'])
+                # No 'value' attribute should be found in the 'item'
+                # element when datatype of the value is list or dict.
+                if type(value) is list or type(value) is dict:
+                    self.assertFalse('value' in attr)
+                # The value of the 'value' attribute should be checked
+                # after casting it to string type if datatype of the
+                # value is int or float. Because attr['value'] returns
+                # string type even if its value is int or float.
+                elif type(value) is int or type(value) is float:
+                    self.assertEqual(attr['value'], str(value))
+                else:
+                    self.assertEqual(attr['value'], value)
+
+        # URL is '/bind10/statistics/xml'
+        check_XML_URL_PATH()
+        for path in stats_httpd.item_name_list(DUMMY_DATA, ''):
+            check_XML_URL_PATH(path)
+
+        def check_XSD_URL_PATH():
+            url_path = stats_httpd.XSD_URL_PATH
+            url_path = urllib.parse.quote(url_path)
+            self.client.putrequest('GET', url_path)
+            self.client.endheaders()
+            response = self.client.getresponse()
+            self.assertEqual(response.getheader("Content-type"), "text/xml")
+            self.assertGreater(int(response.getheader("Content-Length")), 0)
+            self.assertEqual(response.status, 200)
+            root = xml.etree.ElementTree.parse(response).getroot()
+            url_xmlschema = '{%s}' % XMLNS_XSD
+            self.assertGreater(root.tag.find('schema'), 0)
+            self.assertTrue(hasattr(root, 'attrib'))
+            self.assertTrue('targetNamespace' in root.attrib)
+            self.assertEqual(root.attrib['targetNamespace'],
+                             stats_httpd.XSD_NAMESPACE)
+
+        # URL is '/bind10/statistics/xsd'
+        check_XSD_URL_PATH()
+
+        def check_XSL_URL_PATH():
+            url_path = stats_httpd.XSL_URL_PATH
+            url_path = urllib.parse.quote(url_path)
+            self.client.putrequest('GET', url_path)
+            self.client.endheaders()
+            response = self.client.getresponse()
+            self.assertEqual(response.getheader("Content-type"), "text/xml")
+            self.assertGreater(int(response.getheader("Content-Length")), 0)
+            self.assertEqual(response.status, 200)
+            root = xml.etree.ElementTree.parse(response).getroot()
+            url_trans = '{%s}' % XMLNS_XSL
+            url_xhtml = '{%s}' % XMLNS_XHTML
+            self.assertEqual(root.tag, url_trans + 'stylesheet')
+
+        # URL is '/bind10/statistics/xsl'
+        check_XSL_URL_PATH()
+
+        # 302 redirect
+        self.client._http_vsn_str = 'HTTP/1.1'
+        self.client.putrequest('GET', '/')
+        self.client.putheader('Host', self.address)
+        self.client.endheaders()
+        response = self.client.getresponse()
+        self.assertEqual(response.status, 302)
+        self.assertEqual(response.getheader('Location'),
+                         "http://%s:%d%s/" % (self.address, self.port, stats_httpd.XML_URL_PATH))
+
+        # 404 NotFound (random path)
+        self.client._http_vsn_str = 'HTTP/1.0'
+        self.client.putrequest('GET', '/path/to/foo/bar')
+        self.client.endheaders()
+        response = self.client.getresponse()
+        self.assertEqual(response.status, 404)
+        self.client._http_vsn_str = 'HTTP/1.0'
+        self.client.putrequest('GET', '/bind10/foo')
+        self.client.endheaders()
+        response = self.client.getresponse()
+        self.assertEqual(response.status, 404)
+        self.client._http_vsn_str = 'HTTP/1.0'
+        self.client.putrequest('GET', '/bind10/statistics/foo')
+        self.client.endheaders()
+        response = self.client.getresponse()
+        self.assertEqual(response.status, 404)
+        self.client._http_vsn_str = 'HTTP/1.0'
+        self.client.putrequest('GET', stats_httpd.XML_URL_PATH + 'Auth') # with no slash
+        self.client.endheaders()
+        response = self.client.getresponse()
+        self.assertEqual(response.status, 404)
+
+        # 200 ok
+        self.client._http_vsn_str = 'HTTP/1.0'
+        self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/')
+        self.client.endheaders()
+        response = self.client.getresponse()
+        self.assertEqual(response.status, 200)
+        self.client._http_vsn_str = 'HTTP/1.0'
+        self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/#foo')
+        self.client.endheaders()
+        response = self.client.getresponse()
+        self.assertEqual(response.status, 200)
+        self.client._http_vsn_str = 'HTTP/1.0'
+        self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/?foo=bar')
+        self.client.endheaders()
+        response = self.client.getresponse()
+        self.assertEqual(response.status, 200)
+
+        # 404 NotFound (too long path)
+        self.client._http_vsn_str = 'HTTP/1.0'
+        self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/Init/boot_time/a')
+        self.client.endheaders()
+        response = self.client.getresponse()
+        self.assertEqual(response.status, 404)
+
+        # 404 NotFound (nonexistent module name)
+        self.client._http_vsn_str = 'HTTP/1.0'
+        self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/Foo')
+        self.client.endheaders()
+        response = self.client.getresponse()
+        self.assertEqual(response.status, 404)
+        self.client._http_vsn_str = 'HTTP/1.0'
+        self.client.putrequest('GET', stats_httpd.XSD_URL_PATH + '/Foo')
+        self.client.endheaders()
+        response = self.client.getresponse()
+        self.assertEqual(response.status, 404)
+        self.client._http_vsn_str = 'HTTP/1.0'
+        self.client.putrequest('GET', stats_httpd.XSL_URL_PATH + '/Foo')
+        self.client.endheaders()
+        response = self.client.getresponse()
+        self.assertEqual(response.status, 404)
+
+        # 404 NotFound (nonexistent item name)
+        self.client._http_vsn_str = 'HTTP/1.0'
+        self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/Foo/bar')
+        self.client.endheaders()
+        response = self.client.getresponse()
+        self.assertEqual(response.status, 404)
+        self.client._http_vsn_str = 'HTTP/1.0'
+        self.client.putrequest('GET', stats_httpd.XSD_URL_PATH + '/Foo/bar')
+        self.client.endheaders()
+        response = self.client.getresponse()
+        self.assertEqual(response.status, 404)
+        self.client._http_vsn_str = 'HTTP/1.0'
+        self.client.putrequest('GET', stats_httpd.XSL_URL_PATH + '/Foo/bar')
+        self.client.endheaders()
+        response = self.client.getresponse()
+        self.assertEqual(response.status, 404)
+
+        # 404 NotFound (existent module but nonexistent item name)
+        self.client._http_vsn_str = 'HTTP/1.0'
+        self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/Auth/bar')
+        self.client.endheaders()
+        response = self.client.getresponse()
+        self.assertEqual(response.status, 404)
+        self.client._http_vsn_str = 'HTTP/1.0'
+        self.client.putrequest('GET', stats_httpd.XSD_URL_PATH + '/Auth/bar')
+        self.client.endheaders()
+        response = self.client.getresponse()
+        self.assertEqual(response.status, 404)
+        self.client._http_vsn_str = 'HTTP/1.0'
+        self.client.putrequest('GET', stats_httpd.XSL_URL_PATH + '/Auth/bar')
+        self.client.endheaders()
+        response = self.client.getresponse()
+        self.assertEqual(response.status, 404)
+
+    def test_do_GET_failed1(self):
+        # failure case (Stats is down, so rpc_call() results in an exception)
+        # Note: this should eventually be RPCRecipientMissing.
+        self.stats_httpd._rpc_answers.append(
+            isc.cc.session.SessionTimeout('timeout'))
+
+        # request XML
+        self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/')
+        self.client.endheaders()
+        response = self.client.getresponse()
+        self.assertEqual(response.status, 500)
+
+        # request XSD
+        self.client.putrequest('GET', stats_httpd.XSD_URL_PATH)
+        self.client.endheaders()
+        response = self.client.getresponse()
+        self.assertEqual(response.status, 200)
+
+        # request XSL
+        self.client.putrequest('GET', stats_httpd.XSL_URL_PATH)
+        self.client.endheaders()
+        response = self.client.getresponse()
+        self.assertEqual(response.status, 200)
+
+    def test_do_GET_failed2(self):
+        # failure case(Stats replies an error)
+        self.stats_httpd._rpc_answers.append(
+            RPCError(1, "specified arguments are incorrect: I have an error."))
+
+        # request XML
+        self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/')
+        self.client.endheaders()
+        response = self.client.getresponse()
+        self.assertEqual(response.status, 404)
+
+        # request XSD
+        self.stats_httpd._rpc_answers.append(
+            RPCError(1, "specified arguments are incorrect: I have an error."))
+        self.client.putrequest('GET', stats_httpd.XSD_URL_PATH)
+        self.client.endheaders()
+        response = self.client.getresponse()
+        self.assertEqual(response.status, 200)
+
+        # request XSL
+        self.stats_httpd._rpc_answers.append(
+            RPCError(1, "specified arguments are incorrect: I have an error."))
+        self.client.putrequest('GET', stats_httpd.XSL_URL_PATH)
+        self.client.endheaders()
+        response = self.client.getresponse()
+        self.assertEqual(response.status, 200)
+
+    def test_do_HEAD(self):
+        self.client.putrequest('HEAD', stats_httpd.XML_URL_PATH + '/')
+        self.client.endheaders()
+        response = self.client.getresponse()
+        self.assertEqual(response.status, 200)
+
+        self.client.putrequest('HEAD', '/path/to/foo/bar')
+        self.client.endheaders()
+        response = self.client.getresponse()
+        self.assertEqual(response.status, 404)
+
+    @unittest.skipUnless(lxml_etree, "skipping XML validation with XSD")
+    def test_xml_validation_with_xsd(self):
+        """Tests for XML validation with XSD. If lxml is not
+        installed, this tests would be skipped."""
+        def request_xsd():
+            url_path = stats_httpd.XSD_URL_PATH
+            url_path = urllib.parse.quote(url_path)
+            self.client.putrequest('GET', url_path)
+            self.client.endheaders()
+            xsd_doc = self.client.getresponse()
+            xsd_doc = lxml_etree.parse(xsd_doc)
+            return lxml_etree.XMLSchema(xsd_doc)
+
+        def request_xmldoc(path=''):
+            url_path = '%s/%s' % (stats_httpd.XML_URL_PATH, path)
+            url_path = urllib.parse.quote(url_path)
+            self.client.putrequest('GET', url_path)
+            self.client.endheaders()
+            xml_doc = self.client.getresponse()
+            return lxml_etree.parse(xml_doc)
+
+        # request XSD and XML
+        xsd = request_xsd()
+        xml_doc = request_xmldoc()
+        # do validation
+        self.assertTrue(xsd.validate(xml_doc))
+
+        # validate each paths in DUMMY_DATA
+        for path in stats_httpd.item_name_list(DUMMY_DATA, ''):
+            # request XML
+            xml_doc = request_xmldoc(path)
+            # do validation
+            self.assertTrue(xsd.validate(xml_doc))
+
+class TestHttpServerError(unittest.TestCase):
+    """Tests for HttpServerError exception"""
+    def test_raises(self):
+        try:
+            raise stats_httpd.HttpServerError('Nothing')
+        except stats_httpd.HttpServerError as err:
+            self.assertEqual(str(err), 'Nothing')
+
+class TestHttpServer(unittest.TestCase):
+    """Tests for HttpServer class"""
+    def setUp(self):
+        # set the signal handler for deadlock
+        self.sig_handler = SignalHandler(self.fail)
+
+    def tearDown(self):
+        if hasattr(self, "stats_httpd"):
+            self.stats_httpd.stop()
+        # reset the signal handler
+        self.sig_handler.reset()
+
+    def test_httpserver(self):
+        self.stats_httpd = MyStatsHttpd(get_availaddr())
+        self.assertEqual(type(self.stats_httpd.httpd), list)
+        self.assertEqual(len(self.stats_httpd.httpd), 1)
+        for httpd in self.stats_httpd.httpd:
+            self.assertTrue(isinstance(httpd, stats_httpd.HttpServer))
+
+class TestStatsHttpdError(unittest.TestCase):
+    """Tests for StatsHttpdError exception"""
+
+    def test_raises1(self):
+        try:
+            raise stats_httpd.StatsHttpdError('Nothing')
+        except stats_httpd.StatsHttpdError as err:
+            self.assertEqual(str(err), 'Nothing')
+
+    def test_raises2(self):
+        try:
+            raise stats_httpd.StatsHttpdDataError('Nothing')
+        except stats_httpd.StatsHttpdDataError as err:
+            self.assertEqual(str(err), 'Nothing')
+
+class TestStatsHttpd(unittest.TestCase):
+    """Tests for StatsHttpd class"""
+
+    def setUp(self):
+        # set the signal handler for deadlock
+        self.sig_handler = SignalHandler(self.fail)
+        # checking IPv6 enabled on this platform
+        self.ipv6_enabled = is_ipv6_enabled()
+        # instantiation of StatsHttpd indirectly calls gethostbyaddr(), which
+        # can block for an uncontrollable period, leading many undesirable
+        # results.  We should rather eliminate the reliance, but until we
+        # can make such fundamental cleanup we replace it with a faked method;
+        # in our test scenario the return value doesn't matter.
+        self.__gethostbyaddr_orig = socket.gethostbyaddr
+        socket.gethostbyaddr = lambda x: ('test.example.', [], None)
+
+        # Some tests replace this library function.  Keep the original for
+        # restor
+        self.__orig_select_select = select.select
+
+    def tearDown(self):
+        socket.gethostbyaddr = self.__gethostbyaddr_orig
+        if hasattr(self, "stats_httpd"):
+            self.stats_httpd.stop()
+        # reset the signal handler
+        self.sig_handler.reset()
+
+        # restore original of replaced library
+        select.select = self.__orig_select_select
+
+    def test_init(self):
+        server_address = get_availaddr()
+        self.stats_httpd = MyStatsHttpd(server_address)
+        self.assertEqual(self.stats_httpd.running, False)
+        self.assertEqual(self.stats_httpd.poll_intval, 0.5)
+        self.assertNotEqual(len(self.stats_httpd.httpd), 0)
+        self.assertIsNotNone(self.stats_httpd.mccs)
+        self.assertIsNotNone(self.stats_httpd.cc_session)
+        # The real CfgMgr would return 'version', but our test mock omits it,
+        # so the len(config) should be 1
+        self.assertEqual(len(self.stats_httpd.config), 1)
+        self.assertTrue('listen_on' in self.stats_httpd.config)
+        self.assertEqual(len(self.stats_httpd.config['listen_on']), 1)
+        self.assertTrue('address' in self.stats_httpd.config['listen_on'][0])
+        self.assertTrue('port' in self.stats_httpd.config['listen_on'][0])
+        self.assertTrue(server_address in set(self.stats_httpd.http_addrs))
+        self.assertEqual('StatsHttpd', self.stats_httpd.mccs.\
+                             get_module_spec().get_module_name())
+
+    def test_init_hterr(self):
+        """Test the behavior of StatsHttpd constructor when open_httpd fails.
+
+        We specifically check the following two:
+        - close_mccs() is called (so stats-httpd tells ConfigMgr it's shutting
+          down)
+        - the constructor results in HttpServerError exception.
+
+        """
+        self.__mccs_closed = False
+        def call_checker():
+            self.__mccs_closed = True
+        class FailingStatsHttpd(MyStatsHttpd):
+            def open_httpd(self):
+                raise stats_httpd.HttpServerError
+            def close_mccs(self):
+                call_checker()
+        self.assertRaises(stats_httpd.HttpServerError, FailingStatsHttpd)
+        self.assertTrue(self.__mccs_closed)
+
+    def test_openclose_mccs(self):
+        self.stats_httpd = MyStatsHttpd(get_availaddr())
+        mccs = self.stats_httpd.mccs
+        self.assertFalse(self.stats_httpd.mccs.stopped)
+        self.assertFalse(self.stats_httpd.mccs.closed)
+        self.stats_httpd.close_mccs()
+        self.assertTrue(mccs.stopped)
+        self.assertTrue(mccs.closed)
+        self.assertIsNone(self.stats_httpd.mccs)
+        self.stats_httpd.open_mccs()
+        self.assertIsNotNone(self.stats_httpd.mccs)
+        self.stats_httpd.mccs = None
+        self.assertIsNone(self.stats_httpd.mccs)
+        self.assertIsNone(self.stats_httpd.close_mccs())
+
+    def test_mccs(self):
+        self.stats_httpd = MyStatsHttpd(get_availaddr())
+        self.assertIsNotNone(self.stats_httpd.mccs.get_socket())
+        self.assertTrue(
+            isinstance(self.stats_httpd.mccs.get_socket(), socket.socket))
+        self.assertIsNotNone(self.stats_httpd.cc_session)
+        statistics_spec = self.stats_httpd.get_stats_spec()
+        for mod in DUMMY_DATA:
+            self.assertTrue(mod in statistics_spec)
+            for cfg in statistics_spec[mod]:
+                self.assertTrue('item_name' in cfg)
+                self.assertTrue(cfg['item_name'] in DUMMY_DATA[mod])
+            self.assertTrue(len(statistics_spec[mod]), len(DUMMY_DATA[mod]))
+        self.stats_httpd.close_mccs()
+        self.assertIsNone(self.stats_httpd.mccs)
+
+    def test_httpd(self):
+        # dual stack (addresses is ipv4 and ipv6)
+        if self.ipv6_enabled:
+            server_addresses = (get_availaddr('::1'), get_availaddr())
+            self.stats_httpd = MyStatsHttpd(*server_addresses)
+            for ht in self.stats_httpd.httpd:
+                self.assertTrue(isinstance(ht, stats_httpd.HttpServer))
+                self.assertTrue(ht.address_family in set([socket.AF_INET,
+                                                          socket.AF_INET6]))
+                self.assertTrue(isinstance(ht.socket, socket.socket))
+                ht.socket.close() # to silence warning about resource leak
+            self.stats_httpd.close_mccs() # ditto
+
+        # dual stack (address is ipv6)
+        if self.ipv6_enabled:
+            server_addresses = get_availaddr('::1')
+            self.stats_httpd = MyStatsHttpd(server_addresses)
+            for ht in self.stats_httpd.httpd:
+                self.assertTrue(isinstance(ht, stats_httpd.HttpServer))
+                self.assertEqual(ht.address_family, socket.AF_INET6)
+                self.assertTrue(isinstance(ht.socket, socket.socket))
+                ht.socket.close()
+            self.stats_httpd.close_mccs() # ditto
+
+        # dual/single stack (address is ipv4)
+        server_addresses = get_availaddr()
+        self.stats_httpd = MyStatsHttpd(server_addresses)
+        for ht in self.stats_httpd.httpd:
+            self.assertTrue(isinstance(ht, stats_httpd.HttpServer))
+            self.assertEqual(ht.address_family, socket.AF_INET)
+            self.assertTrue(isinstance(ht.socket, socket.socket))
+            ht.socket.close()
+        self.stats_httpd.close_mccs()
+
+    def test_httpd_anyIPv4(self):
+        # any address (IPv4)
+        server_addresses = get_availaddr(address='0.0.0.0')
+        self.stats_httpd = MyStatsHttpd(server_addresses)
+        for ht in self.stats_httpd.httpd:
+            self.assertTrue(isinstance(ht, stats_httpd.HttpServer))
+            self.assertEqual(ht.address_family,socket.AF_INET)
+            self.assertTrue(isinstance(ht.socket, socket.socket))
+
+    def test_httpd_anyIPv6(self):
+        # any address (IPv6)
+        if self.ipv6_enabled:
+            server_addresses = get_availaddr(address='::')
+            self.stats_httpd = MyStatsHttpd(server_addresses)
+            for ht in self.stats_httpd.httpd:
+                self.assertTrue(isinstance(ht, stats_httpd.HttpServer))
+                self.assertEqual(ht.address_family,socket.AF_INET6)
+                self.assertTrue(isinstance(ht.socket, socket.socket))
+
+    def test_httpd_failed(self):
+        # existent hostname
+        self.assertRaises(stats_httpd.HttpServerError, MyStatsHttpd,
+                          get_availaddr(address='localhost'))
+
+        # nonexistent hostname
+        self.assertRaises(stats_httpd.HttpServerError, MyStatsHttpd,
+                          ('my.host.domain', 8000))
+
+        # over flow of port number
+        self.assertRaises(stats_httpd.HttpServerError, MyStatsHttpd,
+                          ('127.0.0.1', 80000))
+
+        # negative
+        self.assertRaises(stats_httpd.HttpServerError, MyStatsHttpd,
+                          ('127.0.0.1', -8000))
+
+        # alphabet
+        self.assertRaises(stats_httpd.HttpServerError, MyStatsHttpd,
+                          ('127.0.0.1', 'ABCDE'))
+
+        # Address already in use
+        server_addresses = get_availaddr()
+        server = MyStatsHttpd(server_addresses)
+        self.assertRaises(stats_httpd.HttpServerError, MyStatsHttpd,
+                          server_addresses)
+
+    def __faked_select(self, ex=None):
+        """A helper subroutine for tests using faked select.select.
+
+        See test_running() for basic features.  If ex is not None,
+        it's assumed to be an exception object and will be raised on the
+        first call.
+
+        """
+        self.assertTrue(self.stats_httpd.running)
+        self.__call_count += 1
+        if ex is not None and self.__call_count == 1:
+            raise ex
+        if self.__call_count == 2:
+            self.stats_httpd.running  = False
+        assert self.__call_count <= 2 # safety net to avoid infinite loop
+        return ([], [], [])
+
+    def test_running(self):
+        # Previous version of this test checks the result of "status" and
+        # "shutdown" commands; however, they are more explicitly tested
+        # in specific tests.  In this test we only have to check:
+        # - start() will set 'running' to True
+        # - as long as 'running' is True, it keeps calling select.select
+        # - when running becomes False, it exists from the loop and calls
+        #   stop()
+        self.stats_httpd = MyStatsHttpd(get_availaddr())
+        self.assertFalse(self.stats_httpd.running)
+
+        # In this test we'll call select.select() 2 times: on the first call
+        # stats_httpd.running should be True; on the second call the faked
+        # select() will set it to False.
+        self.__call_count = 0
+        select.select = lambda r, w, x, t: self.__faked_select()
+        self.stats_httpd.start()
+        self.assertFalse(self.stats_httpd.running)
+        self.assertIsNone(self.stats_httpd.mccs) # stop() clears .mccs
+
+    def test_running_fail(self):
+        # A failure case of start(): we close the (real but dummy) socket for
+        # the CC session.  This breaks the select-loop due to exception
+        self.stats_httpd = MyStatsHttpd(get_availaddr())
+        self.stats_httpd.mccs.get_socket().close()
+        self.assertRaises(ValueError, self.stats_httpd.start)
+
+    def test_failure_with_a_select_error (self):
+        """checks select.error is raised if the exception except
+        errno.EINTR is raised while it's selecting"""
+        def raise_select_except(*args):
+            raise select.error('dummy error')
+        select.select = raise_select_except
+        self.stats_httpd = MyStatsHttpd(get_availaddr())
+        self.assertRaises(select.error, self.stats_httpd.start)
+
+    def test_nofailure_with_errno_EINTR(self):
+        """checks no exception is raised if errno.EINTR is raised
+        while it's selecting"""
+        self.__call_count = 0
+        select.select = lambda r, w, x, t: self.__faked_select(
+            select.error(errno.EINTR))
+        self.stats_httpd = MyStatsHttpd(get_availaddr())
+        self.stats_httpd.start() # shouldn't leak the exception
+        self.assertFalse(self.stats_httpd.running)
+        self.assertIsNone(self.stats_httpd.mccs)
+
+    def test_open_template(self):
+        self.stats_httpd = MyStatsHttpd(get_availaddr())
+        # successful conditions
+        tmpl = self.stats_httpd.open_template(
+            stats_httpd.XML_TEMPLATE_LOCATION)
+        self.assertTrue(isinstance(tmpl, string.Template))
+        opts = dict(
+            xml_string="<dummy></dummy>",
+            xsl_url_path="/path/to/")
+        lines = tmpl.substitute(opts)
+        for n in opts:
+            self.assertGreater(lines.find(opts[n]), 0)
+        tmpl = self.stats_httpd.open_template(
+            stats_httpd.XSD_TEMPLATE_LOCATION)
+        self.assertTrue(isinstance(tmpl, string.Template))
+        opts = dict(xsd_namespace="http://host/path/to/")
+        lines = tmpl.substitute(opts)
+        for n in opts:
+            self.assertGreater(lines.find(opts[n]), 0)
+        tmpl = self.stats_httpd.open_template(
+            stats_httpd.XSL_TEMPLATE_LOCATION)
+        self.assertTrue(isinstance(tmpl, string.Template))
+        opts = dict(xsd_namespace="http://host/path/to/")
+        lines = tmpl.substitute(opts)
+        for n in opts:
+            self.assertGreater(lines.find(opts[n]), 0)
+        # unsuccessful condition
+        self.assertRaises(
+            stats_httpd.StatsHttpdDataError,
+            self.stats_httpd.open_template, '/path/to/foo/bar')
+
+    def test_commands(self):
+        self.stats_httpd = MyStatsHttpd(get_availaddr())
+        self.assertEqual(self.stats_httpd.command_handler("status", None),
+                         isc.config.ccsession.create_answer(
+                0, "Stats Httpd is up. (PID " + str(os.getpid()) + ")"))
+        self.stats_httpd.running = True
+        self.assertEqual(self.stats_httpd.command_handler("shutdown", None),
+                         isc.config.ccsession.create_answer(0))
+        self.assertFalse(self.stats_httpd.running)
+        self.assertEqual(
+            self.stats_httpd.command_handler("__UNKNOWN_COMMAND__", None),
+            isc.config.ccsession.create_answer(
+                1, "Unknown command: __UNKNOWN_COMMAND__"))
+
+    def test_config(self):
+        self.stats_httpd = MyStatsHttpd(get_availaddr())
+        self.assertEqual(
+            self.stats_httpd.config_handler(dict(_UNKNOWN_KEY_=None)),
+            isc.config.ccsession.create_answer(
+                1, "unknown item _UNKNOWN_KEY_"))
+
+        addresses = get_availaddr()
+        self.assertEqual(
+            self.stats_httpd.config_handler(
+                dict(listen_on=[dict(address=addresses[0],port=addresses[1])])),
+            isc.config.ccsession.create_answer(0))
+        self.assertTrue("listen_on" in self.stats_httpd.config)
+        for addr in self.stats_httpd.config["listen_on"]:
+            self.assertTrue("address" in addr)
+            self.assertTrue("port" in addr)
+            self.assertTrue(addr["address"] == addresses[0])
+            self.assertTrue(addr["port"] == addresses[1])
+
+        if self.ipv6_enabled:
+            addresses = get_availaddr("::1")
+            self.assertEqual(
+                self.stats_httpd.config_handler(
+                dict(listen_on=[dict(address=addresses[0],port=addresses[1])])),
+                isc.config.ccsession.create_answer(0))
+            self.assertTrue("listen_on" in self.stats_httpd.config)
+            for addr in self.stats_httpd.config["listen_on"]:
+                self.assertTrue("address" in addr)
+                self.assertTrue("port" in addr)
+                self.assertTrue(addr["address"] == addresses[0])
+                self.assertTrue(addr["port"] == addresses[1])
+
+        addresses = get_availaddr()
+        self.assertEqual(
+            self.stats_httpd.config_handler(
+                dict(listen_on=[dict(address=addresses[0],port=addresses[1])])),
+            isc.config.ccsession.create_answer(0))
+        self.assertTrue("listen_on" in self.stats_httpd.config)
+        for addr in self.stats_httpd.config["listen_on"]:
+            self.assertTrue("address" in addr)
+            self.assertTrue("port" in addr)
+            self.assertTrue(addr["address"] == addresses[0])
+            self.assertTrue(addr["port"] == addresses[1])
+        (ret, arg) = isc.config.ccsession.parse_answer(
+            self.stats_httpd.config_handler(
+                dict(listen_on=[dict(address="1.2.3.4",port=543210)]))
+            )
+        self.assertEqual(ret, 1)
+
+    @unittest.skipUnless(xml_parser, "skipping the test using XMLParser")
+    def test_xml_handler(self):
+        self.stats_httpd = MyStatsHttpd(get_availaddr())
+        module_name = 'Dummy'
+        stats_spec = \
+            { module_name :
+                  [{
+                        "item_name": "foo",
+                        "item_type": "string",
+                        "item_optional": False,
+                        "item_default": "bar",
+                        "item_description": "foo is bar",
+                        "item_title": "Foo"
+                        },
+                   {
+                        "item_name": "foo2",
+                        "item_type": "list",
+                        "item_optional": False,
+                        "item_default": [
+                            {
+                                "zonename" : "test1",
+                                "queries.udp" : 1,
+                                "queries.tcp" : 2
+                                },
+                            {
+                                "zonename" : "test2",
+                                "queries.udp" : 3,
+                                "queries.tcp" : 4
+                                }
+                        ],
+                        "item_title": "Foo bar",
+                        "item_description": "Foo bar",
+                        "list_item_spec": {
+                            "item_name": "foo2-1",
+                            "item_type": "map",
+                            "item_optional": False,
+                            "item_default": {},
+                            "map_item_spec": [
+                                {
+                                    "item_name": "foo2-1-1",
+                                    "item_type": "string",
+                                    "item_optional": False,
+                                    "item_default": "",
+                                    "item_title": "Foo2 1 1",
+                                    "item_description": "Foo bar"
+                                    },
+                                {
+                                    "item_name": "foo2-1-2",
+                                    "item_type": "integer",
+                                    "item_optional": False,
+                                    "item_default": 0,
+                                    "item_title": "Foo2 1 2",
+                                    "item_description": "Foo bar"
+                                    },
+                                {
+                                    "item_name": "foo2-1-3",
+                                    "item_type": "integer",
+                                    "item_optional": False,
+                                    "item_default": 0,
+                                    "item_title": "Foo2 1 3",
+                                    "item_description": "Foo bar"
+                                    }
+                                ]
+                            }
+                        }]
+              }
+        stats_data = \
+            { module_name : { 'foo':'bar',
+                          'foo2': [
+                            {
+                                "foo2-1-1" : "bar1",
+                                "foo2-1-2" : 10,
+                                "foo2-1-3" : 9
+                                },
+                            {
+                                "foo2-1-1" : "bar2",
+                                "foo2-1-2" : 8,
+                                "foo2-1-3" : 7
+                                }
+                            ] } }
+        self.stats_httpd.get_stats_spec = lambda x,y: stats_spec
+        self.stats_httpd.get_stats_data = lambda x,y: stats_data
+        xml_string = self.stats_httpd.xml_handler()
+        stats_xml = xml.etree.ElementTree.fromstring(xml_string)
+        schema_loc = '{%s}schemaLocation' % XMLNS_XSI
+        self.assertEqual(stats_xml.attrib[schema_loc],
+                         stats_httpd.XML_ROOT_ATTRIB['xsi:schemaLocation'])
+        stats_data = stats_data[module_name]
+        stats_spec = stats_spec[module_name]
+        names = stats_httpd.item_name_list(stats_data, '')
+        for i in range(0, len(names)):
+            self.assertEqual('%s/%s' % (module_name, names[i]), stats_xml[i].attrib['identifier'])
+            value = isc.cc.data.find(stats_data, names[i])
+            if type(value) is int:
+                value = str(value)
+            if type(value) is dict or type(value) is list:
+                self.assertFalse('value' in stats_xml[i].attrib)
+            else:
+                self.assertEqual(value, stats_xml[i].attrib['value'])
+            self.assertEqual(module_name, stats_xml[i].attrib['owner'])
+            self.assertEqual(urllib.parse.quote('%s/%s/%s' % (stats_httpd.XML_URL_PATH,
+                                                              module_name, names[i])),
+                             stats_xml[i].attrib['uri'])
+            spec = isc.config.find_spec_part(stats_spec, names[i])
+            self.assertEqual(spec['item_name'], stats_xml[i].attrib['name'])
+            self.assertEqual(spec['item_type'], stats_xml[i].attrib['type'])
+            self.assertEqual(spec['item_description'], stats_xml[i].attrib['description'])
+            self.assertEqual(spec['item_title'], stats_xml[i].attrib['title'])
+            self.assertEqual(str(spec['item_optional']).lower(), stats_xml[i].attrib['optional'])
+            default = spec['item_default']
+            if type(default) is int:
+                default = str(default)
+            if type(default) is dict or type(default) is list:
+                self.assertFalse('default' in stats_xml[i].attrib)
+            else:
+                self.assertEqual(default, stats_xml[i].attrib['default'])
+            self.assertFalse('item_format' in spec)
+            self.assertFalse('format' in stats_xml[i].attrib)
+
+    @unittest.skipUnless(xml_parser, "skipping the test using XMLParser")
+    def test_xsd_handler(self):
+        self.stats_httpd = MyStatsHttpd(get_availaddr())
+        xsd_string = self.stats_httpd.xsd_handler()
+        stats_xsd = xml.etree.ElementTree.fromstring(xsd_string)
+        ns = '{%s}' % XMLNS_XSD
+        stats_xsd = stats_xsd[1].find('%scomplexType/%ssequence/%selement' % ((ns,)*3))
+        self.assertEqual('item', stats_xsd.attrib['name'])
+        stats_xsd = stats_xsd.find('%scomplexType' % ns)
+        type_types = ('boolean', 'integer', 'real', 'string', 'map', \
+                          'list', 'named_set', 'any')
+        attribs = [('identifier', 'string', 'required'),
+                   ('value', 'string', 'optional'),
+                   ('owner', 'string', 'required'),
+                   ('uri', 'anyURI', 'required'),
+                   ('name', 'string', 'required'),
+                   ('type', type_types, 'required'),
+                   ('description', 'string', 'optional'),
+                   ('title', 'string', 'optional'),
+                   ('optional', 'boolean', 'optional'),
+                   ('default', 'string', 'optional'),
+                   ('format', 'string', 'optional')]
+        for i in range(0, len(attribs)):
+            self.assertEqual(attribs[i][0], stats_xsd[i].attrib['name'])
+            if attribs[i][0] == 'type':
+                stats_xsd_types = \
+                    stats_xsd[i].find('%ssimpleType/%srestriction' % \
+                                          ((ns,)*2))
+                for j in range(0, len(attribs[i][1])):
+                    self.assertEqual(attribs[i][1][j], \
+                                         stats_xsd_types[j].attrib['value'])
+            else:
+                self.assertEqual(attribs[i][1], stats_xsd[i].attrib['type'])
+            self.assertEqual(attribs[i][2], stats_xsd[i].attrib['use'])
+
+    @unittest.skipUnless(xml_parser, "skipping the test using XMLParser")
+    def test_xsl_handler(self):
+        self.stats_httpd = MyStatsHttpd(get_availaddr())
+        xsl_string = self.stats_httpd.xsl_handler()
+        stats_xsl = xml.etree.ElementTree.fromstring(xsl_string)
+        nst = '{%s}' % XMLNS_XSL
+        nsx = '{%s}' % XMLNS_XHTML
+        self.assertEqual("bind10:statistics", stats_xsl[2].attrib['match'])
+        stats_xsl = stats_xsl[2].find('%stable' % nsx)
+        self.assertEqual('item', stats_xsl[1].attrib['select'])
+        stats_xsl = stats_xsl[1].find('%str' % nsx)
+        self.assertEqual('@uri', stats_xsl[0].find(
+                '%selement/%sattribute/%svalue-of' % ((nst,)*3)).attrib['select'])
+        self.assertEqual('@identifier', stats_xsl[0].find(
+                '%selement/%svalue-of' % ((nst,)*2)).attrib['select'])
+        self.assertEqual('@value', stats_xsl[1].find('%sif' % nst).attrib['test'])
+        self.assertEqual('@value', stats_xsl[1].find('%sif/%svalue-of' % ((nst,)*2)).attrib['select'])
+        self.assertEqual('@description', stats_xsl[2].find('%sif' % nst).attrib['test'])
+        self.assertEqual('@description', stats_xsl[2].find('%sif/%svalue-of' % ((nst,)*2)).attrib['select'])
+
+class Z_TestOSEnv(unittest.TestCase):
+    def test_for_without_B10_FROM_SOURCE(self):
+        # Note: this test is sensitive due to its substantial side effect of
+        # reloading.  For exmaple, it affects tests that tweak module
+        # attributes (such as test_init_hterr).  It also breaks logging
+        # setting for unit tests.  To minimize these effects, we use
+        # workaround: make it very likely to run at the end of the tests
+        # by naming the test class "Z_".
+
+        # just lets it go through the code without B10_FROM_SOURCE env
+        # variable
+        if "B10_FROM_SOURCE" in os.environ:
+            tmppath = os.environ["B10_FROM_SOURCE"]
+            os.environ.pop("B10_FROM_SOURCE")
+            imp.reload(stats_httpd)
+            os.environ["B10_FROM_SOURCE"] = tmppath
+            imp.reload(stats_httpd)
+
+if __name__ == "__main__":
+    isc.log.resetUnitTestRootLogger()
+    unittest.main()
diff --git a/src/bin/stats/tests/stats_test.py b/src/bin/stats/tests/stats_test.py
new file mode 100644
index 0000000..e010c97
--- /dev/null
+++ b/src/bin/stats/tests/stats_test.py
@@ -0,0 +1,1431 @@
+# Copyright (C) 2010, 2011, 2012  Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+"""
+In each of these tests we start several virtual components. They are
+not the real components, no external processes are started. They are
+just simple mock objects running each in its own thread and pretending
+to be bind10 modules. This helps testing the stats module in a close
+to real environment.
+"""
+
+import unittest
+import os
+import io
+import time
+import imp
+import sys
+
+import stats
+import isc.log
+from test_utils import MyStats
+
+class TestUtilties(unittest.TestCase):
+    items = [
+        { 'item_name': 'test_int1',  'item_type': 'integer', 'item_default': 12345      },
+        { 'item_name': 'test_real1', 'item_type': 'real',    'item_default': 12345.6789 },
+        { 'item_name': 'test_bool1', 'item_type': 'boolean', 'item_default': True       },
+        { 'item_name': 'test_str1',  'item_type': 'string',  'item_default': 'ABCD'     },
+        { 'item_name': 'test_list1', 'item_type': 'list',    'item_default': [1,2,3],
+          'list_item_spec' : { 'item_name': 'number',   'item_type': 'integer' } },
+        { 'item_name': 'test_map1',  'item_type': 'map',     'item_default': {'a':1,'b':2,'c':3},
+          'map_item_spec'  : [ { 'item_name': 'a',   'item_type': 'integer'},
+                               { 'item_name': 'b',   'item_type': 'integer'},
+                               { 'item_name': 'c', 'item_type': 'integer'} ] },
+        { 'item_name': 'test_int2',  'item_type': 'integer' },
+        { 'item_name': 'test_real2', 'item_type': 'real'    },
+        { 'item_name': 'test_bool2', 'item_type': 'boolean' },
+        { 'item_name': 'test_str2',  'item_type': 'string'  },
+        { 'item_name': 'test_list2', 'item_type': 'list',
+          'list_item_spec' : { 'item_name': 'number',   'item_type': 'integer' } },
+        { 'item_name': 'test_map2',  'item_type': 'map',
+          'map_item_spec'  : [ { 'item_name': 'A', 'item_type': 'integer'},
+                               { 'item_name': 'B', 'item_type': 'integer'},
+                               { 'item_name': 'C', 'item_type': 'integer'} ] },
+        { 'item_name': 'test_none',  'item_type': 'none'    },
+        { 'item_name': 'test_list3', 'item_type': 'list',    'item_default': ["one","two","three"],
+          'list_item_spec' : { 'item_name': 'number', 'item_type': 'string' } },
+        { 'item_name': 'test_map3',  'item_type': 'map',     'item_default': {'a':'one','b':'two','c':'three'},
+          'map_item_spec'  : [ { 'item_name': 'a', 'item_type': 'string'},
+                               { 'item_name': 'b', 'item_type': 'string'},
+                               { 'item_name': 'c', 'item_type': 'string'} ] },
+        {
+          'item_name': 'test_named_set',
+          'item_type': 'named_set',
+          'item_default': { },
+          'named_set_item_spec': {
+            'item_name': 'name',
+            'item_type': 'map',
+            'item_default': { },
+            'map_item_spec': [
+              {
+                'item_name': 'number1',
+                'item_type': 'integer'
+                },
+              {
+                'item_name': 'number2',
+                'item_type': 'integer'
+                }
+              ]
+            }
+          }
+        ]
+
+    def setUp(self):
+        self.const_timestamp = 1308730448.965706
+        self.const_timetuple = (2011, 6, 22, 8, 14, 8, 2, 173, 0)
+        self.const_datetime = '2011-06-22T08:14:08Z'
+        self.__orig_time = stats.time
+        self.__orig_gmtime = stats.gmtime
+        stats.time = lambda : self.const_timestamp
+        stats.gmtime = lambda : self.const_timetuple
+
+    def tearDown(self):
+        stats.time = self.__orig_time
+        stats.gmtime = self.__orig_gmtime
+
+    def test_get_spec_defaults(self):
+        self.assertEqual(
+            stats.get_spec_defaults(self.items), {
+                'test_int1'  : 12345              ,
+                'test_real1' : 12345.6789         ,
+                'test_bool1' : True               ,
+                'test_str1'  : 'ABCD'             ,
+                'test_list1' : [1,2,3]            ,
+                'test_map1'  : {'a':1,'b':2,'c':3},
+                'test_int2'  : 0 ,
+                'test_real2' : 0.0,
+                'test_bool2' : False,
+                'test_str2'  : "",
+                'test_list2' : [0],
+                'test_map2'  : { 'A' : 0, 'B' : 0, 'C' : 0 },
+                'test_none'  : None,
+                'test_list3' : [ "one", "two", "three" ],
+                'test_map3'  : { 'a' : 'one', 'b' : 'two', 'c' : 'three' },
+                'test_named_set' : {} })
+        self.assertEqual(stats.get_spec_defaults(None), {})
+        self.assertRaises(KeyError, stats.get_spec_defaults, [{'item_name':'Foo'}])
+
+    def test_get_timestamp(self):
+        self.assertEqual(stats.get_timestamp(), self.const_timestamp)
+
+    def test_get_datetime(self):
+        self.assertEqual(stats.get_datetime(), self.const_datetime)
+        self.assertNotEqual(stats.get_datetime(
+                (2011, 6, 22, 8, 23, 40, 2, 173, 0)), self.const_datetime)
+
+    def test__accum(self):
+        self.assertEqual(stats._accum(None, None), None)
+        self.assertEqual(stats._accum(None, "b"), "b")
+        self.assertEqual(stats._accum("a", None), "a")
+        self.assertEqual(stats._accum(1, 2), 3)
+        self.assertEqual(stats._accum(0.5, 0.3), 0.8)
+        self.assertEqual(stats._accum('aa','bb'), 'bb')
+        self.assertEqual(stats._accum('1970-01-01T09:00:00Z','2012-08-09T09:33:31Z'),
+                         '2012-08-09T09:33:31Z')
+        self.assertEqual(stats._accum(
+                [1, 2, 3], [4, 5]), [5, 7, 3])
+        self.assertEqual(stats._accum(
+                [4, 5], [1, 2, 3]), [5, 7, 3])
+        self.assertEqual(stats._accum(
+                [1, 2, 3], [None, 5, 6]), [1, 7, 9])
+        self.assertEqual(stats._accum(
+                [None, 5, 6], [1, 2, 3]), [1, 7, 9])
+        self.assertEqual(stats._accum(
+                [1, 2, 3], [None, None, None, None]), [1,2,3,None])
+        self.assertEqual(stats._accum(
+                [[1,2],3],[[],5,6]), [[1,2],8,6])
+        self.assertEqual(stats._accum(
+                {'one': 1, 'two': 2, 'three': 3},
+                {'one': 4, 'two': 5}),
+                         {'one': 5, 'two': 7, 'three': 3})
+        self.assertEqual(stats._accum(
+                {'one': 1, 'two': 2, 'three': 3},
+                {'four': 4, 'five': 5}),
+                         {'one': 1, 'two': 2, 'three': 3, 'four': 4, 'five': 5})
+        self.assertEqual(stats._accum(
+                {'one': [1, 2], 'two': [3, None, 5], 'three': [None, 3, None]},
+                {'one': [2], 'two': [4, 5], 'three': [None, None, None], 'four': 'FOUR'}),
+                         {'one':[3,2], 'two':[7,5,5], 'three':[None,3,None], 'four': 'FOUR'})
+        self.assertEqual(stats._accum(
+                [ {'one': 1, 'two': 2, 'three': 3}, {'four': 4, 'five': 5, 'six': 6} ],
+                [ {}, {'four': 1, 'five': 2, 'six': 3} ]),
+                [ {'one': 1, 'two': 2, 'three': 3}, {'four': 5, 'five': 7, 'six': 9} ])
+
+    def test_merge_oldnre(self):
+        self.assertEqual(stats.merge_oldnew(1, 2), 2)
+        self.assertEqual(stats.merge_oldnew(0.5, 0.3), 0.3)
+        self.assertEqual(stats.merge_oldnew('aa','bb'), 'bb')
+        self.assertEqual(stats.merge_oldnew(
+                [1, 2, 3], [4, 5]), [4, 5, 3])
+        self.assertEqual(stats.merge_oldnew(
+                [4, 5], [1, 2, 3]), [1, 2, 3])
+        self.assertEqual(stats.merge_oldnew(
+                [1, 2, 3], [None, 5, 6]), [None, 5, 6])
+        self.assertEqual(stats.merge_oldnew(
+                [None, 5, 6], [1, 2, 3]), [1, 2, 3])
+        self.assertEqual(stats.merge_oldnew(
+                [1, 2, 3], [None, None, None, None]), [None, None, None, None])
+        self.assertEqual(stats.merge_oldnew(
+                [[1,2],3],[[],5,6]), [[1,2],5,6])
+        self.assertEqual(stats.merge_oldnew(
+                {'one': 1, 'two': 2, 'three': 3},
+                {'one': 4, 'two': 5}),
+                         {'one': 4, 'two': 5, 'three': 3})
+        self.assertEqual(stats.merge_oldnew(
+                {'one': 1, 'two': 2, 'three': 3},
+                {'four': 4, 'five': 5}),
+                         {'one': 1, 'two': 2, 'three': 3, 'four': 4, 'five': 5})
+        self.assertEqual(stats.merge_oldnew(
+                {'one': [1, 2], 'two': [3, None, 5], 'three': [None, 3, None]},
+                {'one': [2], 'two': [4, 5], 'three': [None, None, None], 'four': 'FOUR'}),
+                         {'one':[2,2], 'two':[4,5,5], 'three':[None,None,None], 'four': 'FOUR'})
+        self.assertEqual(stats.merge_oldnew(
+                [ {'one': 1, 'two': 2, 'three': 3}, {'four': 4, 'five': 5, 'six': 6} ],
+                [ {}, {'four': 1, 'five': 2, 'six': 3} ]),
+                [ {'one': 1, 'two': 2, 'three': 3}, {'four': 1, 'five': 2, 'six': 3} ])
+
+class TestCallback(unittest.TestCase):
+    def setUp(self):
+        self.dummy_func = lambda *x, **y : (x, y)
+        self.dummy_args = (1,2,3)
+        self.dummy_kwargs = {'a':1,'b':2,'c':3}
+        self.cback1 = stats.Callback(
+            command=self.dummy_func,
+            args=self.dummy_args,
+            kwargs=self.dummy_kwargs
+            )
+        self.cback2 = stats.Callback(
+            args=self.dummy_args,
+            kwargs=self.dummy_kwargs
+            )
+        self.cback3 = stats.Callback(
+            command=self.dummy_func,
+            kwargs=self.dummy_kwargs
+            )
+        self.cback4 = stats.Callback(
+            command=self.dummy_func,
+            args=self.dummy_args
+            )
+
+    def test_init(self):
+        self.assertEqual((self.cback1.command, self.cback1.args, self.cback1.kwargs),
+                         (self.dummy_func, self.dummy_args, self.dummy_kwargs))
+        self.assertEqual((self.cback2.command, self.cback2.args, self.cback2.kwargs),
+                         (None, self.dummy_args, self.dummy_kwargs))
+        self.assertEqual((self.cback3.command, self.cback3.args, self.cback3.kwargs),
+                         (self.dummy_func, (), self.dummy_kwargs))
+        self.assertEqual((self.cback4.command, self.cback4.args, self.cback4.kwargs),
+                         (self.dummy_func, self.dummy_args, {}))
+
+    def test_call(self):
+        self.assertEqual(self.cback1(), (self.dummy_args, self.dummy_kwargs))
+        self.assertEqual(self.cback1(100, 200), ((100, 200), self.dummy_kwargs))
+        self.assertEqual(self.cback1(a=100, b=200), (self.dummy_args, {'a':100, 'b':200}))
+        self.assertEqual(self.cback2(), None)
+        self.assertEqual(self.cback3(), ((), self.dummy_kwargs))
+        self.assertEqual(self.cback3(100, 200), ((100, 200), self.dummy_kwargs))
+        self.assertEqual(self.cback3(a=100, b=200), ((), {'a':100, 'b':200}))
+        self.assertEqual(self.cback4(), (self.dummy_args, {}))
+        self.assertEqual(self.cback4(100, 200), ((100, 200), {}))
+        self.assertEqual(self.cback4(a=100, b=200), (self.dummy_args, {'a':100, 'b':200}))
+
+class TestStats(unittest.TestCase):
+    def setUp(self):
+        # set the signal handler for deadlock
+        self.const_timestamp = 1308730448.965706
+        self.const_datetime = '2011-06-22T08:14:08Z'
+        self.const_default_datetime = '1970-01-01T00:00:00Z'
+        # Record original module-defined functions in case we replace them
+        self.__orig_timestamp = stats.get_timestamp
+        self.__orig_get_datetime = stats.get_datetime
+
+    def tearDown(self):
+        # restore the stored original function in case we replaced them
+        stats.get_timestamp = self.__orig_timestamp
+        stats.get_datetime = self.__orig_get_datetime
+
+    def test_init(self):
+        self.stats = MyStats()
+        self.assertEqual(self.stats.module_name, 'Stats')
+        self.assertFalse(self.stats.running)
+        self.assertTrue('command_show' in self.stats.callbacks)
+        self.assertTrue('command_status' in self.stats.callbacks)
+        self.assertTrue('command_shutdown' in self.stats.callbacks)
+        self.assertTrue('command_show' in self.stats.callbacks)
+        self.assertTrue('command_showschema' in self.stats.callbacks)
+        self.assertEqual(self.stats.config['poll-interval'], 60)
+
+    def test_init_undefcmd(self):
+        spec_str = """\
+{
+  "module_spec": {
+    "module_name": "Stats",
+    "module_description": "Stats daemon",
+    "config_data": [],
+    "commands": [
+      {
+        "command_name": "_undef_command_",
+        "command_description": "a undefined command in stats",
+        "command_args": []
+      }
+    ],
+    "statistics": []
+  }
+}
+"""
+        orig_spec_location = stats.SPECFILE_LOCATION
+        stats.SPECFILE_LOCATION = io.StringIO(spec_str)
+        self.assertRaises(stats.StatsError, MyStats)
+        stats.SPECFILE_LOCATION = orig_spec_location
+
+    def __send_command(self, stats, command_name, params=None):
+        '''Emulate a command arriving to stats by directly calling callback'''
+        return isc.config.ccsession.parse_answer(
+            stats.command_handler(command_name, params))
+
+    def test_start(self):
+        # Define a separate exception class so we can be sure that's actually
+        # the one raised in __check_start() below
+        class CheckException(Exception):
+            pass
+
+        def __check_start(tested_stats):
+            self.assertTrue(tested_stats.running)
+            raise CheckException # terminate the loop
+
+        # start without err
+        self.stats = MyStats()
+        self.assertFalse(self.stats.running)
+        self.stats._check_command = lambda: __check_start(self.stats)
+        # We are going to confirm start() will set running to True, avoiding
+        # to fall into a loop with the exception trick.
+        self.assertRaises(CheckException, self.stats.start)
+        self.assertEqual(self.__send_command(self.stats, "status"),
+                         (0, "Stats is up. (PID " + str(os.getpid()) + ")"))
+
+    def test_shutdown(self):
+        def __check_shutdown(tested_stats):
+            self.assertTrue(tested_stats.running)
+            self.assertEqual(self.__send_command(tested_stats, "shutdown"),
+                             (0, None))
+            self.assertFalse(tested_stats.running)
+            # override get_interval() so it won't go poll statistics
+            tested_stats.get_interval = lambda : 0
+
+        self.stats = MyStats()
+        self.stats._check_command = lambda: __check_shutdown(self.stats)
+        self.stats.start()
+        self.assertTrue(self.stats.mccs.stopped)
+
+    def test_handlers(self):
+        """Test command_handler"""
+
+        __stats = MyStats()
+
+        # 'show' command.  We're going to check the expected methods are
+        # called in the expected order, and check the resulting response.
+        # Details of each method are tested separately.
+        call_log = []
+        def __steal_method(fn_name, *arg):
+            call_log.append((fn_name, arg))
+            if fn_name == 'update_stat':
+                return False        # "no error"
+            if fn_name == 'showschema':
+                return isc.config.create_answer(0, 'no error')
+
+        # Fake some methods and attributes for inspection
+        __stats.do_polling = lambda: __steal_method('polling')
+        __stats.update_statistics_data = \
+            lambda x, y, z: __steal_method('update_stat', x, y, z)
+        __stats.update_modules = lambda: __steal_method('update_module')
+        __stats.mccs.lname = 'test lname'
+        __stats.statistics_data = {'Init': {'boot_time': self.const_datetime}}
+
+        # skip initial polling
+        stats.get_timestamp = lambda: 0
+        __stats._lasttime_poll = 0
+
+        stats.get_datetime = lambda: 42 # make the result predictable
+
+        # now send the command
+        self.assertEqual(
+            self.__send_command(
+                __stats, 'show',
+                params={ 'owner' : 'Init', 'name'  : 'boot_time' }),
+            (0, {'Init': {'boot_time': self.const_datetime}}))
+        # Check if expected methods are called
+        self.assertEqual([('update_stat',
+                           ('Stats', 'test lname',
+                            {'timestamp': 0,
+                             'report_time': 42})),
+                          ('update_module', ())], call_log)
+
+        # Then update faked timestamp so the initial polling will happen, and
+        # confirm that.
+        call_log = []
+        stats.get_timestamp = lambda: 10
+        self.assertEqual(
+            self.__send_command(
+                __stats, 'show',
+                params={ 'owner' : 'Init', 'name'  : 'boot_time' }),
+            (0, {'Init': {'boot_time': self.const_datetime}}))
+        self.assertEqual([('polling', ()),
+                          ('update_stat',
+                           ('Stats', 'test lname',
+                            {'timestamp': 10,
+                             'report_time': 42})),
+                          ('update_module', ())], call_log)
+
+        # 'status' command.  We can confirm the behavior without any fake
+        self.assertEqual(
+            self.__send_command(__stats, 'status'),
+            (0, "Stats is up. (PID " + str(os.getpid()) + ")"))
+
+        # 'showschema' command.  update_modules() will be called, which
+        # (implicitly) confirms the correct method is called; further details
+        # are tested separately.
+        call_log = []
+        (rcode, value) = self.__send_command(__stats, 'showschema')
+        self.assertEqual([('update_module', ())], call_log)
+
+        # Unknown command.  Error should be returned
+        self.assertEqual(
+            self.__send_command(__stats, '__UNKNOWN__'),
+            (1, "Unknown command: '__UNKNOWN__'"))
+
+    def test_update_modules(self):
+        """Confirm the behavior of Stats.update_modules().
+
+        It checks whether the expected command is sent to ConfigManager,
+        and whether the answer from ConfigManager is handled as expected.
+
+        """
+
+        def __check_rpc_call(command, group):
+            self.assertEqual('ConfigManager', group)
+            self.assertEqual(command,
+                             isc.config.ccsession.COMMAND_GET_STATISTICS_SPEC)
+            answer_value = {'Init': [{
+                        "item_name": "boot_time",
+                        "item_type": "string",
+                        "item_optional": False,
+                        # Use a different default so we can check it below
+                        "item_default": "2013-01-01T00:00:01Z",
+                        "item_title": "Boot time",
+                        "item_description": "dummy desc",
+                        "item_format": "date-time"
+                        }]}
+            return answer_value
+
+        self.stats = MyStats()
+        self.stats.cc_session.rpc_call = __check_rpc_call
+
+        self.stats.update_modules()
+
+        # Stats is always incorporated.  For others, only the ones returned
+        # by group_recvmsg() above is available.
+        self.assertTrue('Stats' in self.stats.modules)
+        self.assertTrue('Init' in self.stats.modules)
+        self.assertFalse('Dummy' in self.stats.modules)
+
+        my_statistics_data = stats.get_spec_defaults(
+            self.stats.modules['Stats'].get_statistics_spec())
+        self.assertTrue('report_time' in my_statistics_data)
+        self.assertTrue('boot_time' in my_statistics_data)
+        self.assertTrue('last_update_time' in my_statistics_data)
+        self.assertTrue('timestamp' in my_statistics_data)
+        self.assertTrue('lname' in my_statistics_data)
+        self.assertEqual(my_statistics_data['report_time'],
+                         self.const_default_datetime)
+        self.assertEqual(my_statistics_data['boot_time'],
+                         self.const_default_datetime)
+        self.assertEqual(my_statistics_data['last_update_time'],
+                         self.const_default_datetime)
+        self.assertEqual(my_statistics_data['timestamp'], 0.0)
+        self.assertEqual(my_statistics_data['lname'], "")
+        my_statistics_data = stats.get_spec_defaults(
+            self.stats.modules['Init'].get_statistics_spec())
+        self.assertTrue('boot_time' in my_statistics_data)
+        self.assertEqual(my_statistics_data['boot_time'],
+                         "2013-01-01T00:00:01Z")
+
+        # Error case
+        def __raise_on_rpc_call(x, y):
+            raise isc.config.RPCError(99, 'error')
+        orig_parse_answer = stats.isc.config.ccsession.parse_answer
+        self.stats.cc_session.rpc_call = __raise_on_rpc_call
+        self.assertRaises(stats.StatsError, self.stats.update_modules)
+
+    def test_get_statistics_data(self):
+        """Confirm the behavior of Stats.get_statistics_data().
+
+        It should first call update_modules(), and then retrieve the requested
+        data from statistics_data.  We confirm this by fake update_modules()
+        where we set the expected data in statistics_data.
+
+        """
+        self.stats = MyStats()
+        def __faked_update_modules():
+            self.stats.statistics_data = { \
+                'Stats': {
+                    'report_time': self.const_default_datetime,
+                    'boot_time': None,
+                    'last_update_time': None,
+                    'timestamp': 0.0,
+                    'lname': 'dummy name'
+                    },
+                'Init': { 'boot_time': None }
+                }
+
+        self.stats.update_modules = __faked_update_modules
+
+        my_statistics_data = self.stats.get_statistics_data()
+        self.assertTrue('Stats' in my_statistics_data)
+        self.assertTrue('Init' in my_statistics_data)
+        self.assertTrue('boot_time' in my_statistics_data['Init'])
+
+        my_statistics_data = self.stats.get_statistics_data(owner='Stats')
+        self.assertTrue('Stats' in my_statistics_data)
+        self.assertTrue('report_time' in my_statistics_data['Stats'])
+        self.assertTrue('boot_time' in my_statistics_data['Stats'])
+        self.assertTrue('last_update_time' in my_statistics_data['Stats'])
+        self.assertTrue('timestamp' in my_statistics_data['Stats'])
+        self.assertTrue('lname' in my_statistics_data['Stats'])
+        self.assertRaises(stats.StatsError, self.stats.get_statistics_data,
+                          owner='Foo')
+
+        my_statistics_data = self.stats.get_statistics_data(
+            owner='Stats', name='report_time')
+        self.assertEqual(my_statistics_data['Stats']['report_time'],
+                         self.const_default_datetime)
+
+        my_statistics_data = self.stats.get_statistics_data(
+            owner='Stats', name='boot_time')
+        self.assertTrue('boot_time' in my_statistics_data['Stats'])
+
+        my_statistics_data = self.stats.get_statistics_data(
+            owner='Stats', name='last_update_time')
+        self.assertTrue('last_update_time' in my_statistics_data['Stats'])
+
+        my_statistics_data = self.stats.get_statistics_data(
+            owner='Stats', name='timestamp')
+        self.assertEqual(my_statistics_data['Stats']['timestamp'], 0.0)
+
+        my_statistics_data = self.stats.get_statistics_data(
+            owner='Stats', name='lname')
+        self.assertTrue(len(my_statistics_data['Stats']['lname']) >0)
+        self.assertRaises(stats.StatsError, self.stats.get_statistics_data,
+                          owner='Stats', name='Bar')
+        self.assertRaises(stats.StatsError, self.stats.get_statistics_data,
+                          owner='Foo', name='Bar')
+        self.assertRaises(stats.StatsError, self.stats.get_statistics_data,
+                          name='Bar')
+
+    def test_update_statistics_data(self):
+        """test for list-type statistics"""
+        self.stats = MyStats()
+        _test_exp1 = {
+              'zonename': 'test1.example',
+              'queries.tcp': 5,
+              'queries.udp': 4
+            }
+        _test_exp2 = {
+              'zonename': 'test2.example',
+              'queries.tcp': 3,
+              'queries.udp': 2
+            }
+        _test_exp3 = {}
+        _test_exp4 = {
+              'queries.udp': 4
+            }
+        _test_exp5_1 = {
+              'queries.perzone': [
+                { },
+                {
+                  'queries.udp': 9876
+                }
+              ]
+            }
+        _test_exp5_2 = {
+              'queries.perzone[1]/queries.udp':
+                  isc.cc.data.find(_test_exp5_1,
+                                   'queries.perzone[1]/queries.udp')
+            }
+        # Success cases
+        self.assertEqual(self.stats.statistics_data['Stats']['lname'],
+                         self.stats.cc_session.lname)
+        self.stats.update_statistics_data(
+            'Stats', self.stats.cc_session.lname,
+            {'lname': 'foo at bar'})
+        self.assertEqual(self.stats.statistics_data['Stats']['lname'],
+                         'foo at bar')
+        self.assertIsNone(self.stats.update_statistics_data(
+            'Auth', 'foo1', {'queries.perzone': [_test_exp1]}))
+        self.assertEqual(self.stats.statistics_data_bymid['Auth']\
+                             ['foo1']['queries.perzone'],\
+                             [_test_exp1])
+        self.assertIsNone(self.stats.update_statistics_data(
+            'Auth', 'foo1', {'queries.perzone': [_test_exp2]}))
+        self.assertEqual(self.stats.statistics_data_bymid['Auth']\
+                             ['foo1']['queries.perzone'],\
+                             [_test_exp2])
+        self.assertIsNone(self.stats.update_statistics_data(
+            'Auth', 'foo1', {'queries.perzone': [_test_exp1,_test_exp2]}))
+        self.assertEqual(self.stats.statistics_data_bymid['Auth']\
+                             ['foo1']['queries.perzone'],
+                         [_test_exp1,_test_exp2])
+        # differential update
+        self.assertIsNone(self.stats.update_statistics_data(
+            'Auth', 'foo1', {'queries.perzone': [_test_exp3,_test_exp4]}))
+        _new_data = stats.merge_oldnew(_test_exp2,_test_exp4)
+        self.assertEqual(self.stats.statistics_data_bymid['Auth']\
+                             ['foo1']['queries.perzone'], \
+                             [_test_exp1,_new_data])
+        self.assertIsNone(self.stats.update_statistics_data(
+            'Auth', 'foo1', _test_exp5_2))
+        _new_data = stats.merge_oldnew(_new_data,
+                                       _test_exp5_1['queries.perzone'][1])
+        self.assertEqual(self.stats.statistics_data_bymid['Auth']\
+                             ['foo1']['queries.perzone'], \
+                             [_test_exp1,_new_data])
+        # Error cases
+        self.assertEqual(self.stats.update_statistics_data('Stats', None,
+                                                           {'lname': 0.0}),
+                         ['0.0 should be a string'])
+        self.assertEqual(self.stats.update_statistics_data('Dummy', None,
+                                                           {'foo': 'bar'}),
+                         ['unknown module name: Dummy'])
+        self.assertEqual(self.stats.update_statistics_data(
+                'Auth', 'foo1', {'queries.perzone': [None]}), ['None should be a map'])
+
+    def test_update_statistics_data_pt2(self):
+        """test for named_set-type statistics"""
+        self.stats = MyStats()
+        _test_exp1 = \
+            { 'test10.example': { 'queries.tcp': 5, 'queries.udp': 4 } }
+        _test_exp2 = \
+            { 'test20.example': { 'queries.tcp': 3, 'queries.udp': 2 } }
+        _test_exp3 = {}
+        _test_exp4 = { 'test20.example': { 'queries.udp': 4 } }
+        _test_exp5_1 = { 'test10.example': { 'queries.udp': 5432 } }
+        _test_exp5_2 ={
+              'nds_queries.perzone/test10.example/queries.udp':
+                  isc.cc.data.find(_test_exp5_1, 'test10.example/queries.udp')
+            }
+        _test_exp6 = { 'foo/bar':  'brabra' }
+        _test_exp7 = { 'foo[100]': 'bar' }
+        # Success cases
+        self.assertIsNone(self.stats.update_statistics_data(
+            'Auth', 'foo1', {'nds_queries.perzone': _test_exp1}))
+        self.assertEqual(self.stats.statistics_data_bymid['Auth']\
+                             ['foo1']['nds_queries.perzone'],\
+                             _test_exp1)
+        self.assertIsNone(self.stats.update_statistics_data(
+            'Auth', 'foo1', {'nds_queries.perzone': _test_exp2}))
+        self.assertEqual(self.stats.statistics_data_bymid['Auth']\
+                             ['foo1']['nds_queries.perzone'],\
+                         dict(_test_exp1,**_test_exp2))
+        self.assertIsNone(self.stats.update_statistics_data(
+            'Auth', 'foo1', {'nds_queries.perzone':
+                                 dict(_test_exp1, **_test_exp2)}))
+        self.assertEqual(self.stats.statistics_data_bymid['Auth']\
+                             ['foo1']['nds_queries.perzone'],
+                         dict(_test_exp1, **_test_exp2))
+        # differential update
+        self.assertIsNone(self.stats.update_statistics_data(
+            'Auth', 'foo1', {'nds_queries.perzone':
+                                 dict(_test_exp3, **_test_exp4)}))
+        _new_val = dict(_test_exp1,
+                        **stats.merge_oldnew(_test_exp2,_test_exp4))
+        self.assertEqual(self.stats.statistics_data_bymid['Auth']\
+                             ['foo1']['nds_queries.perzone'],\
+                             _new_val)
+        self.assertIsNone(self.stats.update_statistics_data(
+            'Auth', 'foo1', _test_exp5_2))
+        _new_val = stats.merge_oldnew(_new_val, _test_exp5_1)
+        self.assertEqual(self.stats.statistics_data_bymid['Auth']\
+                             ['foo1']['nds_queries.perzone'],\
+                             _new_val)
+        self.assertIsNone(self.stats.update_statistics_data(
+            'Auth', 'foo2', _test_exp5_2))
+        _new_val = stats.merge_oldnew(_new_val, _test_exp5_1)
+        self.assertEqual(self.stats.statistics_data_bymid['Auth']\
+                             ['foo2']['nds_queries.perzone'],\
+                             _test_exp5_1)
+        # Error cases
+        self.assertEqual(self.stats.update_statistics_data(
+                'Auth', 'foo1', {'nds_queries.perzone': None}),
+                         ['None should be a map'])
+        self.assertEqual(self.stats.statistics_data_bymid['Auth']\
+                             ['foo1']['nds_queries.perzone'],\
+                             _new_val)
+        self.assertEqual(self.stats.update_statistics_data(
+                'Auth', 'foo1', _test_exp6), ['unknown item foo'])
+        self.assertEqual(self.stats.statistics_data_bymid['Auth']\
+                             ['foo1']['nds_queries.perzone'],\
+                             _new_val)
+        self.assertEqual(self.stats.update_statistics_data(
+                'Init', 'bar1', _test_exp7), ["KeyError: 'foo'"])
+        self.assertEqual(self.stats.update_statistics_data(
+                'Foo', 'foo1', _test_exp6), ['unknown module name: Foo'])
+
+    def test_update_statistics_data_withmid(self):
+        self.stats = MyStats()
+
+        # This test relies on existing statistics data at the Stats object.
+        # This version of test prepares the data using the do_polling() method;
+        # that's a bad practice because a unittest for a method
+        # (update_statistics_data) would heavily depend on details of another
+        # method (do_polling).  However, there's currently no direct test
+        # for do_polling (which is also bad), so we still keep that approach,
+        # partly for testing do_polling indirectly.  #2781 should provide
+        # direct test for do_polling, with which this test scenario should
+        # also be changed to be more stand-alone.
+
+        # We use the knowledge of what kind of messages are sent via
+        # do_polling, and return the following faked answer directly.
+        create_answer = isc.config.ccsession.create_answer # shortcut
+        self.stats._answers = [
+            # Answer for "show_processes"
+            (create_answer(0, [[1034, 'b10-auth-1', 'Auth'],
+                               [1035, 'b10-auth-2', 'Auth']]),  None),
+            # Answers for "getstats".  2 for Auth instances and 1 for Init.
+            # we return some bogus values for Init, but the rest of the test
+            # doesn't need it, so it's okay.
+            (create_answer(0, self.stats._auth_sdata), {'from': 'auth1'}),
+            (create_answer(0, self.stats._auth_sdata), {'from': 'auth2'}),
+            (create_answer(0, self.stats._auth_sdata), {'from': 'auth3'})
+            ]
+        # do_polling calls update_modules internally; in our scenario there's
+        # no change in modules, so we make it no-op.
+        self.stats.update_modules = lambda: None
+        # Now call do_polling.
+        self.stats.do_polling()
+
+        # samples of query number
+        bar1_tcp = 1001
+        bar2_tcp = 2001
+        bar3_tcp = 1002
+        bar3_udp = 1003
+        # two auth instances invoked, so we double the pre-set stat values
+        sum_qtcp = self.stats._queries_tcp * 2
+        sum_qudp = self.stats._queries_udp * 2
+        self.stats.update_statistics_data('Auth', "bar1 at foo",
+                                          {'queries.tcp': bar1_tcp})
+        self.assertTrue('Auth' in self.stats.statistics_data)
+        self.assertTrue('queries.tcp' in self.stats.statistics_data['Auth'])
+        self.assertEqual(self.stats.statistics_data['Auth']['queries.tcp'],
+                         bar1_tcp + sum_qtcp)
+        self.assertTrue('Auth' in self.stats.statistics_data_bymid)
+        self.assertTrue('bar1 at foo' in self.stats.statistics_data_bymid['Auth'])
+        self.assertTrue('queries.tcp' in self.stats.statistics_data_bymid
+                        ['Auth']['bar1 at foo'])
+        self.assertEqual(self.stats.statistics_data_bymid['Auth']['bar1 at foo'],
+                         {'queries.tcp': bar1_tcp})
+        # check consolidation of statistics data even if there is
+        # non-existent mid of Auth
+        self.stats.update_statistics_data('Auth', "bar2 at foo",
+                                          {'queries.tcp': bar2_tcp})
+        self.assertTrue('Auth' in self.stats.statistics_data)
+        self.assertTrue('queries.tcp' in self.stats.statistics_data['Auth'])
+        self.assertEqual(self.stats.statistics_data['Auth']['queries.tcp'],
+                         bar1_tcp + bar2_tcp + sum_qtcp)
+        self.assertTrue('Auth' in self.stats.statistics_data_bymid)
+        self.assertTrue('bar1 at foo' in self.stats.statistics_data_bymid['Auth'])
+        self.assertTrue('queries.tcp' in self.stats.statistics_data_bymid['Auth']['bar1 at foo'])
+        self.assertEqual(self.stats.statistics_data_bymid['Auth']['bar1 at foo'],
+                         {'queries.tcp': bar1_tcp})
+        self.assertEqual(self.stats.statistics_data_bymid['Auth']['bar2 at foo'],
+                         {'queries.tcp': bar2_tcp})
+        # kill running Auth but the statistics data doesn't change
+        self.stats.update_statistics_data()
+        self.assertTrue('Auth' in self.stats.statistics_data)
+        self.assertTrue('queries.tcp' in self.stats.statistics_data['Auth'])
+        self.assertTrue('queries.udp' in self.stats.statistics_data['Auth'])
+        self.assertEqual(self.stats.statistics_data['Auth']['queries.tcp'],
+                         bar1_tcp + bar2_tcp + sum_qtcp)
+        self.assertEqual(self.stats.statistics_data['Auth']['queries.udp'],
+                         sum_qudp)
+        self.assertTrue('Auth' in self.stats.statistics_data_bymid)
+        # restore statistics data of killed auth
+        self.stats.update_statistics_data('Auth',
+                                          "bar1 at foo",
+                                          {'queries.tcp': bar1_tcp})
+        # set another mid of Auth
+        self.stats.update_statistics_data('Auth',
+                                          "bar3 at foo",
+                                          {'queries.tcp':bar3_tcp,
+                                           'queries.udp':bar3_udp})
+        self.assertTrue('Auth' in self.stats.statistics_data)
+        self.assertTrue('queries.tcp' in self.stats.statistics_data['Auth'])
+        self.assertTrue('queries.udp' in self.stats.statistics_data['Auth'])
+        self.assertEqual(self.stats.statistics_data['Auth']['queries.tcp'],
+                         bar1_tcp + bar2_tcp + bar3_tcp + sum_qtcp)
+        self.assertEqual(self.stats.statistics_data['Auth']['queries.udp'],
+                         bar3_udp + sum_qudp)
+        self.assertTrue('Auth' in self.stats.statistics_data_bymid)
+        self.assertTrue('bar1 at foo' in self.stats.statistics_data_bymid['Auth'])
+        self.assertTrue('bar3 at foo' in self.stats.statistics_data_bymid['Auth'])
+        self.assertTrue('queries.tcp' in self.stats.statistics_data_bymid['Auth']['bar1 at foo'])
+        self.assertTrue('queries.udp' in self.stats.statistics_data_bymid['Auth']['bar3 at foo'])
+        self.assertTrue('queries.udp' in self.stats.statistics_data_bymid['Auth']['bar3 at foo'])
+        self.assertEqual(self.stats.statistics_data_bymid['Auth']['bar1 at foo']['queries.tcp'], bar1_tcp)
+        self.assertEqual(self.stats.statistics_data_bymid['Auth']['bar3 at foo']['queries.tcp'], bar3_tcp)
+        self.assertEqual(self.stats.statistics_data_bymid['Auth']['bar3 at foo']['queries.udp'], bar3_udp)
+
+    def test_config(self):
+        orig_get_timestamp = stats.get_timestamp
+        stats.get_timestamp = lambda : self.const_timestamp
+        stat = MyStats()
+
+        # test updating poll-interval
+        self.assertEqual(stat.config['poll-interval'], 60)
+        self.assertEqual(stat.get_interval(), 60)
+        self.assertEqual(stat.next_polltime, self.const_timestamp + 60)
+        self.assertEqual(stat.config_handler({'poll-interval': 120}),
+                         isc.config.create_answer(0))
+        self.assertEqual(stat.config['poll-interval'], 120)
+        self.assertEqual(stat.get_interval(), 120)
+        self.assertEqual(stat.next_polltime, self.const_timestamp + 120)
+        stats.get_timestamp = orig_get_timestamp
+        self.assertEqual(stat.config_handler({'poll-interval': "foo"}),
+                         isc.config.create_answer(1, 'foo should be an integer'))
+        self.assertEqual(stat.config_handler({'poll-interval': -1}),
+                         isc.config.create_answer(1, 'Negative integer ignored'))
+        # unknown item
+        self.assertEqual(
+            stat.config_handler({'_UNKNOWN_KEY_': None}),
+            isc.config.ccsession.create_answer(
+                1, "unknown item _UNKNOWN_KEY_"))
+        # test no change if zero interval time
+        self.assertEqual(stat.config_handler({'poll-interval': 0}),
+                         isc.config.create_answer(0))
+        self.assertEqual(stat.config['poll-interval'], 0)
+
+        # see the comment for test_update_statistics_data_withmid.  We abuse
+        # do_polling here, too.  With #2781 we should make it more direct.
+        create_answer = isc.config.ccsession.create_answer # shortcut
+        stat._answers = [\
+            # Answer for "show_processes"
+            (create_answer(0, []),  None),
+            # Answers for "getstats" for Init (the other one for Auth, but
+            # that doesn't matter for this test)
+            (create_answer(0, stat._init_sdata), {'from': 'init'}),
+            (create_answer(0, stat._init_sdata), {'from': 'init'})
+            ]
+        stat.update_modules = lambda: None
+
+        self.assertEqual(
+            self.__send_command(
+                stat, 'show',
+                params={ 'owner' : 'Init', 'name'  : 'boot_time' }),
+            (0, {'Init': {'boot_time': self.const_datetime}}))
+
+    def test_commands(self):
+        self.stats = MyStats()
+
+        # status
+        self.assertEqual(self.stats.command_status(),
+                isc.config.create_answer(
+                0, "Stats is up. (PID " + str(os.getpid()) + ")"))
+
+        # shutdown
+        self.stats.running = True
+        self.assertEqual(self.stats.command_shutdown(),
+                         isc.config.create_answer(0))
+        self.assertFalse(self.stats.running)
+
+    def test_command_show_error(self):
+        self.stats = MyStats()
+        self.assertEqual(self.stats.command_show(owner='Foo', name=None),
+                         isc.config.create_answer(
+                1,
+                "specified arguments are incorrect: owner: Foo, name: None"))
+        self.assertEqual(self.stats.command_show(owner='Foo', name='_bar_'),
+                         isc.config.create_answer(
+                1,
+                "specified arguments are incorrect: owner: Foo, name: _bar_"))
+        self.assertEqual(self.stats.command_show(owner='Foo', name='bar'),
+                         isc.config.create_answer(
+                1,
+                "specified arguments are incorrect: owner: Foo, name: bar"))
+
+    def test_command_show_auth(self):
+        self.stats = MyStats()
+        self.stats.update_modules = lambda: None
+
+        # Test data borrowed from test_update_statistics_data_withmid
+        create_answer = isc.config.ccsession.create_answer # shortcut
+        self.stats._answers = [
+            (create_answer(0, [[1034, 'b10-auth-1', 'Auth'],
+                               [1035, 'b10-auth-2', 'Auth']]),  None),
+            (create_answer(0, self.stats._auth_sdata), {'from': 'auth1'}),
+            (create_answer(0, self.stats._auth_sdata), {'from': 'auth2'}),
+            (create_answer(0, self.stats._auth_sdata), {'from': 'auth3'})
+            ]
+
+        num_instances = 2
+        sum_qtcp = 0
+        sum_qudp = 0
+        sum_qtcp_perzone1 = 0
+        sum_qudp_perzone1 = 0
+        sum_qtcp_perzone2 = 4 * num_instances
+        sum_qudp_perzone2 = 3 * num_instances
+        sum_qtcp_nds_perzone10 = 0
+        sum_qudp_nds_perzone10 = 0
+        sum_qtcp_nds_perzone20 = 4 * num_instances
+        sum_qudp_nds_perzone20 = 3 * num_instances
+
+        self.maxDiff = None
+        for a in (0, num_instances):
+            sum_qtcp += self.stats._queries_tcp
+            sum_qudp += self.stats._queries_udp
+            sum_qtcp_perzone1 += self.stats._queries_per_zone[0]['queries.tcp']
+            sum_qudp_perzone1 += self.stats._queries_per_zone[0]['queries.udp']
+            sum_qtcp_nds_perzone10 += \
+                self.stats._nds_queries_per_zone['test10.example']['queries.tcp']
+            sum_qudp_nds_perzone10 += \
+                self.stats._nds_queries_per_zone['test10.example']['queries.udp']
+
+        self.assertEqual(self.stats.command_show(owner='Auth'),
+                         isc.config.create_answer(
+                0, {'Auth':{ 'queries.udp': sum_qudp,
+                     'queries.tcp': sum_qtcp,
+                     'queries.perzone': [{ 'zonename': 'test1.example',
+                                           'queries.udp': sum_qudp_perzone1,
+                                           'queries.tcp': sum_qtcp_perzone1 },
+                                         { 'zonename': 'test2.example',
+                                           'queries.udp': sum_qudp_perzone2,
+                                           'queries.tcp': sum_qtcp_perzone2 }
+                                         ],
+                     'nds_queries.perzone': { 'test10.example' : {
+                                              'queries.udp': sum_qudp_nds_perzone10,
+                                              'queries.tcp': sum_qtcp_nds_perzone10 },
+                                              'test20.example' : {
+                                              'queries.udp': sum_qudp_nds_perzone20,
+                                              'queries.tcp': sum_qtcp_nds_perzone20 }
+                             }}}))
+        self.assertEqual(self.stats.command_show(owner='Auth', name='queries.udp'),
+                         isc.config.create_answer(
+                0, {'Auth': {'queries.udp': sum_qudp}}))
+        self.assertEqual(self.stats.command_show(owner='Auth', name='queries.perzone'),
+                         isc.config.create_answer(
+                0, {'Auth': {'queries.perzone': [
+                            { 'zonename': 'test1.example',
+                              'queries.udp': sum_qudp_perzone1,
+                              'queries.tcp': sum_qtcp_perzone1 },
+                            { 'zonename': 'test2.example',
+                              'queries.udp': sum_qudp_perzone2,
+                              'queries.tcp': sum_qtcp_perzone2 }]}}))
+        self.assertEqual(self.stats.command_show(owner='Auth', name='nds_queries.perzone'),
+                         isc.config.create_answer(
+                0, {'Auth': {'nds_queries.perzone': {
+                            'test10.example': {
+                                'queries.udp': sum_qudp_nds_perzone10,
+                                'queries.tcp': sum_qtcp_nds_perzone10 },
+                            'test20.example': {
+                                'queries.udp': sum_qudp_nds_perzone20,
+                                'queries.tcp': sum_qtcp_nds_perzone20 }}}}))
+
+    def test_command_show_stats(self):
+        self.stats = MyStats()
+        orig_get_datetime = stats.get_datetime
+        orig_get_timestamp = stats.get_timestamp
+        stats.get_datetime = lambda x=None: self.const_datetime
+        stats.get_timestamp = lambda : self.const_timestamp
+        self.assertEqual(self.stats.command_show(owner='Stats',
+                                                 name='report_time'),
+                         isc.config.create_answer(
+                0, {'Stats': {'report_time':self.const_datetime}}))
+        self.assertEqual(self.stats.command_show(owner='Stats',
+                                                 name='timestamp'),
+                         isc.config.create_answer(
+                0, {'Stats': {'timestamp':self.const_timestamp}}))
+        stats.get_datetime = orig_get_datetime
+        stats.get_timestamp = orig_get_timestamp
+        self.stats.do_polling = lambda : None
+        self.stats.modules[self.stats.module_name] = \
+            isc.config.module_spec.ModuleSpec(
+            { "module_name": self.stats.module_name, "statistics": [] } )
+        self.assertRaises(
+            stats.StatsError, self.stats.command_show,
+            owner=self.stats.module_name, name='bar')
+
+    def test_command_showchema(self):
+        self.stats = MyStats()
+        (rcode, value) = isc.config.ccsession.parse_answer(
+            self.stats.command_showschema())
+        self.assertEqual(rcode, 0)
+        self.assertEqual(len(value), 3)
+        self.assertTrue('Stats' in value)
+        self.assertTrue('Init' in value)
+        self.assertTrue('Auth' in value)
+        self.assertFalse('__Dummy__' in value)
+        schema = value['Stats']
+        self.assertEqual(len(schema), 5)
+        for item in schema:
+            self.assertTrue(len(item) == 6 or len(item) == 7)
+            self.assertTrue('item_name' in item)
+            self.assertTrue('item_type' in item)
+            self.assertTrue('item_optional' in item)
+            self.assertTrue('item_default' in item)
+            self.assertTrue('item_title' in item)
+            self.assertTrue('item_description' in item)
+            if len(item) == 7:
+                self.assertTrue('item_format' in item)
+
+        schema = value['Init']
+        self.assertEqual(len(schema), 1)
+        for item in schema:
+            self.assertTrue(len(item) == 7)
+            self.assertTrue('item_name' in item)
+            self.assertTrue('item_type' in item)
+            self.assertTrue('item_optional' in item)
+            self.assertTrue('item_default' in item)
+            self.assertTrue('item_title' in item)
+            self.assertTrue('item_description' in item)
+            self.assertTrue('item_format' in item)
+
+        schema = value['Auth']
+        self.assertEqual(len(schema), 4)
+        for item in schema:
+            if item['item_type'] == 'list' or item['item_type'] == 'named_set':
+                self.assertEqual(len(item), 7)
+            else:
+                self.assertEqual(len(item), 6)
+            self.assertTrue('item_name' in item)
+            self.assertTrue('item_type' in item)
+            self.assertTrue('item_optional' in item)
+            self.assertTrue('item_default' in item)
+            self.assertTrue('item_title' in item)
+            self.assertTrue('item_description' in item)
+
+        (rcode, value) = isc.config.ccsession.parse_answer(
+            self.stats.command_showschema(owner='Stats'))
+        self.assertEqual(rcode, 0)
+        self.assertTrue('Stats' in value)
+        self.assertFalse('Init' in value)
+        self.assertFalse('Auth' in value)
+        for item in value['Stats']:
+            self.assertTrue(len(item) == 6 or len(item) == 7)
+            self.assertTrue('item_name' in item)
+            self.assertTrue('item_type' in item)
+            self.assertTrue('item_optional' in item)
+            self.assertTrue('item_default' in item)
+            self.assertTrue('item_title' in item)
+            self.assertTrue('item_description' in item)
+            if len(item) == 7:
+                self.assertTrue('item_format' in item)
+
+        (rcode, value) = isc.config.ccsession.parse_answer(
+            self.stats.command_showschema(owner='Stats', name='report_time'))
+        self.assertEqual(rcode, 0)
+        self.assertTrue('Stats' in value)
+        self.assertFalse('Init' in value)
+        self.assertFalse('Auth' in value)
+        self.assertEqual(len(value['Stats'][0]), 7)
+        self.assertTrue('item_name' in value['Stats'][0])
+        self.assertTrue('item_type' in value['Stats'][0])
+        self.assertTrue('item_optional' in value['Stats'][0])
+        self.assertTrue('item_default' in value['Stats'][0])
+        self.assertTrue('item_title' in value['Stats'][0])
+        self.assertTrue('item_description' in value['Stats'][0])
+        self.assertTrue('item_format' in value['Stats'][0])
+        self.assertEqual(value['Stats'][0]['item_name'], 'report_time')
+        self.assertEqual(value['Stats'][0]['item_format'], 'date-time')
+
+        self.assertEqual(self.stats.command_showschema(owner='Foo'),
+                         isc.config.create_answer(
+                1, "specified arguments are incorrect: owner: Foo, name: None"))
+        self.assertEqual(self.stats.command_showschema(owner='Foo', name='bar'),
+                         isc.config.create_answer(
+                1, "specified arguments are incorrect: owner: Foo, name: bar"))
+        self.assertEqual(self.stats.command_showschema(owner='Auth'),
+                         isc.config.create_answer(
+                0, {'Auth': [{
+                        "item_default": 0,
+                        "item_description": "A number of total query counts which all auth servers receive over TCP since they started initially",
+                        "item_name": "queries.tcp",
+                        "item_optional": False,
+                        "item_title": "Queries TCP",
+                        "item_type": "integer"
+                        },
+                    {
+                        "item_default": 0,
+                        "item_description": "A number of total query counts which all auth servers receive over UDP since they started initially",
+                        "item_name": "queries.udp",
+                        "item_optional": False,
+                        "item_title": "Queries UDP",
+                        "item_type": "integer"
+                        },
+                    {
+                        "item_name": "queries.perzone",
+                        "item_type": "list",
+                        "item_optional": False,
+                        "item_default": [
+                            {
+                                "zonename" : "test1.example",
+                                "queries.udp" : 1,
+                                "queries.tcp" : 2
+                                },
+                            {
+                                "zonename" : "test2.example",
+                                "queries.udp" : 3,
+                                "queries.tcp" : 4
+                                }
+                        ],
+                        "item_title": "Queries per zone",
+                        "item_description": "Queries per zone",
+                        "list_item_spec": {
+                            "item_name": "zones",
+                            "item_type": "map",
+                            "item_optional": False,
+                            "item_default": {},
+                            "map_item_spec": [
+                                {
+                                    "item_name": "zonename",
+                                    "item_type": "string",
+                                    "item_optional": False,
+                                    "item_default": "",
+                                    "item_title": "Zonename",
+                                    "item_description": "Zonename"
+                                    },
+                                {
+                                    "item_name": "queries.udp",
+                                    "item_type": "integer",
+                                    "item_optional": False,
+                                    "item_default": 0,
+                                    "item_title": "Queries UDP per zone",
+                                    "item_description": "A number of UDP query counts per zone"
+                                    },
+                                {
+                                    "item_name": "queries.tcp",
+                                    "item_type": "integer",
+                                    "item_optional": False,
+                                    "item_default": 0,
+                                    "item_title": "Queries TCP per zone",
+                                    "item_description": "A number of TCP query counts per zone"
+                                    }
+                                ]
+                            }
+                        },
+                    {
+                        "item_name": "nds_queries.perzone",
+                        "item_type": "named_set",
+                        "item_optional": False,
+                        "item_default": {
+                            "test10.example" : {
+                                "queries.udp" : 1,
+                                "queries.tcp" : 2
+                            },
+                            "test20.example" : {
+                                "queries.udp" : 3,
+                                "queries.tcp" : 4
+                            }
+                        },
+                        "item_title": "Queries per zone",
+                        "item_description": "Queries per zone",
+                        "named_set_item_spec": {
+                            "item_name": "zonename",
+                            "item_type": "map",
+                            "item_optional": False,
+                            "item_default": {},
+                            "item_title": "Zonename",
+                            "item_description": "Zonename",
+                            "map_item_spec": [
+                                {
+                                    "item_name": "queries.udp",
+                                    "item_type": "integer",
+                                    "item_optional": False,
+                                    "item_default": 0,
+                                    "item_title": "Queries UDP per zone",
+                                    "item_description": "A number of UDP query counts per zone"
+                                    },
+                                {
+                                    "item_name": "queries.tcp",
+                                    "item_type": "integer",
+                                    "item_optional": False,
+                                    "item_default": 0,
+                                    "item_title": "Queries TCP per zone",
+                                    "item_description": "A number of TCP query counts per zone"
+                                    }
+                                ]
+                            }
+                        }]}))
+        self.assertEqual(self.stats.command_showschema(owner='Auth', name='queries.tcp'),
+                         isc.config.create_answer(
+                0, {'Auth': [{
+                    "item_default": 0,
+                    "item_description": "A number of total query counts which all auth servers receive over TCP since they started initially",
+                    "item_name": "queries.tcp",
+                    "item_optional": False,
+                    "item_title": "Queries TCP",
+                    "item_type": "integer"
+                    }]}))
+        self.assertEqual(self.stats.command_showschema(owner='Auth', name='queries.perzone'),
+                         isc.config.create_answer(
+                0, {'Auth':[{
+                    "item_name": "queries.perzone",
+                    "item_type": "list",
+                    "item_optional": False,
+                    "item_default": [
+                        {
+                            "zonename" : "test1.example",
+                            "queries.udp" : 1,
+                            "queries.tcp" : 2
+                            },
+                        {
+                            "zonename" : "test2.example",
+                            "queries.udp" : 3,
+                            "queries.tcp" : 4
+                            }
+                    ],
+                    "item_title": "Queries per zone",
+                    "item_description": "Queries per zone",
+                    "list_item_spec": {
+                        "item_name": "zones",
+                        "item_type": "map",
+                        "item_optional": False,
+                        "item_default": {},
+                        "map_item_spec": [
+                            {
+                                "item_name": "zonename",
+                                "item_type": "string",
+                                "item_optional": False,
+                                "item_default": "",
+                                "item_title": "Zonename",
+                                "item_description": "Zonename"
+                                },
+                            {
+                                "item_name": "queries.udp",
+                                "item_type": "integer",
+                                "item_optional": False,
+                                "item_default": 0,
+                                "item_title": "Queries UDP per zone",
+                                "item_description": "A number of UDP query counts per zone"
+                                },
+                            {
+                                "item_name": "queries.tcp",
+                                "item_type": "integer",
+                                "item_optional": False,
+                                "item_default": 0,
+                                "item_title": "Queries TCP per zone",
+                                "item_description": "A number of TCP query counts per zone"
+                                }
+                            ]
+                         }
+                     }]}))
+        self.assertEqual(self.stats.command_showschema(owner='Auth', name='nds_queries.perzone'),
+                         isc.config.create_answer(
+                0, {'Auth':[{
+                    "item_name": "nds_queries.perzone",
+                    "item_type": "named_set",
+                    "item_optional": False,
+                    "item_default": {
+                        "test10.example" : {
+                            "queries.udp" : 1,
+                            "queries.tcp" : 2
+                        },
+                        "test20.example" : {
+                            "queries.udp" : 3,
+                            "queries.tcp" : 4
+                        }
+                    },
+                    "item_title": "Queries per zone",
+                    "item_description": "Queries per zone",
+                    "named_set_item_spec": {
+                        "item_name": "zonename",
+                        "item_type": "map",
+                        "item_optional": False,
+                        "item_default": {},
+                        "item_title": "Zonename",
+                        "item_description": "Zonename",
+                        "map_item_spec": [
+                            {
+                                "item_name": "queries.udp",
+                                "item_type": "integer",
+                                "item_optional": False,
+                                "item_default": 0,
+                                "item_title": "Queries UDP per zone",
+                                "item_description": "A number of UDP query counts per zone"
+                                },
+                            {
+                                "item_name": "queries.tcp",
+                                "item_type": "integer",
+                                "item_optional": False,
+                                "item_default": 0,
+                                "item_title": "Queries TCP per zone",
+                                "item_description": "A number of TCP query counts per zone"
+                                }
+                            ]
+                        }
+                    }]}))
+
+        self.assertEqual(self.stats.command_showschema(owner='Stats', name='bar'),
+                         isc.config.create_answer(
+                1, "specified arguments are incorrect: owner: Stats, name: bar"))
+        self.assertEqual(self.stats.command_showschema(name='bar'),
+                         isc.config.create_answer(
+                1, "module name is not specified"))
+
+    def test_polling_init(self):
+        """check statistics data of 'Init'."""
+
+        stat = MyStats()
+        stat.update_modules = lambda: None
+        create_answer = isc.config.ccsession.create_answer # shortcut
+
+        stat._answers = [
+            # Answer for "show_processes"
+            (create_answer(0, []),  None),
+            # Answers for "getstats" for Init (type of boot_time is invalid)
+            (create_answer(0, {'boot_time': self.const_datetime}),
+             {'from': 'init'}),
+            ]
+
+        stat.do_polling()
+        self.assertEqual(
+            stat.statistics_data_bymid['Init']['init'],
+            {'boot_time': self.const_datetime})
+
+    def test_polling_consolidate(self):
+        """check statistics data of multiple instances of same module."""
+        stat = MyStats()
+        stat.update_modules = lambda: None
+        create_answer = isc.config.ccsession.create_answer # shortcut
+
+        # Test data borrowed from test_update_statistics_data_withmid
+        stat._answers = [
+            (create_answer(0, [[1034, 'b10-auth-1', 'Auth'],
+                               [1035, 'b10-auth-2', 'Auth']]),  None),
+            (create_answer(0, stat._auth_sdata), {'from': 'auth1'}),
+            (create_answer(0, stat._auth_sdata), {'from': 'auth2'}),
+            (create_answer(0, stat._auth_sdata), {'from': 'auth3'})
+            ]
+
+        stat.do_polling()
+
+        # check statistics data of each 'Auth' instances.  expected data
+        # for 'nds_queries.perzone' is special as it needs data merge.
+        self.assertEqual(2, len(stat.statistics_data_bymid['Auth'].values()))
+        for s in stat.statistics_data_bymid['Auth'].values():
+            self.assertEqual(
+                s, {'queries.perzone': stat._auth_sdata['queries.perzone'],
+                    'nds_queries.perzone': stat._nds_queries_per_zone,
+                    'queries.tcp': stat._auth_sdata['queries.tcp'],
+                    'queries.udp': stat._auth_sdata['queries.udp']})
+
+        # check consolidation of statistics data of the auth instances.
+        # it's union of the reported data and the spec default.
+        n = len(stat.statistics_data_bymid['Auth'].values())
+        self.maxDiff = None
+        self.assertEqual(
+            stat.statistics_data['Auth'],
+            {'queries.perzone': [
+                    {'zonename': 'test1.example',
+                     'queries.tcp': 5 * n,
+                     'queries.udp': 4 * n},
+                    {'zonename': 'test2.example',
+                     'queries.tcp': 4 * n,
+                     'queries.udp': 3 * n},
+                    ],
+             'nds_queries.perzone': {
+                    'test10.example': {
+                        'queries.tcp': 5 * n,
+                        'queries.udp': 4 * n
+                        },
+                    'test20.example': {
+                        'queries.tcp': 4 * n,
+                        'queries.udp': 3 * n
+                        },
+                    },
+             'queries.tcp': 3 * n,
+             'queries.udp': 2 * n})
+
+    def test_polling_stats(self):
+        """Check statistics data of 'Stats'
+
+        This is actually irrelevant to do_polling(), but provided to
+        compatibility of older tests.
+
+        """
+        stat = MyStats()
+        self.assertEqual(len(stat.statistics_data['Stats']), 5)
+        self.assertTrue('boot_time' in stat.statistics_data['Stats'])
+        self.assertTrue('last_update_time' in stat.statistics_data['Stats'])
+        self.assertTrue('report_time' in stat.statistics_data['Stats'])
+        self.assertTrue('timestamp' in stat.statistics_data['Stats'])
+        self.assertEqual(stat.statistics_data['Stats']['lname'],
+                         stat.mccs._session.lname)
+
+    def test_polling2(self):
+        """Test do_polling() doesn't incorporate broken statistics data.
+
+        Actually, this is not a test for do_polling() itself.  It's bad, but
+        fixing that is a subject of different ticket.
+
+        """
+        stat = MyStats()
+        # check default statistics data of 'Init'
+        self.assertEqual(
+             stat.statistics_data['Init'],
+             {'boot_time': self.const_default_datetime})
+
+        # set invalid statistics
+        create_answer = isc.config.ccsession.create_answer # shortcut
+        stat._answers = [
+            # Answer for "show_processes"
+            (create_answer(0, []),  None),
+            # Answers for "getstats" for Init (type of boot_time is invalid)
+            (create_answer(0, {'boot_time': 1}), {'from': 'init'}),
+            ]
+        stat.update_modules = lambda: None
+
+        # do_polling() should ignore the invalid answer;
+        # default data shouldn't be replaced.
+        stat.do_polling()
+        self.assertEqual(
+             stat.statistics_data['Init'],
+             {'boot_time': self.const_default_datetime})
+
+class Z_TestOSEnv(unittest.TestCase):
+    # Running this test would break logging setting.  To prevent it from
+    # affecting other tests we use the same workaround as
+    # Z_TestStatsHttpdError.
+    def test_osenv(self):
+        """
+        test for the environ variable "B10_FROM_SOURCE"
+        "B10_FROM_SOURCE" is set in Makefile
+        """
+        # test case having B10_FROM_SOURCE
+        self.assertTrue("B10_FROM_SOURCE" in os.environ)
+        self.assertEqual(stats.SPECFILE_LOCATION, \
+                             os.environ["B10_FROM_SOURCE"] + os.sep + \
+                             "src" + os.sep + "bin" + os.sep + "stats" + \
+                             os.sep + "stats.spec")
+        # test case not having B10_FROM_SOURCE
+        path = os.environ["B10_FROM_SOURCE"]
+        os.environ.pop("B10_FROM_SOURCE")
+        self.assertFalse("B10_FROM_SOURCE" in os.environ)
+        # import stats again
+        imp.reload(stats)
+        # revert the changes
+        os.environ["B10_FROM_SOURCE"] = path
+        imp.reload(stats)
+
+if __name__ == "__main__":
+    isc.log.resetUnitTestRootLogger()
+    unittest.main()
diff --git a/src/bin/stats/tests/test_utils.py b/src/bin/stats/tests/test_utils.py
index 45e9a17..8886ad2 100644
--- a/src/bin/stats/tests/test_utils.py
+++ b/src/bin/stats/tests/test_utils.py
@@ -20,13 +20,11 @@ Utilities and mock modules for unittests of statistics modules
 import os
 import io
 import time
-import sys
 import threading
-import tempfile
 import json
 import signal
+import socket
 
-import msgq
 import isc.config.cfgmgr
 import stats
 import stats_httpd
@@ -51,19 +49,6 @@ class SignalHandler():
         """invokes unittest.TestCase.fail as a signal handler"""
         self.fail_handler("A deadlock might be detected")
 
-def send_command(command_name, module_name, params=None):
-    cc_session = isc.cc.Session()
-    command = isc.config.ccsession.create_command(command_name, params)
-    seq = cc_session.group_sendmsg(command, module_name)
-    try:
-        (answer, env) = cc_session.group_recvmsg(False, seq)
-        if answer:
-            return isc.config.ccsession.parse_answer(answer)
-    except isc.cc.SessionTimeout:
-        pass
-    finally:
-        cc_session.close()
-
 class ThreadingServerManager:
     def __init__(self, server, *args, **kwargs):
         self.server = server(*args, **kwargs)
@@ -91,45 +76,7 @@ class ThreadingServerManager:
         else:
             self.server._thread.join(0) # timeout is 0
 
-class MockMsgq:
-    def __init__(self):
-        self._started = threading.Event()
-        self.msgq = msgq.MsgQ(verbose=False)
-        result = self.msgq.setup()
-        if result:
-            sys.exit("Error on Msgq startup: %s" % result)
-
-    def run(self):
-        self._started.set()
-        try:
-            self.msgq.run()
-        finally:
-            # Make sure all the sockets, etc, are removed once it stops.
-            self.msgq.shutdown()
-
-    def shutdown(self):
-        # Ask it to terminate nicely
-        self.msgq.stop()
-
-class MockCfgmgr:
-    def __init__(self):
-        self._started = threading.Event()
-        self.cfgmgr = isc.config.cfgmgr.ConfigManager(
-            os.environ['CONFIG_TESTDATA_PATH'], "b10-config.db")
-        self.cfgmgr.read_config()
-
-    def run(self):
-        self._started.set()
-        try:
-            self.cfgmgr.run()
-        except Exception:
-            pass
-
-    def shutdown(self):
-        self.cfgmgr.running = False
-
-class MockInit:
-    spec_str = """\
+INIT_SPEC_STR = """\
 {
   "module_spec": {
     "module_name": "Init",
@@ -221,56 +168,12 @@ class MockInit:
   }
 }
 """
-    _BASETIME = CONST_BASETIME
 
-    def __init__(self):
-        self._started = threading.Event()
-        self.running = False
-        self.spec_file = io.StringIO(self.spec_str)
-        # create ModuleCCSession object
-        self.mccs = isc.config.ModuleCCSession(
-            self.spec_file,
-            self.config_handler,
-            self.command_handler)
-        self.spec_file.close()
-        self.cc_session = self.mccs._session
-        self.got_command_name = ''
-        self.pid_list = [[ 9999, "b10-auth", "Auth" ],
-                         [ 9998, "b10-auth-2", "Auth" ]]
-        self.statistics_data = {
-            'boot_time': time.strftime('%Y-%m-%dT%H:%M:%SZ', self._BASETIME)
-            }
-
-    def run(self):
-        self.mccs.start()
-        self.running = True
-        self._started.set()
-        try:
-            while self.running:
-                self.mccs.check_command(False)
-        except Exception:
-            pass
-
-    def shutdown(self):
-        self.running = False
-
-    def config_handler(self, new_config):
-        return isc.config.create_answer(0)
-
-    def command_handler(self, command, *args, **kwargs):
-        self._started.set()
-        self.got_command_name = command
-        sdata = self.statistics_data
-        if command == 'getstats':
-            return isc.config.create_answer(0, sdata)
-        elif command == 'show_processes':
-            # Return dummy pids
-            return isc.config.create_answer(
-                0, self.pid_list)
-        return isc.config.create_answer(1, "Unknown Command")
-
-class MockAuth:
-    spec_str = """\
+# Note: this is derived of the spec for the DNS authoritative server, but
+# for the purpose of this test, it's completely irrelevant to DNS.
+# Some statisittics specs do not make sense for practical sense but used
+# just cover various types of statistics data (list, map/dict, etc).
+AUTH_SPEC_STR = """\
 {
   "module_spec": {
     "module_name": "Auth",
@@ -392,68 +295,6 @@ class MockAuth:
   }
 }
 """
-    def __init__(self):
-        self._started = threading.Event()
-        self.running = False
-        self.spec_file = io.StringIO(self.spec_str)
-        # create ModuleCCSession object
-        self.mccs = isc.config.ModuleCCSession(
-            self.spec_file,
-            self.config_handler,
-            self.command_handler)
-        self.spec_file.close()
-        self.cc_session = self.mccs._session
-        self.got_command_name = ''
-        self.queries_tcp = 3
-        self.queries_udp = 2
-        self.queries_per_zone = [{
-                'zonename': 'test1.example',
-                'queries.tcp': 5,
-                'queries.udp': 4
-                }]
-        self.nds_queries_per_zone = {
-            'test10.example': {
-                'queries.tcp': 5,
-                'queries.udp': 4
-                }
-            }
-
-    def run(self):
-        self.mccs.start()
-        self.running = True
-        self._started.set()
-        try:
-            while self.running:
-                self.mccs.check_command(False)
-        except Exception:
-            pass
-
-    def shutdown(self):
-        self.running = False
-
-    def config_handler(self, new_config):
-        return isc.config.create_answer(0)
-
-    def command_handler(self, command, *args, **kwargs):
-        self.got_command_name = command
-        sdata = { 'queries.tcp': self.queries_tcp,
-                  'queries.udp': self.queries_udp,
-                  'queries.perzone' : self.queries_per_zone,
-                  'nds_queries.perzone' : {
-                    'test10.example': {
-                    'queries.tcp': \
-                      isc.cc.data.find(
-                        self.nds_queries_per_zone,
-                        'test10.example/queries.tcp')
-                    }
-                  },
-                  'nds_queries.perzone/test10.example/queries.udp' :
-                      isc.cc.data.find(self.nds_queries_per_zone,
-                                       'test10.example/queries.udp')
-                }
-        if command == 'getstats':
-            return isc.config.create_answer(0, sdata)
-        return isc.config.create_answer(1, "Unknown Command")
 
 class MyModuleCCSession(isc.config.ConfigData):
     """Mocked ModuleCCSession class.
@@ -468,6 +309,7 @@ class MyModuleCCSession(isc.config.ConfigData):
         isc.config.ConfigData.__init__(self, module_spec)
         self._session = self
         self.stopped = False
+        self.closed = False
         self.lname = 'mock_mod_ccs'
 
     def start(self):
@@ -476,10 +318,13 @@ class MyModuleCCSession(isc.config.ConfigData):
     def send_stopping(self):
         self.stopped = True     # just record it's called to inspect it later
 
-class SimpleStats(stats.Stats):
+    def close(self):
+        self.closed = True
+
+class MyStats(stats.Stats):
     """A faked Stats class for unit tests.
 
-    This class inherits most of the real Stats class, but replace the
+    This class inherits most of the real Stats class, but replaces the
     ModuleCCSession with a fake one so we can avoid network I/O in tests,
     and can also inspect or tweak messages via the session more easily.
     This class also maintains some faked module information and statistics
@@ -500,9 +345,9 @@ class SimpleStats(stats.Stats):
         # the default answer from faked recvmsg if _answers is empty
         self.__default_answer = isc.config.ccsession.create_answer(
             0, {'Init':
-                    json.loads(MockInit.spec_str)['module_spec']['statistics'],
+                    json.loads(INIT_SPEC_STR)['module_spec']['statistics'],
                 'Auth':
-                    json.loads(MockAuth.spec_str)['module_spec']['statistics']
+                    json.loads(AUTH_SPEC_STR)['module_spec']['statistics']
                 })
         # setup faked auth statistics
         self.__init_auth_stat()
@@ -530,24 +375,24 @@ class SimpleStats(stats.Stats):
     def __init_auth_stat(self):
         self._queries_tcp = 3
         self._queries_udp = 2
-        self.__queries_per_zone = [{
+        self._queries_per_zone = [{
                 'zonename': 'test1.example', 'queries.tcp': 5, 'queries.udp': 4
                 }]
-        self.__nds_queries_per_zone = \
+        self._nds_queries_per_zone = \
             { 'test10.example': { 'queries.tcp': 5, 'queries.udp': 4 } }
         self._auth_sdata = \
             { 'queries.tcp': self._queries_tcp,
               'queries.udp': self._queries_udp,
-              'queries.perzone' : self.__queries_per_zone,
+              'queries.perzone' : self._queries_per_zone,
               'nds_queries.perzone' : {
                 'test10.example': {
                     'queries.tcp': isc.cc.data.find(
-                        self.__nds_queries_per_zone,
+                        self._nds_queries_per_zone,
                         'test10.example/queries.tcp')
                     }
                 },
               'nds_queries.perzone/test10.example/queries.udp' :
-                  isc.cc.data.find(self.__nds_queries_per_zone,
+                  isc.cc.data.find(self._nds_queries_per_zone,
                                    'test10.example/queries.udp')
               }
 
@@ -589,32 +434,62 @@ class SimpleStats(stats.Stats):
         answer, _ = self.__group_recvmsg(None, None)
         return isc.config.ccsession.parse_answer(answer)[1]
 
-class MyStats(stats.Stats):
-
-    stats._BASETIME = CONST_BASETIME
-    stats.get_timestamp = lambda: time.mktime(CONST_BASETIME)
-    stats.get_datetime = lambda x=None: time.strftime("%Y-%m-%dT%H:%M:%SZ", CONST_BASETIME)
-
-    def __init__(self):
-        self._started = threading.Event()
-        stats.Stats.__init__(self)
+class MyStatsHttpd(stats_httpd.StatsHttpd):
+    """A faked StatsHttpd class for unit tests.
 
-    def run(self):
-        self._started.set()
-        try:
-            self.start()
-        except Exception:
-            pass
+    This class inherits most of the real StatsHttpd class, but replaces the
+    ModuleCCSession with a fake one so we can avoid network I/O in tests,
+    and can also inspect or tweak messages via the session more easily.
 
-    def shutdown(self):
-        self.command_shutdown()
+    """
 
-class MyStatsHttpd(stats_httpd.StatsHttpd):
     ORIG_SPECFILE_LOCATION = stats_httpd.SPECFILE_LOCATION
     def __init__(self, *server_address):
         self._started = threading.Event()
+        self.__dummy_sock = None # see below
+
+        # Prepare commonly used statistics schema and data requested in
+        # stats-httpd tests.  For the purpose of these tests, the content of
+        # statistics data is not so important (they don't test whther the
+        # counter values are correct, etc), so hardcoding the common case
+        # should suffice.  Note also that some of the statistics values and
+        # specs don't make sense in practice (see also comments on
+        # AUTH_SPEC_STR).
+        with open(stats.SPECFILE_LOCATION) as f:
+            stat_spec_str = f.read()
+        self.__default_spec_answer = {
+            'Init': json.loads(INIT_SPEC_STR)['module_spec']['statistics'],
+            'Auth': json.loads(AUTH_SPEC_STR)['module_spec']['statistics'],
+            'Stats': json.loads(stat_spec_str)['module_spec']['statistics']
+            }
+        self.__default_data_answer = {
+            'Init': {'boot_time':
+                         time.strftime('%Y-%m-%dT%H:%M:%SZ', CONST_BASETIME)},
+            'Stats': {'last_update_time':
+                          time.strftime('%Y-%m-%dT%H:%M:%SZ', CONST_BASETIME),
+                      'report_time':
+                          time.strftime('%Y-%m-%dT%H:%M:%SZ', CONST_BASETIME),
+                      'lname': 'test-lname',
+                      'boot_time':
+                          time.strftime('%Y-%m-%dT%H:%M:%SZ', CONST_BASETIME),
+                      'timestamp': time.mktime(CONST_BASETIME)},
+            'Auth': {'queries.udp': 4, 'queries.tcp': 6,
+                     'queries.perzone': [
+                    {'queries.udp': 8, 'queries.tcp': 10,
+                     'zonename': 'test1.example'},
+                    {'queries.udp': 6, 'queries.tcp': 8,
+                     'zonename': 'test2.example'}],
+                     'nds_queries.perzone': {
+                    'test10.example': {'queries.udp': 8, 'queries.tcp': 10},
+                    'test20.example': {'queries.udp': 6, 'queries.tcp': 8}}}}
+
+        # if set, use them as faked response to rpc_call (see below).
+        # it's a list of answer data of rpc_call.
+        self._rpc_answers = []
+
         if server_address:
-            stats_httpd.SPECFILE_LOCATION = self.create_specfile(*server_address)
+            stats_httpd.SPECFILE_LOCATION = \
+                self.__create_specfile(*server_address)
             try:
                 stats_httpd.StatsHttpd.__init__(self)
             finally:
@@ -624,7 +499,51 @@ class MyStatsHttpd(stats_httpd.StatsHttpd):
         else:
             stats_httpd.StatsHttpd.__init__(self)
 
-    def create_specfile(self, *server_address):
+        # replace some (faked) ModuleCCSession methods so we can inspect/fake.
+        # in order to satisfy select.select() we need some real socket.  We
+        # use an unusable AF_UNIX socket; we won't actually use it for
+        # communication.
+        self.cc_session.rpc_call = self.__rpc_call
+        self.__dummy_sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+        self.mccs.get_socket = lambda: self.__dummy_sock
+
+    def open_mccs(self):
+        self.mccs = MyModuleCCSession(stats_httpd.SPECFILE_LOCATION,
+                                      self.config_handler,
+                                      self.command_handler)
+        self.cc_session = self.mccs._session
+        self.mccs.start = self.load_config # force reload
+
+    def close_mccs(self):
+        super().close_mccs()
+        if self.__dummy_sock is not None:
+            self.__dummy_sock.close()
+            self.__dummy_sock = None
+
+    def __rpc_call(self, command, group, params={}):
+        """Faked ModuleCCSession.rpc_call for tests.
+
+        The stats httpd module only issues two commands: 'showschema' and
+        'show'.  In most cases we can simply use the prepared default
+        answer.  If customization is needed, the test case can add a
+        faked answer by appending it to _rpc_answers.  If the added object
+        is of Exception type this method raises it instead of return it,
+        emulating the situation where rpc_call() results in an exception.
+
+        """
+        if len(self._rpc_answers) == 0:
+            if command == 'showschema':
+                return self.__default_spec_answer
+            elif command == 'show':
+                return self.__default_data_answer
+            assert False, "unexpected command for faked rpc_call: " + command
+
+        answer = self._rpc_answers.pop(0)
+        if issubclass(type(answer), Exception):
+            raise answer
+        return answer
+
+    def __create_specfile(self, *server_address):
         spec_io = open(self.ORIG_SPECFILE_LOCATION)
         try:
             spec = json.load(spec_io)
@@ -633,7 +552,8 @@ class MyStatsHttpd(stats_httpd.StatsHttpd):
             for i in range(len(config)):
                 if config[i]['item_name'] == 'listen_on':
                     config[i]['item_default'] = \
-                        [ dict(address=a[0], port=a[1]) for a in server_address ]
+                        [ dict(address=a[0], port=a[1])
+                          for a in server_address ]
                     break
             return io.StringIO(json.dumps(spec))
         finally:
@@ -641,53 +561,4 @@ class MyStatsHttpd(stats_httpd.StatsHttpd):
 
     def run(self):
         self._started.set()
-        try:
-            self.start()
-        except Exception:
-            pass
-
-    def shutdown(self):
-        self.command_handler('shutdown', None)
-
-class BaseModules:
-    def __init__(self):
-        # MockMsgq
-        self.msgq = ThreadingServerManager(MockMsgq)
-        self.msgq.run()
-        # Check whether msgq is ready. A SessionTimeout is raised here if not.
-        isc.cc.session.Session().close()
-        # MockCfgmgr
-        self.cfgmgr = ThreadingServerManager(MockCfgmgr)
-        self.cfgmgr.run()
-        # MockInit
-        self.b10_init = ThreadingServerManager(MockInit)
-        self.b10_init.run()
-        # MockAuth
-        self.auth = ThreadingServerManager(MockAuth)
-        self.auth.run()
-        self.auth2 = ThreadingServerManager(MockAuth)
-        self.auth2.run()
-
-
-    def shutdown(self):
-        # MockMsgq. We need to wait (blocking) for it, otherwise it'll wipe out
-        # a socket for another test during its shutdown.
-        self.msgq.shutdown(True)
-
-        # We also wait for the others, but these are just so we don't create
-        # too many threads in parallel.
-
-        # MockAuth
-        self.auth2.shutdown(True)
-        self.auth.shutdown(True)
-        # MockInit
-        self.b10_init.shutdown(True)
-        # MockCfgmgr
-        self.cfgmgr.shutdown(True)
-        # remove the unused socket file
-        socket_file = self.msgq.server.msgq.socket_file
-        try:
-            if os.path.exists(socket_file):
-                os.remove(socket_file)
-        except OSError:
-            pass
+        self.start()
diff --git a/src/bin/xfrin/b10-xfrin.xml b/src/bin/xfrin/b10-xfrin.xml
index 588048a..0da7ce0 100644
--- a/src/bin/xfrin/b10-xfrin.xml
+++ b/src/bin/xfrin/b10-xfrin.xml
@@ -213,6 +213,132 @@ operation
 -->
 
   </refsect1>
+  <refsect1>
+    <title>STATISTICS DATA</title>
+
+    <para>
+      The statistics data collected by the <command>b10-xfrin</command>
+      daemon for <quote>Xfrin</quote> include:
+    </para>
+
+    <variablelist>
+
+      <varlistentry>
+        <term>zones</term>
+        <listitem><simpara>
+          A directory name of per-zone statistics
+          </simpara>
+          <variablelist>
+
+            <varlistentry>
+              <term><replaceable>zonename</replaceable></term>
+              <listitem><simpara>
+                An actual zone name or special zone name
+                <quote>_SERVER_</quote> representing the entire server.
+                Zone classes (e.g. IN, CH, and HS) are mixed and counted so
+                far. But these will be distinguished in future release.
+                </simpara>
+                <variablelist>
+
+                  <varlistentry>
+                    <term>soaoutv4</term>
+                    <listitem><simpara>
+                      Number of IPv4 SOA queries sent from Xfrin
+                    </simpara></listitem>
+                  </varlistentry>
+
+                  <varlistentry>
+                    <term>soaoutv6</term>
+                    <listitem><simpara>
+                      Number of IPv6 SOA queries sent from Xfrin
+                    </simpara></listitem>
+                  </varlistentry>
+
+                  <varlistentry>
+                    <term>axfrreqv4</term>
+                    <listitem><simpara>
+                      Number of IPv4 AXFR requests sent from Xfrin
+                    </simpara></listitem>
+                  </varlistentry>
+
+                  <varlistentry>
+                    <term>axfrreqv6</term>
+                    <listitem><simpara>
+                      Number of IPv6 AXFR requests sent from Xfrin
+                    </simpara></listitem>
+                  </varlistentry>
+
+                  <varlistentry>
+                    <term>ixfrreqv4</term>
+                    <listitem><simpara>
+                      Number of IPv4 IXFR requests sent from Xfrin
+                    </simpara></listitem>
+                  </varlistentry>
+
+                  <varlistentry>
+                    <term>ixfrreqv6</term>
+                    <listitem><simpara>
+                      Number of IPv6 IXFR requests sent from Xfrin
+                    </simpara></listitem>
+                  </varlistentry>
+
+                  <varlistentry>
+                    <term>xfrsuccess</term>
+                    <listitem><simpara>
+                      Number of zone transfer requests succeeded.
+                      These include the case where the zone turns
+                      out to be the latest as a result of an
+                      initial SOA query (and there is actually no
+                      AXFR or IXFR transaction).
+                    </simpara></listitem>
+                  </varlistentry>
+
+                  <varlistentry>
+                    <term>xfrfail</term>
+                    <listitem><simpara>
+                      Number of zone transfer requests failed
+                    </simpara></listitem>
+                  </varlistentry>
+
+                  <varlistentry>
+                    <term>last_axfr_duration</term>
+                    <listitem><simpara>
+                      Duration in seconds of the last successful AXFR.  0.0
+                      means no successful AXFR done or means a successful AXFR
+                      done in less than a microsecond.  If an AXFR is aborted
+                      due to some failure, this duration won't be updated.
+                    </simpara></listitem>
+                  </varlistentry>
+
+                  <varlistentry>
+                    <term>last_ixfr_duration</term>
+                    <listitem><simpara>
+                      Duration in seconds of the last successful IXFR.  0.0
+                      means no successful IXFR done or means a successful IXFR
+                      done in less than a microsecond.  If an IXFR is aborted
+                      due to some failure, this duration won't be updated.
+                    </simpara></listitem>
+                  </varlistentry>
+
+                </variablelist>
+              </listitem>
+            </varlistentry><!-- end of zonename -->
+
+          </variablelist>
+        </listitem>
+      </varlistentry><!-- end of zones -->
+
+    </variablelist>
+
+    <para>
+      In per-zone counters the special zone name <quote>_SERVER_</quote>
+      exists.
+      It doesn't mean a specific zone. It represents the entire server
+      and the counter value of this special zone is the total of the
+      same counter for all zones.
+    </para>
+
+  </refsect1>
 
 <!--
   <refsect1>
diff --git a/src/bin/xfrin/tests/xfrin_test.py b/src/bin/xfrin/tests/xfrin_test.py
index 5759bb1..2305cdb 100644
--- a/src/bin/xfrin/tests/xfrin_test.py
+++ b/src/bin/xfrin/tests/xfrin_test.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009-2011  Internet Systems Consortium.
+# Copyright (C) 2009-2013  Internet Systems Consortium.
 #
 # Permission to use, copy, modify, and distribute this software for any
 # purpose with or without fee is hereby granted, provided that the above
@@ -19,6 +19,7 @@ import shutil
 import socket
 import sys
 import io
+from datetime import datetime
 from isc.testutils.tsigctx_mock import MockTSIGContext
 from isc.testutils.ccsession_mock import MockModuleCCSession
 from isc.testutils.rrset_utils import *
@@ -717,7 +718,7 @@ class TestXfrinConnection(unittest.TestCase):
         self.sock_map = {}
         self.conn = MockXfrinConnection(self.sock_map, TEST_ZONE_NAME,
                                         TEST_RRCLASS, None, threading.Event(),
-                                        TEST_MASTER_IPV4_ADDRINFO)
+                                        self._master_addrinfo)
         self.conn.init_socket()
         self.soa_response_params = {
             'questions': [example_soa_question],
@@ -749,6 +750,10 @@ class TestXfrinConnection(unittest.TestCase):
             os.remove(TEST_DB_FILE)
         xfrin.check_zone = self.__orig_check_zone
 
+    @property
+    def _master_addrinfo(self):
+        return TEST_MASTER_IPV4_ADDRINFO
+
     def __check_zone(self, name, rrclass, rrsets, callbacks):
         '''
         A mock function used instead of dns.check_zone.
@@ -1065,6 +1070,20 @@ class TestAXFR(TestXfrinConnection):
         self.assertRaises(XfrinProtocolError,
                           self.conn._handle_xfrin_responses)
 
+    def test_ipver_str(self):
+        addrs = (((socket.AF_INET, socket.SOCK_STREAM), 'v4'),
+                 ((socket.AF_INET6, socket.SOCK_STREAM), 'v6'),
+                 ((socket.AF_UNIX, socket.SOCK_STREAM), None))
+        for (info, ver) in addrs:
+            c = MockXfrinConnection({}, TEST_ZONE_NAME, RRClass.CH, None,
+                                    threading.Event(), info)
+            c.init_socket()
+            if ver is not None:
+                self.assertEqual(ver, c._get_ipver_str())
+            else:
+                self.assertRaises(ValueError, c._get_ipver_str)
+            c.close()
+
     def test_soacheck(self):
         # we need to defer the creation until we know the QID, which is
         # determined in _check_soa_serial(), so we use response_generator.
@@ -2104,6 +2123,187 @@ class TestXFRSessionWithSQLite3(TestXfrinConnection):
         self.assertFalse(self.record_exist(Name('dns01.example.com'),
                                            RRType.A))
 
+class TestStatisticsXfrinConn(TestXfrinConnection):
+    '''Test class based on TestXfrinConnection and including paramters
+    and methods related to statistics tests'''
+    def setUp(self):
+        super().setUp()
+        # clear all statistics counters before each test
+        self.conn._counters.clear_all()
+        # fake datetime
+        self.__orig_datetime = isc.statistics.counters.datetime
+        self.__orig_start_timer = isc.statistics.counters._start_timer
+        time1 = datetime(2000, 1, 1, 0, 0, 0, 0)
+        time2 = datetime(2000, 1, 1, 0, 0, 0, 1)
+        class FakeDateTime:
+            @classmethod
+            def now(cls): return time2
+        isc.statistics.counters.datetime = FakeDateTime
+        isc.statistics.counters._start_timer = lambda : time1
+        delta = time2 - time1
+        self._const_sec = round(delta.days * 86400 + delta.seconds +
+                                delta.microseconds * 1E-6, 6)
+        # List of statistics counter names and expected initial values
+        self.__name_to_counter = (('axfrreqv4', 0),
+                                 ('axfrreqv6', 0),
+                                 ('ixfrreqv4', 0),
+                                 ('ixfrreqv6', 0),
+                                 ('last_axfr_duration', 0.0),
+                                 ('last_ixfr_duration', 0.0),
+                                 ('soaoutv4', 0),
+                                 ('soaoutv6', 0),
+                                 ('xfrfail', 0),
+                                 ('xfrsuccess', 0))
+        self.__zones = 'zones'
+
+    def tearDown(self):
+        super().tearDown()
+        isc.statistics.counters.datetime = self.__orig_datetime
+        isc.statistics.counters._start_timer = self.__orig_start_timer
+
+    @property
+    def _ipver(self):
+        return 'v4'
+
+    def _check_init_statistics(self):
+        '''checks exception being raised if not incremented statistics
+        counter gotten'''
+        for (name, exp) in self.__name_to_counter:
+            self.assertRaises(isc.cc.data.DataNotFoundError,
+                              self.conn._counters.get, self.__zones,
+                              TEST_ZONE_NAME_STR, name)
+
+    def _check_updated_statistics(self, overwrite):
+        '''checks getting expect values after updating the pairs of
+        statistics counter name and value on to the "overwrite"
+        dictionary'''
+        name2count = dict(self.__name_to_counter)
+        name2count.update(overwrite)
+        for (name, exp) in name2count.items():
+            act = self.conn._counters.get(self.__zones,
+                                          TEST_ZONE_NAME_STR,
+                                          name)
+            msg = '%s is expected %s but actually %s' % (name, exp, act)
+            self.assertEqual(exp, act, msg=msg)
+
+class TestStatisticsXfrinAXFRv4(TestStatisticsXfrinConn):
+    '''Xfrin AXFR tests for IPv4 to check statistics counters'''
+    def test_soaout(self):
+        '''tests that an soaoutv4 or soaoutv6 counter is incremented
+        when an soa query succeeds'''
+        self.conn.response_generator = self._create_soa_response_data
+        self._check_init_statistics()
+        self.assertEqual(self.conn._check_soa_serial(), XFRIN_OK)
+        self._check_updated_statistics({'soaout' + self._ipver: 1})
+
+    def test_axfrreq_xfrsuccess_last_axfr_duration(self):
+        '''tests that axfrreqv4 or axfrreqv6 and xfrsuccess counters
+        and last_axfr_duration timer are incremented when xfr succeeds'''
+        self.conn.response_generator = self._create_normal_response_data
+        self._check_init_statistics()
+        self.assertEqual(self.conn.do_xfrin(False), XFRIN_OK)
+        self._check_updated_statistics({'axfrreq' + self._ipver: 1,
+                                        'xfrsuccess': 1,
+                                        'last_axfr_duration': self._const_sec})
+
+    def test_axfrreq_xfrsuccess_last_axfr_duration2(self):
+        '''tests that axfrreqv4 or axfrreqv6 and xfrsuccess counters
+        and last_axfr_duration timer are incremented when raising
+        XfrinZoneUptodate. The exception is treated as success.'''
+        def exception_raiser():
+            raise XfrinZoneUptodate()
+        self.conn._handle_xfrin_responses = exception_raiser
+        self._check_init_statistics()
+        self.assertEqual(self.conn.do_xfrin(False), XFRIN_OK)
+        self._check_updated_statistics({'axfrreq' + self._ipver: 1,
+                                        'xfrsuccess': 1,
+                                        'last_axfr_duration':
+                                            self._const_sec})
+
+    def test_axfrreq_xfrfail(self):
+        '''tests that axfrreqv4 or axfrreqv6 and xfrfail counters are
+        incremented even if some failure exceptions are expected to be
+        raised inside do_xfrin(): XfrinZoneError, XfrinProtocolError,
+        XfrinException, and Exception'''
+        self._check_init_statistics()
+        count = 0
+        for ex in [XfrinZoneError, XfrinProtocolError, XfrinException,
+                   Exception]:
+            def exception_raiser():
+                raise ex()
+            self.conn._handle_xfrin_responses = exception_raiser
+            self.assertEqual(self.conn.do_xfrin(False), XFRIN_FAIL)
+            count += 1
+            self._check_updated_statistics({'axfrreq' + self._ipver: count,
+                                            'xfrfail': count})
+
+class TestStatisticsXfrinIXFRv4(TestStatisticsXfrinConn):
+    '''Xfrin IXFR tests for IPv4 to check statistics counters'''
+    def test_ixfrreq_xfrsuccess_last_ixfr_duration(self):
+        '''tests that ixfrreqv4 or ixfrreqv6 and xfrsuccess counters
+        and last_ixfr_duration timer are incremented when xfr succeeds'''
+        def create_ixfr_response():
+            self.conn.reply_data = self.conn.create_response_data(
+                questions=[Question(TEST_ZONE_NAME, TEST_RRCLASS,
+                                    RRType.IXFR)],
+                answers=[soa_rrset, begin_soa_rrset, soa_rrset, soa_rrset])
+        self.conn.response_generator = create_ixfr_response
+        self._check_init_statistics()
+        self.assertEqual(XFRIN_OK, self.conn.do_xfrin(False, RRType.IXFR))
+        self._check_updated_statistics({'ixfrreq' + self._ipver: 1,
+                                        'xfrsuccess': 1,
+                                        'last_ixfr_duration':
+                                            self._const_sec})
+
+    def test_ixfrreq_xfrsuccess_last_ixfr_duration2(self):
+        '''tests that ixfrreqv4 or ixfrreqv6 and xfrsuccess counters
+        and last_ixfr_duration timer are incremented when raising
+        XfrinZoneUptodate. The exception is treated as success.'''
+        def exception_raiser():
+            raise XfrinZoneUptodate()
+        self.conn._handle_xfrin_responses = exception_raiser
+        self._check_init_statistics()
+        self.assertEqual(self.conn.do_xfrin(False, RRType.IXFR), XFRIN_OK)
+        self._check_updated_statistics({'ixfrreq' + self._ipver: 1,
+                                        'xfrsuccess': 1,
+                                        'last_ixfr_duration':
+                                            self._const_sec})
+
+    def test_ixfrreq_xfrfail(self):
+        '''tests that ixfrreqv4 or ixfrreqv6 and xfrfail counters are
+        incremented even if some failure exceptions are expected to be
+        raised inside do_xfrin(): XfrinZoneError, XfrinProtocolError,
+        XfrinException, and Exception'''
+        self._check_init_statistics()
+        count = 0
+        for ex in [XfrinZoneError, XfrinProtocolError, XfrinException,
+                   Exception]:
+            def exception_raiser():
+                raise ex()
+            self.conn._handle_xfrin_responses = exception_raiser
+            self.assertEqual(self.conn.do_xfrin(False, RRType.IXFR), XFRIN_FAIL)
+            count += 1
+            self._check_updated_statistics({'ixfrreq' + self._ipver: count,
+                                            'xfrfail': count})
+
+class TestStatisticsXfrinAXFRv6(TestStatisticsXfrinAXFRv4):
+    '''Same tests as TestStatisticsXfrinAXFRv4 for IPv6'''
+    @property
+    def _master_addrinfo(self):
+        return TEST_MASTER_IPV6_ADDRINFO
+    @property
+    def _ipver(self):
+        return 'v6'
+
+class TestStatisticsIXFRv6(TestStatisticsXfrinIXFRv4):
+    '''Same tests as TestStatisticsXfrinIXFRv4 for IPv6'''
+    @property
+    def _master_addrinfo(self):
+        return TEST_MASTER_IPV6_ADDRINFO
+    @property
+    def _ipver(self):
+        return 'v6'
+
 class TestXfrinRecorder(unittest.TestCase):
     def setUp(self):
         self.recorder = XfrinRecorder()
@@ -2512,6 +2712,14 @@ class TestXfrin(unittest.TestCase):
         self.assertEqual(self.xfr.config_handler({'transfers_in': 3})['result'][0], 0)
         self.assertEqual(self.xfr._max_transfers_in, 3)
 
+    def test_command_handler_getstats(self):
+        module_spec = isc.config.module_spec_from_file(
+            xfrin.SPECFILE_LOCATION)
+        ans = isc.config.parse_answer(
+            self.xfr.command_handler("getstats", None))
+        self.assertEqual(0, ans[0])
+        self.assertTrue(module_spec.validate_statistics(False, ans[1]))
+
     def _check_zones_config(self, config_given):
         if 'transfers_in' in config_given:
             self.assertEqual(config_given['transfers_in'],
diff --git a/src/bin/xfrin/xfrin.py.in b/src/bin/xfrin/xfrin.py.in
index 52d230b..2066404 100755
--- a/src/bin/xfrin/xfrin.py.in
+++ b/src/bin/xfrin/xfrin.py.in
@@ -1,6 +1,6 @@
 #!@PYTHON@
 
-# Copyright (C) 2009-2011  Internet Systems Consortium.
+# Copyright (C) 2009-2013  Internet Systems Consortium.
 #
 # Permission to use, copy, modify, and distribute this software for any
 # purpose with or without fee is hereby granted, provided that the above
@@ -28,6 +28,7 @@ import time
 from functools import reduce
 from optparse import OptionParser, OptionValueError
 from isc.config.ccsession import *
+from isc.statistics import Counters
 from isc.notify import notify_out
 import isc.util.process
 from isc.datasrc import DataSourceClient, ZoneFinder
@@ -612,6 +613,7 @@ class XfrinConnection(asyncore.dispatcher):
         # keep a record of this specific transfer to log on success
         # (time, rr/s, etc)
         self._transfer_stats = XfrinTransferStats()
+        self._counters = Counters(SPECFILE_LOCATION)
 
     def init_socket(self):
         '''Initialize the underlyig socket.
@@ -891,6 +893,19 @@ class XfrinConnection(asyncore.dispatcher):
         # All okay, return it
         return soa
 
+    def _get_ipver_str(self):
+        """Returns a 'v4' or 'v6' string representing a IP version
+        depending on the socket family. This is for an internal use
+        only (except for tests). This is supported only for IP sockets.
+        It raises a ValueError exception on other address families.
+
+        """
+        if self.socket.family == socket.AF_INET:
+            return 'v4'
+        elif self.socket.family == socket.AF_INET6:
+            return 'v6'
+        raise ValueError("Invalid address family. "
+                         "This is supported only for IP sockets")
 
     def _check_soa_serial(self):
         '''Send SOA query and compare the local and remote serials.
@@ -902,6 +917,9 @@ class XfrinConnection(asyncore.dispatcher):
         '''
 
         self._send_query(RRType.SOA)
+        # count soaoutv4 or soaoutv6 requests
+        self._counters.inc('zones', self._zone_name.to_text(),
+                           'soaout' + self._get_ipver_str())
         data_len = self._get_request_response(2)
         msg_len = socket.htons(struct.unpack('H', data_len)[0])
         soa_response = self._get_request_response(msg_len)
@@ -931,9 +949,7 @@ class XfrinConnection(asyncore.dispatcher):
         try:
             ret = XFRIN_OK
             self._request_type = request_type
-            # Right now RRType.[IA]XFR().to_text() is 'TYPExxx', so we need
-            # to hardcode here.
-            req_str = 'IXFR' if request_type == RRType.IXFR else 'AXFR'
+            req_str = request_type.to_text()
             if check_soa:
                 self._check_soa_serial()
                 self.close()
@@ -941,7 +957,16 @@ class XfrinConnection(asyncore.dispatcher):
                 if not self.connect_to_master():
                     raise XfrinException('Unable to reconnect to master')
 
+            # start statistics timer
+            # Note: If the timer for the zone is already started but
+            # not yet stopped due to some error, the last start time
+            # is overwritten at this point.
+            self._counters.start_timer('zones', self._zone_name.to_text(),
+                                       'last_' + req_str.lower() + '_duration')
             logger.info(XFRIN_XFR_TRANSFER_STARTED, req_str, self.zone_str())
+            # An AXFR or IXFR is being requested.
+            self._counters.inc('zones', self._zone_name.to_text(),
+                               req_str.lower() + 'req' + self._get_ipver_str())
             self._send_query(self._request_type)
             self.__state = XfrinInitialSOA()
             self._handle_xfrin_responses()
@@ -968,7 +993,6 @@ class XfrinConnection(asyncore.dispatcher):
                             "%.3f" % self._transfer_stats.get_running_time(),
                             "%.f" % self._transfer_stats.get_bytes_per_second()
                            )
-
         except XfrinZoneUptodate:
             # Eventually we'll probably have to treat this case as a trigger
             # of trying another primary server, etc, but for now we treat it
@@ -1004,11 +1028,21 @@ class XfrinConnection(asyncore.dispatcher):
                          self.zone_str(), str(e))
             ret = XFRIN_FAIL
         finally:
+            # A xfrsuccess or xfrfail counter is incremented depending on
+            # the result.
+            result = {XFRIN_OK: 'xfrsuccess', XFRIN_FAIL: 'xfrfail'}[ret]
+            self._counters.inc('zones', self._zone_name.to_text(), result)
+            # The started statistics timer is finally stopped only in
+            # a successful case.
+            if ret == XFRIN_OK:
+                self._counters.stop_timer('zones',
+                                          self._zone_name.to_text(),
+                                          'last_' + req_str.lower() +
+                                          '_duration')
             # Make sure any remaining transaction in the diff is closed
             # (if not yet - possible in case of xfr-level exception) as soon
             # as possible
             self._diff = None
-
         return ret
 
     def _check_response_header(self, msg):
@@ -1339,6 +1373,7 @@ class Xfrin:
         self._cc_setup()
         self.recorder = XfrinRecorder()
         self._shutdown_event = threading.Event()
+        self._counters = Counters(SPECFILE_LOCATION)
 
     def _cc_setup(self):
         '''This method is used only as part of initialization, but is
@@ -1484,6 +1519,7 @@ class Xfrin:
             th.join()
 
     def command_handler(self, command, args):
+        logger.debug(DBG_XFRIN_TRACE, XFRIN_RECEIVED_COMMAND, command)
         answer = create_answer(0)
         try:
             if command == 'shutdown':
@@ -1552,6 +1588,14 @@ class Xfrin:
                                        (False if command == 'retransfer' else True))
                 answer = create_answer(ret[0], ret[1])
 
+            # return statistics data to the stats daemon
+            elif command == "getstats":
+                # The log level is here set to debug in order to avoid
+                # that a log becomes too verbose. Because the
+                # b10-stats daemon is periodically asking to the
+                # b10-xfrin daemon.
+                answer = create_answer(0, self._counters.get_statistics())
+
             else:
                 answer = create_answer(1, 'unknown command: ' + command)
         except XfrinException as err:
diff --git a/src/bin/xfrin/xfrin.spec b/src/bin/xfrin/xfrin.spec
index 7ab1085..dc993f7 100644
--- a/src/bin/xfrin/xfrin.spec
+++ b/src/bin/xfrin/xfrin.spec
@@ -94,6 +94,119 @@
           }
         ]
       }
+    ],
+    "statistics": [
+      {
+        "item_name": "zones",
+        "item_type": "named_set",
+        "item_optional": false,
+        "item_default": {
+          "_SERVER_" : {
+	    "soaoutv4": 0,
+	    "soaoutv6": 0,
+	    "axfrreqv4": 0,
+	    "axfrreqv6": 0,
+	    "ixfrreqv4": 0,
+	    "ixfrreqv6": 0,
+	    "xfrsuccess": 0,
+	    "xfrfail": 0,
+	    "last_ixfr_duration": 0.0,
+	    "last_axfr_duration": 0.0
+          }
+        },
+        "item_title": "Zone names",
+        "item_description": "A directory name of per-zone statistics",
+        "named_set_item_spec": {
+          "item_name": "zonename",
+          "item_type": "map",
+          "item_optional": false,
+          "item_default": {},
+          "item_title": "Zone name",
+          "item_description": "An actual zone name or special zone name _SERVER_ representing the entire server. Zone classes (e.g. IN, CH, and HS) are mixed and counted so far. But these will be distinguished in future release.",
+          "map_item_spec": [
+            {
+              "item_name": "soaoutv4",
+              "item_type": "integer",
+              "item_optional": false,
+              "item_default": 0,
+              "item_title": "SOAOutv4",
+              "item_description": "Number of IPv4 SOA queries sent from Xfrin"
+            },
+            {
+              "item_name": "soaoutv6",
+              "item_type": "integer",
+              "item_optional": false,
+              "item_default": 0,
+              "item_title": "SOAOutv6",
+              "item_description": "Number of IPv6 SOA queries sent from Xfrin"
+            },
+            {
+              "item_name": "axfrreqv4",
+              "item_type": "integer",
+              "item_optional": false,
+              "item_default": 0,
+              "item_title": "AXFRReqv4",
+              "item_description": "Number of IPv4 AXFR requests sent from Xfrin"
+            },
+            {
+              "item_name": "axfrreqv6",
+              "item_type": "integer",
+              "item_optional": false,
+              "item_default": 0,
+              "item_title": "AXFRReqv6",
+              "item_description": "Number of IPv6 AXFR requests sent from Xfrin"
+            },
+            {
+              "item_name": "ixfrreqv4",
+              "item_type": "integer",
+              "item_optional": false,
+              "item_default": 0,
+              "item_title": "IXFRReqv4",
+              "item_description": "Number of IPv4 IXFR requests sent from Xfrin"
+            },
+            {
+              "item_name": "ixfrreqv6",
+              "item_type": "integer",
+              "item_optional": false,
+              "item_default": 0,
+              "item_title": "IXFRReqv6",
+              "item_description": "Number of IPv6 IXFR requests sent from Xfrin"
+            },
+            {
+              "item_name": "xfrsuccess",
+              "item_type": "integer",
+              "item_optional": false,
+              "item_default": 0,
+              "item_title": "XfrSuccess",
+              "item_description": "Number of zone transfer requests succeeded. These include the case where the zone turns out to be the latest as a result of an initial SOA query (and there is actually no AXFR or IXFR transaction)."
+            },
+            {
+              "item_name": "xfrfail",
+              "item_type": "integer",
+              "item_optional": false,
+              "item_default": 0,
+              "item_title": "XfrFail",
+              "item_description": "Number of zone transfer requests failed"
+            },
+            {
+              "item_name": "last_axfr_duration",
+              "item_type": "real",
+              "item_optional": false,
+              "item_default": 0.0,
+              "item_title": "Last AXFR duration",
+              "item_description": "Duration in seconds of the last successful AXFR.  0.0 means no successful AXFR done or means a successful AXFR done in less than a microsecond.  If an AXFR is aborted due to some failure, this duration won't be updated."
+            },
+            {
+              "item_name": "last_ixfr_duration",
+              "item_type": "real",
+              "item_optional": false,
+              "item_default": 0.0,
+              "item_title": "Last IXFR duration",
+              "item_description": "Duration in seconds of the last successful IXFR.  0.0 means no successful IXFR done or means a successful IXFR done in less than a microsecond.  If an IXFR is aborted due to some failure, this duration won't be updated."
+            }
+          ]
+        }
+      }
     ]
   }
 }
diff --git a/src/bin/xfrin/xfrin_messages.mes b/src/bin/xfrin/xfrin_messages.mes
index 1d90b75..eeddee9 100644
--- a/src/bin/xfrin/xfrin_messages.mes
+++ b/src/bin/xfrin/xfrin_messages.mes
@@ -135,6 +135,9 @@ from does not match the master address in the Xfrin configuration. The notify
 is ignored. This may indicate that the configuration for the master is wrong,
 that a wrong machine is sending notifies, or that fake notifies are being sent.
 
+% XFRIN_RECEIVED_COMMAND received command: %1
+The xfrin daemon received a command on the command channel.
+
 % XFRIN_RETRANSFER_UNKNOWN_ZONE got notification to retransfer unknown zone %1
 There was an internal command to retransfer the given zone, but the
 zone is not known to the system. This may indicate that the configuration
diff --git a/src/lib/asiodns/sync_udp_server.cc b/src/lib/asiodns/sync_udp_server.cc
index 9a06691..989484f 100644
--- a/src/lib/asiodns/sync_udp_server.cc
+++ b/src/lib/asiodns/sync_udp_server.cc
@@ -75,6 +75,15 @@ SyncUDPServer::scheduleRead() {
 
 void
 SyncUDPServer::handleRead(const asio::error_code& ec, const size_t length) {
+    // If the server has been stopped, it could even have been destroyed
+    // by the time of this call.  We'll solve this problem in #2946, but
+    // until then we exit as soon as possible without accessing any other
+    // invalidated fields (note that referencing stopped_ is also incorrect,
+    // but experiments showed it often keeps the original value in practice,
+    // so we live with it until the complete fix).
+    if (stopped_) {
+        return;
+    }
     if (ec) {
         using namespace asio::error;
         const asio::error_code::value_type err_val = ec.value();
diff --git a/src/lib/asiodns/sync_udp_server.h b/src/lib/asiodns/sync_udp_server.h
index becbb2e..cabc8bb 100644
--- a/src/lib/asiodns/sync_udp_server.h
+++ b/src/lib/asiodns/sync_udp_server.h
@@ -158,9 +158,22 @@ private:
     asio::error_code ec_;
     // The callback functor for internal asynchronous read event.  This is
     // stateless (and it will be copied in the ASIO library anyway), so
-    // can be const
-    const boost::function<void(const asio::error_code&, size_t)>
-    recv_callback_;
+    // can be const.
+    // SunStudio doesn't like a boost::function object to be passed, so
+    // we use the wrapper class as a workaround.
+    class CallbackWrapper {
+    public:
+        CallbackWrapper(boost::function<void(const asio::error_code&, size_t)>
+                        callback) :
+            callback_(callback)
+        {}
+        void operator()(const asio::error_code& error, size_t len) {
+            callback_(error, len);
+        }
+    private:
+        boost::function<void(const asio::error_code&, size_t)> callback_;
+    };
+    const CallbackWrapper recv_callback_;
 
     // Auxiliary functions
 
diff --git a/src/lib/asiolink/io_service.cc b/src/lib/asiolink/io_service.cc
index 0d791a1..df08316 100644
--- a/src/lib/asiolink/io_service.cc
+++ b/src/lib/asiolink/io_service.cc
@@ -24,6 +24,23 @@
 namespace isc {
 namespace asiolink {
 
+namespace {
+// A trivial wrapper for boost::function.  SunStudio doesn't seem to be capable
+// of handling a boost::function object if directly passed to
+// io_service::post().
+class CallbackWrapper {
+public:
+    CallbackWrapper(const boost::function<void()>& callback) :
+        callback_(callback)
+    {}
+    void operator()() {
+        callback_();
+    }
+private:
+    boost::function<void()> callback_;
+};
+}
+
 class IOServiceImpl {
 private:
     IOServiceImpl(const IOService& source);
@@ -64,7 +81,8 @@ public:
     /// generalized.
     asio::io_service& get_io_service() { return io_service_; };
     void post(const boost::function<void ()>& callback) {
-        io_service_.post(callback);
+        const CallbackWrapper wrapper(callback);
+        io_service_.post(wrapper);
     }
 private:
     asio::io_service io_service_;
diff --git a/src/lib/datasrc/memory/memory_messages.mes b/src/lib/datasrc/memory/memory_messages.mes
index 8580c8e..cf51706 100644
--- a/src/lib/datasrc/memory/memory_messages.mes
+++ b/src/lib/datasrc/memory/memory_messages.mes
@@ -126,13 +126,13 @@ RRset is split into multiple locations is not supported yet.
 Debug information. A zone object for this zone is being searched for in the
 in-memory data source.
 
-% DATASRC_MEMORY_MEM_LOAD_FROM_FILE loading zone '%1/%2' from file '%3'
-Debug information. The content of master file is being loaded into the memory.
-
 % DATASRC_MEMORY_MEM_LOAD_FROM_DATASRC loading zone '%1/%2' from other data source
 Debug information. The content of another  data source is being loaded
 into the memory.
 
+% DATASRC_MEMORY_MEM_LOAD_FROM_FILE loading zone '%1/%2' from file '%3'
+Debug information. The content of master file is being loaded into the memory.
+
 % DATASRC_MEMORY_MEM_NO_NSEC3PARAM NSEC3PARAM is missing for NSEC3-signed zone %1/%2
 The in-memory data source has loaded a zone signed with NSEC3 RRs,
 but it doesn't have a NSEC3PARAM RR at the zone origin.  It's likely that
diff --git a/src/lib/dhcp/libdhcp++.dox b/src/lib/dhcp/libdhcp++.dox
index 194175a..eabc392 100644
--- a/src/lib/dhcp/libdhcp++.dox
+++ b/src/lib/dhcp/libdhcp++.dox
@@ -57,6 +57,53 @@ DHCPv6, but is rarely used in DHCPv4. isc::dhcp::Option::addOption(),
 isc::dhcp::Option::delOption(), isc::dhcp::Option::getOption() can be used
 for that purpose.
 
+ at section libdhcpRelay Relay v6 support in Pkt6
+
+DHCPv6 clients that are not connected to the same link as DHCPv6
+servers need relays to reach the server. Each relay receives a message
+on a client facing interface, encapsulates it into RELAY_MSG option
+and sends as RELAY_FORW message towards the server (or the next relay,
+which is closer to the server). This procedure can be repeated up to
+32 times. Kea is able to support up to 32 relays. Each traversed relay
+may add certain options.  The most obvious example is interface-id
+option, but there may be other options as well. Each relay may add such
+an option, regardless of whether other relays added it before. Thanks
+to encapsulation, those options are separated and it is possible to
+differentiate which relay inserted specific instance of an option.
+
+Interface-id is used to identify a subnet (or interface) the original message
+came from and is used for that purpose on two occasions. First, the server
+uses the interface-id included by the first relay (the one closest to
+the client) to select appropriate subnet for a given request. Server includes
+that interface-id in its copy, when sending data back to the client.
+This will be used by the relay to choose proper interface when forwarding
+response towards the client.
+
+Pkt6 class has a public Pkt6::relay_info_ field, which is of type Pkt6::RelayInfo.
+This is a simple structure that represents the information in each RELAY_FORW
+or RELAY_REPL message. It is important to understand the order in which
+the data appear here. Consider the following network:
+
+\verbatim
+client-------relay1-----relay2-----relay3----server
+\endverbatim
+
+Client will transmit SOLICIT message. Relay1 will forward it as
+RELAY_FORW with SOLICIT in it. Relay2 forward it as RELAY_FORW with
+RELAY_FORW with SOLICIT in it. Finally the third relay will add yet
+another RELAY_FORW around it. The server will parse the packet and
+create Pkt6 object for it. Its relay_info_ will have 3
+elements. Packet parsing is done in reverse order, compare to the
+order the packet traversed in the network.  The first element
+(relay_info_[0]) will represent relay3 information (the "last" relay or
+in other words the one closest to the server). The second element
+will represent relay2. The third element (relay_info_[2]) will represent
+the first relay (relay1) or in other words the one closest to the client.
+
+Packets sent by the server must maintain the same encapsulation order.
+This is easy to do - just copy data from client's message object into
+server's response object. See Pkt6::coyRelayInfo for details.
+
 @section libdhcpIfaceMgr Interface Manager
 
 Interface Manager (or IfaceMgr) is an abstraction layer about low-level
diff --git a/src/lib/dhcp/pkt6.cc b/src/lib/dhcp/pkt6.cc
index c97281e..27c8ca6 100644
--- a/src/lib/dhcp/pkt6.cc
+++ b/src/lib/dhcp/pkt6.cc
@@ -72,6 +72,62 @@ uint16_t Pkt6::len() {
     }
 }
 
+OptionPtr Pkt6::getAnyRelayOption(uint16_t opt_type, RelaySearchOrder order) {
+
+    if (relay_info_.empty()) {
+        // There's no relay info, this is a direct message
+        return (OptionPtr());
+    }
+
+    int start = 0; // First relay to check
+    int end = 0;   // Last relay to check
+    int direction = 0; // How we going to iterate: forward or backward?
+
+    switch (order) {
+    case RELAY_SEARCH_FROM_CLIENT:
+        // Search backwards
+        start = relay_info_.size() - 1;
+        end = 0;
+        direction = -1;
+        break;
+    case RELAY_SEARCH_FROM_SERVER:
+        // Search forward
+        start = 0;
+        end = relay_info_.size() - 1;
+        direction = 1;
+        break;
+    case RELAY_GET_FIRST:
+        // Look at the innermost relay only
+        start = relay_info_.size() - 1;
+        end = start;
+        direction = 1;
+        break;
+    case RELAY_GET_LAST:
+        // Look at the outermost relay only
+        start = 0;
+        end = 0;
+        direction = 1;
+    }
+
+    // This is a tricky loop. It must go from start to end, but it must work in
+    // both directions (start > end; or start < end). We can't use regular
+    // exit condition, because we don't know whether to use i <= end or i >= end.
+    // That's why we check if in the next iteration we would go past the
+    // list (end + direction). It is similar to STL concept of end pointing
+    // to a place after the last element
+    for (int i = start; i != end + direction; i += direction) {
+        OptionPtr opt = getRelayOption(opt_type, i);
+        if (opt) {
+            return (opt);
+        }
+    }
+
+    // We iterated over specified relays and haven't found what we were
+    // looking for
+    return (OptionPtr());
+}
+
+
 OptionPtr Pkt6::getRelayOption(uint16_t opt_type, uint8_t relay_level) {
     if (relay_level >= relay_info_.size()) {
         isc_throw(OutOfRange, "This message was relayed " << relay_info_.size() << " time(s)."
@@ -483,5 +539,33 @@ const char* Pkt6::getName() const {
     return (getName(getType()));
 }
 
+void Pkt6::copyRelayInfo(const Pkt6Ptr& question) {
+
+    // We use index rather than iterator, because we need that as a parameter
+    // passed to getRelayOption()
+    for (int i = 0; i < question->relay_info_.size(); ++i) {
+        RelayInfo info;
+        info.msg_type_ = DHCPV6_RELAY_REPL;
+        info.hop_count_ = question->relay_info_[i].hop_count_;
+        info.linkaddr_ = question->relay_info_[i].linkaddr_;
+        info.peeraddr_ = question->relay_info_[i].peeraddr_;
+
+        // Is there an interface-id option in this nesting level?
+        // If there is, we need to echo it back
+        OptionPtr opt = question->getRelayOption(D6O_INTERFACE_ID, i);
+        // taken from question->RelayInfo_[i].options_
+        if (opt) {
+            info.options_.insert(make_pair(opt->getType(), opt));
+        }
+
+        /// @todo: Implement support for ERO (Echo Request Option, RFC4994)
+
+        // Add this relay-forw info (client's message) to our relay-repl
+        // message (server's response)
+        relay_info_.push_back(info);
+    }
+}
+
+
 } // end of isc::dhcp namespace
 } // end of isc namespace
diff --git a/src/lib/dhcp/pkt6.h b/src/lib/dhcp/pkt6.h
index 0bf4192..c65142e 100644
--- a/src/lib/dhcp/pkt6.h
+++ b/src/lib/dhcp/pkt6.h
@@ -30,6 +30,9 @@ namespace isc {
 
 namespace dhcp {
 
+class Pkt6;
+typedef boost::shared_ptr<Pkt6> Pkt6Ptr;
+
 class Pkt6 {
 public:
     /// specifies non-relayed DHCPv6 packet header length (over UDP)
@@ -44,6 +47,28 @@ public:
         TCP = 1  // there are TCP DHCPv6 packets (bulk leasequery, failover)
     };
 
+    /// @brief defines relay search pattern
+    ///
+    /// Defines order in which options are searched in a message that
+    /// passed through mulitple relays. RELAY_SEACH_FROM_CLIENT will
+    /// start search from the relay that was the closest to the client
+    /// (i.e. innermost in the encapsulated message, which also means
+    /// this was the first relay that forwarded packet received by the
+    /// server and this will be the last relay that will handle the
+    /// response that server sent towards the client.).
+    /// RELAY_SEARCH_FROM_SERVER is the opposite. This will be the
+    /// relay closest to the server (i.e. outermost in the encapsulated
+    /// message, which also means it was the last relay that relayed
+    /// the received message and will be the first one to process
+    /// server's response). RELAY_GET_FIRST will try to get option from
+    /// the first relay only (closest to the client), RELAY_GET_LAST will
+    /// try to get option form the the last relay (closest to the server).
+    enum RelaySearchOrder {
+        RELAY_SEARCH_FROM_CLIENT = 1,
+        RELAY_SEARCH_FROM_SERVER = 2,
+        RELAY_GET_FIRST = 3,
+        RELAY_GET_LAST = 4
+    };
 
     /// @brief structure that describes a single relay information
     ///
@@ -201,6 +226,18 @@ public:
     /// @return pointer to the option (or NULL if there is no such option)
     OptionPtr getRelayOption(uint16_t option_code, uint8_t nesting_level);
 
+    /// @brief Return first instance of a specified option
+    ///
+    /// When a client's packet traverses multiple relays, each passing relay may
+    /// insert extra options. This method allows the specific instance of a given
+    /// option to be obtained (e.g. closest to the client, closest to the server,
+    /// etc.) See @ref RelaySearchOrder for a detailed description.
+    ///
+    /// @param option_code searched option
+    /// @param order option search order (see @ref RelaySearchOrder)
+    /// @return option pointer (or NULL if no option matches specified criteria)
+    OptionPtr getAnyRelayOption(uint16_t option_code, RelaySearchOrder order);
+
     /// @brief Returns all instances of specified type.
     ///
     /// Returns all instances of options of the specified type. DHCPv6 protocol
@@ -356,6 +393,14 @@ public:
     ///         be freed by the caller.
     const char* getName() const;
 
+    /// @brief copies relay information from client's packet to server's response
+    ///
+    /// This information is not simply copied over. Some parameter are
+    /// removed, msg_type_is updated (RELAY-FORW => RELAY-REPL), etc.
+    ///
+    /// @param question client's packet
+    void copyRelayInfo(const Pkt6Ptr& question);
+
     /// relay information
     ///
     /// this is a public field. Otherwise we hit one of the two problems:
@@ -494,8 +539,6 @@ protected:
     boost::posix_time::ptime timestamp_;
 }; // Pkt6 class
 
-typedef boost::shared_ptr<Pkt6> Pkt6Ptr;
-
 } // isc::dhcp namespace
 
 } // isc namespace
diff --git a/src/lib/dhcp/tests/iface_mgr_unittest.cc b/src/lib/dhcp/tests/iface_mgr_unittest.cc
index 31b9300..ede7abf 100644
--- a/src/lib/dhcp/tests/iface_mgr_unittest.cc
+++ b/src/lib/dhcp/tests/iface_mgr_unittest.cc
@@ -34,6 +34,7 @@ using namespace std;
 using namespace isc;
 using namespace isc::asiolink;
 using namespace isc::dhcp;
+using boost::scoped_ptr;
 
 namespace {
 
@@ -45,11 +46,10 @@ char LOOPBACK[BUF_SIZE] = "lo";
 const uint16_t PORT1 = 10547;   // V6 socket
 const uint16_t PORT2 = 10548;   // V4 socket
 
-// On some systems measured duration of receive6() and
-// receive4() appears to be shorter than select() timeout.
-// called by these functions. This may be the case
-// if different ime resolutions are used by these functions.
-// For such cases we set the tolerance of 0.01s.
+// On some systems measured duration of receive6() and receive4() appears to be
+// shorter than select() timeout.  This may be the case if different time
+// resolutions are used by these functions.  For such cases we set the
+// tolerance to 0.01s.
 const uint32_t TIMEOUT_TOLERANCE = 10000;
 
 /// Mock object implementing PktFilter class.  It is used by
@@ -101,16 +101,16 @@ public:
 };
 
 class NakedIfaceMgr: public IfaceMgr {
-    // "naked" Interface Manager, exposes internal fields
+    // "Naked" Interface Manager, exposes internal fields
 public:
     NakedIfaceMgr() { }
     IfaceCollection & getIfacesLst() { return ifaces_; }
 };
 
-// dummy class for now, but this will be expanded when needed
+// Dummy class for now, but this will be expanded when needed
 class IfaceMgrTest : public ::testing::Test {
 public:
-    // these are empty for now, but let's keep them around
+    // These are empty for now, but let's keep them around
     IfaceMgrTest() {
     }
 
@@ -119,18 +119,17 @@ public:
 
 };
 
-// We need some known interface to work reliably. Loopback interface
-// is named lo on Linux and lo0 on BSD boxes. We need to find out
-// which is available. This is not a real test, but rather a workaround
-// that will go away when interface detection is implemented.
+// We need some known interface to work reliably. Loopback interface is named
+// lo on Linux and lo0 on BSD boxes. We need to find out which is available.
+// This is not a real test, but rather a workaround that will go away when
+// interface detection is implemented.
 
 // NOTE: At this stage of development, write access to current directory
 // during running tests is required.
 TEST_F(IfaceMgrTest, loDetect) {
 
-    // poor man's interface detection
-    // it will go away as soon as proper interface detection
-    // is implemented
+    // Poor man's interface detection.  It will go away as soon as proper
+    // interface detection is implemented
     if (if_nametoindex("lo") > 0) {
         snprintf(LOOPBACK, BUF_SIZE - 1, "lo");
     } else if (if_nametoindex("lo0") > 0) {
@@ -151,7 +150,7 @@ TEST_F(IfaceMgrTest, loDetect) {
 
 #if 0
 TEST_F(IfaceMgrTest, dhcp6Sniffer) {
-    // testing socket operation in a portable way is tricky
+    // Testing socket operation in a portable way is tricky
     // without interface detection implemented
 
     unlink("interfaces.txt");
@@ -160,13 +159,13 @@ TEST_F(IfaceMgrTest, dhcp6Sniffer) {
     interfaces << "eth0 fe80::21e:8cff:fe9b:7349";
     interfaces.close();
 
-    NakedIfaceMgr* ifacemgr = new NakedIfaceMgr();
+    boost::scoped_ptr<NakedIfaceMgr> ifacemgr = new NakedIfaceMgr();
 
-    Pkt6* pkt = NULL;
+    Pkt6Ptr pkt;
     int cnt = 0;
     cout << "---8X-----------------------------------------" << endl;
     while (true) {
-        pkt = ifacemgr->receive();
+        pkt.reset(ifacemgr->receive());
 
         cout << "// this code is autogenerated. Do NOT edit." << endl;
         cout << "// Received " << pkt->data_len_ << " bytes packet:" << endl;
@@ -194,31 +193,26 @@ TEST_F(IfaceMgrTest, dhcp6Sniffer) {
         cout << "    return (pkt);" << endl;
         cout << "}" << endl << endl;
 
-        delete pkt;
+        pkt.reset();
     }
     cout << "---8X-----------------------------------------" << endl;
 
-    // never happens. Infinite loop is infinite
-    delete pkt;
-    delete ifacemgr;
+    // Never happens. Infinite loop is infinite
 }
 #endif
 
 TEST_F(IfaceMgrTest, basic) {
-    // checks that IfaceManager can be instantiated
+    // Checks that IfaceManager can be instantiated
 
     IfaceMgr & ifacemgr = IfaceMgr::instance();
     ASSERT_TRUE(&ifacemgr != 0);
 }
 
 TEST_F(IfaceMgrTest, ifaceClass) {
-    // basic tests for Iface inner class
+    // Basic tests for Iface inner class
 
-    Iface* iface = new Iface("eth5", 7);
-
-    EXPECT_STREQ("eth5/7", iface->getFullName().c_str());
-
-    delete iface;
+    Iface iface("eth5", 7);
+    EXPECT_STREQ("eth5/7", iface.getFullName().c_str());
 }
 
 // TODO: Implement getPlainMac() test as soon as interface detection
@@ -226,9 +220,9 @@ TEST_F(IfaceMgrTest, ifaceClass) {
 TEST_F(IfaceMgrTest, getIface) {
 
     cout << "Interface checks. Please ignore socket binding errors." << endl;
-    NakedIfaceMgr* ifacemgr = new NakedIfaceMgr();
+    scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
 
-    // interface name, ifindex
+    // Interface name, ifindex
     Iface iface1("lo1", 100);
     Iface iface2("eth9", 101);
     Iface iface3("en3", 102);
@@ -237,7 +231,7 @@ TEST_F(IfaceMgrTest, getIface) {
          << " in the tested system and there are no lo1, eth9, en3, e1000g4"
          << " or wifi15 interfaces present." << endl;
 
-    // note: real interfaces may be detected as well
+    // Note: real interfaces may be detected as well
     ifacemgr->getIfacesLst().push_back(iface1);
     ifacemgr->getIfacesLst().push_back(iface2);
     ifacemgr->getIfacesLst().push_back(iface3);
@@ -252,25 +246,22 @@ TEST_F(IfaceMgrTest, getIface) {
     }
 
 
-    // check that interface can be retrieved by ifindex
+    // Check that interface can be retrieved by ifindex
     Iface* tmp = ifacemgr->getIface(102);
     ASSERT_TRUE(tmp != NULL);
 
     EXPECT_EQ("en3", tmp->getName());
     EXPECT_EQ(102, tmp->getIndex());
 
-    // check that interface can be retrieved by name
+    // Check that interface can be retrieved by name
     tmp = ifacemgr->getIface("lo1");
     ASSERT_TRUE(tmp != NULL);
 
     EXPECT_EQ("lo1", tmp->getName());
     EXPECT_EQ(100, tmp->getIndex());
 
-    // check that non-existing interfaces are not returned
+    // Check that non-existing interfaces are not returned
     EXPECT_EQ(static_cast<void*>(NULL), ifacemgr->getIface("wifi15") );
-
-    delete ifacemgr;
-
 }
 
 TEST_F(IfaceMgrTest, receiveTimeout6) {
@@ -279,7 +270,7 @@ TEST_F(IfaceMgrTest, receiveTimeout6) {
               << " Test will block for a few seconds when waiting"
               << " for timeout to occur." << std::endl;
 
-    boost::scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+    scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
     // Open socket on the lo interface.
     IOAddress loAddr("::1");
     int socket1 = 0;
@@ -331,7 +322,7 @@ TEST_F(IfaceMgrTest, receiveTimeout4) {
               << " Test will block for a few seconds when waiting"
               << " for timeout to occur." << std::endl;
 
-    boost::scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+    scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
     // Open socket on the lo interface.
     IOAddress loAddr("127.0.0.1");
     int socket1 = 0;
@@ -378,12 +369,12 @@ TEST_F(IfaceMgrTest, receiveTimeout4) {
 }
 
 TEST_F(IfaceMgrTest, multipleSockets) {
-    boost::scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+    scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
 
-    // container for initialized socket descriptors
+    // Container for initialized socket descriptors
     std::list<uint16_t> init_sockets;
 
-    // create socket #1
+    // Create socket #1
     int socket1 = 0;
     ASSERT_NO_THROW(
         socket1 = ifacemgr->openSocketFromIface(LOOPBACK, PORT1, AF_INET);
@@ -391,7 +382,7 @@ TEST_F(IfaceMgrTest, multipleSockets) {
     ASSERT_GT(socket1, 0);
     init_sockets.push_back(socket1);
 
-    // create socket #2
+    // Create socket #2
     IOAddress loAddr("127.0.0.1");
     int socket2 = 0;
     ASSERT_NO_THROW(
@@ -436,7 +427,7 @@ TEST_F(IfaceMgrTest, multipleSockets) {
             }
         }
     }
-    // all created sockets have been matched if this condition works.
+    // All created sockets have been matched if this condition works.
     EXPECT_EQ(sockets.size(), matched_sockets);
 
     // closeSockets() is the other function that we want to test. It
@@ -444,7 +435,7 @@ TEST_F(IfaceMgrTest, multipleSockets) {
     // them anymore communication.
     ifacemgr->closeSockets();
 
-    // closed sockets are supposed to be removed from the list
+    // Closed sockets are supposed to be removed from the list
     sockets = iface_ptr->getSockets();
     ASSERT_EQ(0, sockets.size());
 
@@ -466,27 +457,27 @@ TEST_F(IfaceMgrTest, multipleSockets) {
 }
 
 TEST_F(IfaceMgrTest, sockets6) {
-    // testing socket operation in a portable way is tricky
-    // without interface detection implemented
+    // Testing socket operation in a portable way is tricky
+    // without interface detection implemented.
 
-    boost::scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+    scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
 
     IOAddress loAddr("::1");
 
     Pkt6 pkt6(DHCPV6_SOLICIT, 123);
     pkt6.setIface(LOOPBACK);
 
-    // bind multicast socket to port 10547
+    // Bind multicast socket to port 10547
     int socket1 = ifacemgr->openSocket(LOOPBACK, loAddr, 10547);
     EXPECT_GT(socket1, 0); // socket > 0
 
     EXPECT_EQ(socket1, ifacemgr->getSocket(pkt6));
 
-    // bind unicast socket to port 10548
+    // Bind unicast socket to port 10548
     int socket2 = ifacemgr->openSocket(LOOPBACK, loAddr, 10548);
     EXPECT_GT(socket2, 0);
 
-    // removed code for binding socket twice to the same address/port
+    // Removed code for binding socket twice to the same address/port
     // as it caused problems on some platforms (e.g. Mac OS X)
 
     // Close sockets here because the following tests will want to
@@ -511,7 +502,7 @@ TEST_F(IfaceMgrTest, sockets6) {
 }
 
 TEST_F(IfaceMgrTest, socketsFromIface) {
-    boost::scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+    scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
 
     // Open v6 socket on loopback interface and bind to port
     int socket1 = 0;
@@ -547,7 +538,7 @@ TEST_F(IfaceMgrTest, socketsFromIface) {
 
 
 TEST_F(IfaceMgrTest, socketsFromAddress) {
-    boost::scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+    scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
 
     // Open v6 socket on loopback interface and bind to port
     int socket1 = 0;
@@ -582,7 +573,7 @@ TEST_F(IfaceMgrTest, socketsFromAddress) {
 }
 
 TEST_F(IfaceMgrTest, socketsFromRemoteAddress) {
-    boost::scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+    scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
 
     // Open v6 socket to connect to remote address.
     // Loopback address is the only one that we know
@@ -630,7 +621,7 @@ TEST_F(IfaceMgrTest, DISABLED_sockets6Mcast) {
     // testing socket operation in a portable way is tricky
     // without interface detection implemented
 
-    NakedIfaceMgr* ifacemgr = new NakedIfaceMgr();
+    scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
 
     IOAddress loAddr("::1");
     IOAddress mcastAddr("ff02::1:2");
@@ -651,8 +642,6 @@ TEST_F(IfaceMgrTest, DISABLED_sockets6Mcast) {
 
     close(socket1);
     close(socket2);
-
-    delete ifacemgr;
 }
 
 TEST_F(IfaceMgrTest, sendReceive6) {
@@ -660,7 +649,7 @@ TEST_F(IfaceMgrTest, sendReceive6) {
     // testing socket operation in a portable way is tricky
     // without interface detection implemented
 
-    boost::scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+    scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
 
     // let's assume that every supported OS have lo interface
     IOAddress loAddr("::1");
@@ -721,7 +710,7 @@ TEST_F(IfaceMgrTest, sendReceive4) {
     // testing socket operation in a portable way is tricky
     // without interface detection implemented
 
-    boost::scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+    scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
 
     // let's assume that every supported OS have lo interface
     IOAddress loAddr("127.0.0.1");
@@ -842,7 +831,7 @@ TEST_F(IfaceMgrTest, setPacketFilter) {
 
 TEST_F(IfaceMgrTest, socket4) {
 
-    NakedIfaceMgr* ifacemgr = new NakedIfaceMgr();
+    scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
 
     // Let's assume that every supported OS have lo interface.
     IOAddress loAddr("127.0.0.1");
@@ -862,16 +851,12 @@ TEST_F(IfaceMgrTest, socket4) {
     EXPECT_EQ(socket1, ifacemgr->getSocket(pkt));
 
     close(socket1);
-
-    delete ifacemgr;
 }
 
 // Test the Iface structure itself
 TEST_F(IfaceMgrTest, iface) {
-    Iface* iface = NULL;
-    EXPECT_NO_THROW(
-        iface = new Iface("eth0",1);
-    );
+    boost::scoped_ptr<Iface> iface;
+    EXPECT_NO_THROW(iface.reset(new Iface("eth0",1)));
 
     EXPECT_EQ("eth0", iface->getName());
     EXPECT_EQ(1, iface->getIndex());
@@ -902,9 +887,7 @@ TEST_F(IfaceMgrTest, iface) {
 
     EXPECT_EQ(0, addrs.size());
 
-    EXPECT_NO_THROW(
-        delete iface;
-    );
+    EXPECT_NO_THROW(iface.reset());
 }
 
 TEST_F(IfaceMgrTest, iface_methods) {
@@ -942,22 +925,22 @@ TEST_F(IfaceMgrTest, iface_methods) {
 
 TEST_F(IfaceMgrTest, socketInfo) {
 
-    // check that socketinfo for IPv4 socket is functional
+    // Check that socketinfo for IPv4 socket is functional
     SocketInfo sock1(7, IOAddress("192.0.2.56"), DHCP4_SERVER_PORT + 7);
     EXPECT_EQ(7, sock1.sockfd_);
     EXPECT_EQ("192.0.2.56", sock1.addr_.toText());
     EXPECT_EQ(AF_INET, sock1.family_);
     EXPECT_EQ(DHCP4_SERVER_PORT + 7, sock1.port_);
 
-    // check that socketinfo for IPv6 socket is functional
+    // Check that socketinfo for IPv6 socket is functional
     SocketInfo sock2(9, IOAddress("2001:db8:1::56"), DHCP4_SERVER_PORT + 9);
     EXPECT_EQ(9, sock2.sockfd_);
     EXPECT_EQ("2001:db8:1::56", sock2.addr_.toText());
     EXPECT_EQ(AF_INET6, sock2.family_);
     EXPECT_EQ(DHCP4_SERVER_PORT + 9, sock2.port_);
 
-    // now let's test if IfaceMgr handles socket info properly
-    NakedIfaceMgr* ifacemgr = new NakedIfaceMgr();
+    // Now let's test if IfaceMgr handles socket info properly
+    scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
     Iface* loopback = ifacemgr->getIface(LOOPBACK);
     ASSERT_TRUE(loopback);
     loopback->addSocket(sock1);
@@ -971,14 +954,14 @@ TEST_F(IfaceMgrTest, socketInfo) {
         BadValue
     );
 
-    // try to send over non-existing interface
+    // Try to send over non-existing interface
     pkt6.setIface("nosuchinterface45");
     EXPECT_THROW(
         ifacemgr->getSocket(pkt6),
         BadValue
     );
 
-    // this will work
+    // This will work
     pkt6.setIface(LOOPBACK);
     EXPECT_EQ(9, ifacemgr->getSocket(pkt6));
 
@@ -988,13 +971,13 @@ TEST_F(IfaceMgrTest, socketInfo) {
     );
     EXPECT_EQ(true, deleted);
 
-    // it should throw again, there's no usable socket anymore
+    // It should throw again, there's no usable socket anymore
     EXPECT_THROW(
         ifacemgr->getSocket(pkt6),
         Unexpected
     );
 
-    // repeat for pkt4
+    // Repeat for pkt4
     Pkt4 pkt4(DHCPDISCOVER, 1);
 
     // pkt4 does not have interface set yet.
@@ -1023,8 +1006,6 @@ TEST_F(IfaceMgrTest, socketInfo) {
         ifacemgr->getSocket(pkt4),
         Unexpected
     );
-
-    delete ifacemgr;
 }
 
 #if defined(OS_LINUX)
@@ -1150,14 +1131,17 @@ void parse_ifconfig(const std::string& textFile, IfaceMgr::IfaceCollection& ifac
                 // Ubuntu style format: inet6 addr: ::1/128 Scope:Host
                 addr = line.substr(line.find("addr:") + 6, string::npos);
             } else {
-                // Gentoo style format: inet6 fe80::6ef0:49ff:fe96:ba17  prefixlen 64  scopeid 0x20<link>
+                // Gentoo style format: inet6 fe80::6ef0:49ff:fe96:ba17
+                // prefixlen 64  scopeid 0x20<link>
                 addr = line.substr(line.find("inet6") + 6, string::npos);
             }
 
-            // handle Ubuntu format: inet6 addr: fe80::f66d:4ff:fe96:58f2/64 Scope:Link
+            // Handle Ubuntu format: inet6 addr: fe80::f66d:4ff:fe96:58f2/64
+            // Scope:Link
             addr = addr.substr(0, addr.find("/"));
 
-            // handle inet6 fe80::ca3a:35ff:fed4:8f1d  prefixlen 64  scopeid 0x20<link>
+            // Handle inet6 fe80::ca3a:35ff:fed4:8f1d  prefixlen 64
+            // scopeid 0x20<link>
             addr = addr.substr(0, addr.find(" "));
             IOAddress a(addr);
             iface->addAddress(a);
@@ -1176,7 +1160,7 @@ void parse_ifconfig(const std::string& textFile, IfaceMgr::IfaceCollection& ifac
             IOAddress a(addr);
             iface->addAddress(a);
         } else if(line.find("Metric") != string::npos) {
-            // flags
+            // Flags
             if (line.find("UP") != string::npos) {
                 iface->flag_up_ = true;
             }
@@ -1197,15 +1181,16 @@ void parse_ifconfig(const std::string& textFile, IfaceMgr::IfaceCollection& ifac
 }
 
 
-// This test compares implemented detection routines to output of "ifconfig -a" command.
-// It is far from perfect, but it is able to verify that interface names, flags,
-// MAC address, IPv4 and IPv6 addresses are detected properly. Interface list completeness
-// (check that each interface is reported, i.e. no missing or extra interfaces) and
-// address completeness is verified.
+// This test compares implemented detection routines to output of "ifconfig
+// -a" command.  It is far from perfect, but it is able to verify that
+// interface names, flags, MAC address, IPv4 and IPv6 addresses are detected
+// properly. Interface list completeness (check that each interface is reported,
+// i.e. no missing or extra interfaces) and address completeness is verified.
 //
 // Things that are not tested:
 // - ifindex (ifconfig does not print it out)
-// - address scopes and lifetimes (we don't need it, so it is not implemented in IfaceMgr)
+// - address scopes and lifetimes (we don't need it, so it is not implemented
+//   in IfaceMgr)
 // TODO: temporarily disabled, see ticket #1529
 TEST_F(IfaceMgrTest, DISABLED_detectIfaces_linux) {
 
@@ -1219,7 +1204,7 @@ TEST_F(IfaceMgrTest, DISABLED_detectIfaces_linux) {
 
     ASSERT_EQ(0, result);
 
-    // list of interfaces parsed from ifconfig
+    // List of interfaces parsed from ifconfig
     IfaceMgr::IfaceCollection parsedIfaces;
 
     ASSERT_NO_THROW(
@@ -1253,7 +1238,7 @@ TEST_F(IfaceMgrTest, DISABLED_detectIfaces_linux) {
         cout << endl;
     }
 
-    // Ok, now we have 2 lists of interfaces. Need to compare them
+    // OK, now we have 2 lists of interfaces. Need to compare them
     ASSERT_EQ(detectedIfaces.size(), parsedIfaces.size());
 
     // TODO: This could could probably be written simple with find()
@@ -1271,14 +1256,14 @@ TEST_F(IfaceMgrTest, DISABLED_detectIfaces_linux) {
 
             cout << "Checking interface " << detected->getName() << endl;
 
-            // start with checking flags
+            // Start with checking flags
             EXPECT_EQ(detected->flag_loopback_, i->flag_loopback_);
             EXPECT_EQ(detected->flag_up_, i->flag_up_);
             EXPECT_EQ(detected->flag_running_, i->flag_running_);
             EXPECT_EQ(detected->flag_multicast_, i->flag_multicast_);
             EXPECT_EQ(detected->flag_broadcast_, i->flag_broadcast_);
 
-            // skip MAC comparison for loopback as netlink returns MAC
+            // Skip MAC comparison for loopback as netlink returns MAC
             // 00:00:00:00:00:00 for lo
             if (!detected->flag_loopback_) {
                 ASSERT_EQ(detected->getMacLen(), i->getMacLen());
@@ -1287,7 +1272,7 @@ TEST_F(IfaceMgrTest, DISABLED_detectIfaces_linux) {
 
             EXPECT_EQ(detected->getAddresses().size(), i->getAddresses().size());
 
-            // now compare addresses
+            // Now compare addresses
             const Iface::AddressCollection& addrs = detected->getAddresses();
             for (Iface::AddressCollection::const_iterator addr = addrs.begin();
                  addr != addrs.end(); ++addr) {
@@ -1310,7 +1295,7 @@ TEST_F(IfaceMgrTest, DISABLED_detectIfaces_linux) {
                      << " matched with 'ifconfig -a' output." << endl;
             }
         }
-        if (!found) { // corresponding interface was not found
+        if (!found) { // Corresponding interface was not found
             FAIL();
         }
     }
@@ -1327,14 +1312,14 @@ void my_callback(void) {
 }
 
 TEST_F(IfaceMgrTest, controlSession) {
-    // tests if extra control socket and its callback can be passed and
+    // Tests if extra control socket and its callback can be passed and
     // it is supported properly by receive4() method.
 
     callback_ok = false;
 
-    NakedIfaceMgr* ifacemgr = new NakedIfaceMgr();
+    scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
 
-    // create pipe and register it as extra socket
+    // Create pipe and register it as extra socket
     int pipefd[2];
     EXPECT_TRUE(pipe(pipefd) == 0);
     EXPECT_NO_THROW(ifacemgr->set_session_socket(pipefd[0], my_callback));
@@ -1360,8 +1345,6 @@ TEST_F(IfaceMgrTest, controlSession) {
     // There was some data, so this time callback should be called
     EXPECT_TRUE(callback_ok);
 
-    delete ifacemgr;
-
     // close both pipe ends
     close(pipefd[1]);
     close(pipefd[0]);
diff --git a/src/lib/dhcp/tests/libdhcp++_unittest.cc b/src/lib/dhcp/tests/libdhcp++_unittest.cc
index dbab492..f924427 100644
--- a/src/lib/dhcp/tests/libdhcp++_unittest.cc
+++ b/src/lib/dhcp/tests/libdhcp++_unittest.cc
@@ -52,8 +52,7 @@ public:
     /// @param buf option-buffer
     static OptionPtr genericOptionFactory(Option::Universe u, uint16_t type,
                                           const OptionBuffer& buf) {
-        Option* option = new Option(u, type, buf);
-        return OptionPtr(option);
+        return (OptionPtr(new Option(u, type, buf)));
     }
 
     /// @brief Test DHCPv4 option definition.
@@ -265,11 +264,11 @@ TEST_F(LibDhcpTest, packOptions6) {
     OptionPtr opt4(new Option(Option::V6, 6, buf.begin() + 8, buf.begin() + 12));
     OptionPtr opt5(new Option(Option::V6, 8, buf.begin() + 12, buf.begin() + 14));
 
-    opts.insert(pair<int, OptionPtr >(opt1->getType(), opt1));
-    opts.insert(pair<int, OptionPtr >(opt1->getType(), opt2));
-    opts.insert(pair<int, OptionPtr >(opt1->getType(), opt3));
-    opts.insert(pair<int, OptionPtr >(opt1->getType(), opt4));
-    opts.insert(pair<int, OptionPtr >(opt1->getType(), opt5));
+    opts.insert(make_pair(opt1->getType(), opt1));
+    opts.insert(make_pair(opt1->getType(), opt2));
+    opts.insert(make_pair(opt1->getType(), opt3));
+    opts.insert(make_pair(opt1->getType(), opt4));
+    opts.insert(make_pair(opt1->getType(), opt5));
 
     OutputBuffer assembled(512);
 
diff --git a/src/lib/dhcp/tests/option4_addrlst_unittest.cc b/src/lib/dhcp/tests/option4_addrlst_unittest.cc
index 3a9f2e7..767d690 100644
--- a/src/lib/dhcp/tests/option4_addrlst_unittest.cc
+++ b/src/lib/dhcp/tests/option4_addrlst_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013  Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -21,6 +21,7 @@
 #include <util/buffer.h>
 
 #include <gtest/gtest.h>
+#include <boost/scoped_ptr.hpp>
 
 #include <iostream>
 #include <sstream>
@@ -32,6 +33,7 @@ using namespace isc;
 using namespace isc::dhcp;
 using namespace isc::asiolink;
 using namespace isc::util;
+using boost::scoped_ptr;
 
 namespace {
 
@@ -62,7 +64,7 @@ class Option4AddrLstTest : public ::testing::Test {
 protected:
 
     Option4AddrLstTest():
-        vec_(vector<uint8_t>(300,0)) // 300 bytes long filled with 0s
+        vec_(vector<uint8_t>(300, 0)) // 300 bytes long filled with 0s
     {
         sampleAddrs_.push_back(IOAddress("192.0.2.3"));
         sampleAddrs_.push_back(IOAddress("255.255.255.0"));
@@ -79,13 +81,13 @@ TEST_F(Option4AddrLstTest, parse1) {
 
     memcpy(&vec_[0], sampledata, sizeof(sampledata));
 
-    // just one address
-    Option4AddrLst* opt1 = 0;
+    // Just one address
+    scoped_ptr<Option4AddrLst> opt1;
     EXPECT_NO_THROW(
-        opt1 = new Option4AddrLst(DHO_DOMAIN_NAME_SERVERS,
-                                  vec_.begin(),
-                                  vec_.begin()+4);
-        // use just first address (4 bytes), not the whole
+        opt1.reset(new Option4AddrLst(DHO_DOMAIN_NAME_SERVERS,
+                                      vec_.begin(),
+                                      vec_.begin()+4));
+        // Use just first address (4 bytes), not the whole
         // sampledata
     );
 
@@ -99,26 +101,21 @@ TEST_F(Option4AddrLstTest, parse1) {
 
     EXPECT_EQ("192.0.2.3", addrs[0].toText());
 
-    EXPECT_NO_THROW(
-        delete opt1;
-        opt1 = 0;
-    );
-
-    // 1 address
+    EXPECT_NO_THROW(opt1.reset());
 }
 
 TEST_F(Option4AddrLstTest, parse4) {
 
-    vector<uint8_t> buffer(300,0); // 300 bytes long filled with 0s
+    vector<uint8_t> buffer(300, 0); // 300 bytes long filled with 0s
 
     memcpy(&buffer[0], sampledata, sizeof(sampledata));
 
     // 4 addresses
-    Option4AddrLst* opt4 = 0;
+    scoped_ptr<Option4AddrLst> opt4;
     EXPECT_NO_THROW(
-        opt4 = new Option4AddrLst(254,
-                                  buffer.begin(),
-                                  buffer.begin()+sizeof(sampledata));
+        opt4.reset(new Option4AddrLst(254,
+                                      buffer.begin(),
+                                      buffer.begin()+sizeof(sampledata)));
     );
 
     EXPECT_EQ(Option::V4, opt4->getUniverse());
@@ -134,17 +131,15 @@ TEST_F(Option4AddrLstTest, parse4) {
     EXPECT_EQ("0.0.0.0", addrs[2].toText());
     EXPECT_EQ("127.0.0.1", addrs[3].toText());
 
-    EXPECT_NO_THROW(
-        delete opt4;
-        opt4 = 0;
-    );
+    EXPECT_NO_THROW(opt4.reset());
 }
 
 TEST_F(Option4AddrLstTest, assembly1) {
 
-    Option4AddrLst* opt = 0;
+    scoped_ptr<Option4AddrLst> opt;
     EXPECT_NO_THROW(
-        opt = new Option4AddrLst(DHO_DOMAIN_NAME_SERVERS, IOAddress("192.0.2.3"));
+        opt.reset(new Option4AddrLst(DHO_DOMAIN_NAME_SERVERS,
+                                     IOAddress("192.0.2.3")));
     );
     EXPECT_EQ(Option::V4, opt->getUniverse());
     EXPECT_EQ(DHO_DOMAIN_NAME_SERVERS, opt->getType());
@@ -163,28 +158,21 @@ TEST_F(Option4AddrLstTest, assembly1) {
 
     EXPECT_EQ(0, memcmp(expected1, buf.getData(), 6));
 
-    EXPECT_NO_THROW(
-        delete opt;
-        opt = 0;
-    );
+    EXPECT_NO_THROW(opt.reset());
 
     // This is old-fashioned option. We don't serve IPv6 types here!
     EXPECT_THROW(
-        opt = new Option4AddrLst(DHO_DOMAIN_NAME_SERVERS, IOAddress("2001:db8::1")),
+        opt.reset(new Option4AddrLst(DHO_DOMAIN_NAME_SERVERS,
+                                     IOAddress("2001:db8::1"))),
         BadValue
     );
-    if (opt) {
-        // test failed. Exception was not thrown, but option was created instead.
-        delete opt;
-    }
 }
 
 TEST_F(Option4AddrLstTest, assembly4) {
 
-
-    Option4AddrLst* opt = 0;
+    scoped_ptr<Option4AddrLst> opt;
     EXPECT_NO_THROW(
-        opt = new Option4AddrLst(254, sampleAddrs_);
+        opt.reset(new Option4AddrLst(254, sampleAddrs_));
     );
     EXPECT_EQ(Option::V4, opt->getUniverse());
     EXPECT_EQ(254, opt->getType());
@@ -206,27 +194,21 @@ TEST_F(Option4AddrLstTest, assembly4) {
 
     ASSERT_EQ(0, memcmp(expected4, buf.getData(), 18));
 
-    EXPECT_NO_THROW(
-        delete opt;
-        opt = 0;
-    );
+    EXPECT_NO_THROW(opt.reset());
 
     // This is old-fashioned option. We don't serve IPv6 types here!
     sampleAddrs_.push_back(IOAddress("2001:db8::1"));
     EXPECT_THROW(
-        opt = new Option4AddrLst(DHO_DOMAIN_NAME_SERVERS, sampleAddrs_),
+        opt.reset(new Option4AddrLst(DHO_DOMAIN_NAME_SERVERS, sampleAddrs_)),
         BadValue
     );
-    if (opt) {
-        // test failed. Exception was not thrown, but option was created instead.
-        delete opt;
-    }
 }
 
 TEST_F(Option4AddrLstTest, setAddress) {
-    Option4AddrLst* opt = 0;
+
+    scoped_ptr<Option4AddrLst> opt;
     EXPECT_NO_THROW(
-        opt = new Option4AddrLst(123, IOAddress("1.2.3.4"));
+        opt.reset(new Option4AddrLst(123, IOAddress("1.2.3.4")));
     );
     opt->setAddress(IOAddress("192.0.255.255"));
 
@@ -240,17 +222,15 @@ TEST_F(Option4AddrLstTest, setAddress) {
         BadValue
     );
 
-    EXPECT_NO_THROW(
-        delete opt;
-    );
+    EXPECT_NO_THROW(opt.reset());
 }
 
 TEST_F(Option4AddrLstTest, setAddresses) {
 
-    Option4AddrLst* opt = 0;
+    scoped_ptr<Option4AddrLst> opt;
 
     EXPECT_NO_THROW(
-        opt = new Option4AddrLst(123); // empty list
+        opt.reset(new Option4AddrLst(123)); // Empty list
     );
 
     opt->setAddresses(sampleAddrs_);
@@ -269,9 +249,7 @@ TEST_F(Option4AddrLstTest, setAddresses) {
         BadValue
     );
 
-    EXPECT_NO_THROW(
-        delete opt;
-    );
+    EXPECT_NO_THROW(opt.reset());
 }
 
 } // namespace
diff --git a/src/lib/dhcp/tests/option6_addrlst_unittest.cc b/src/lib/dhcp/tests/option6_addrlst_unittest.cc
index 96db59c..35a7829 100644
--- a/src/lib/dhcp/tests/option6_addrlst_unittest.cc
+++ b/src/lib/dhcp/tests/option6_addrlst_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -21,6 +21,7 @@
 #include <util/buffer.h>
 
 #include <gtest/gtest.h>
+#include <boost/scoped_ptr.hpp>
 
 #include <iostream>
 #include <sstream>
@@ -32,6 +33,7 @@ using namespace isc;
 using namespace isc::dhcp;
 using namespace isc::asiolink;
 using namespace isc::util;
+using boost::scoped_ptr;
 
 namespace {
 class Option6AddrLstTest : public ::testing::Test {
@@ -47,7 +49,7 @@ public:
 
 TEST_F(Option6AddrLstTest, basic) {
 
-    // Limiting tests to just a 2001:db8::/32 as is *wrong*.
+    // Limiting tests to just a 2001:db8::/32 is *wrong*.
     // Good tests check corner cases as well.
     // ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff checks
     // for integer overflow.
@@ -110,10 +112,11 @@ TEST_F(Option6AddrLstTest, basic) {
 
     memcpy(&buf_[0], sampledata, 48);
 
-    // just a single address
-    Option6AddrLst* opt1 = 0;
+    // Just a single address
+    scoped_ptr<Option6AddrLst> opt1;
     EXPECT_NO_THROW(
-        opt1 = new Option6AddrLst(D6O_NAME_SERVERS, buf_.begin(), buf_.begin() + 16 );
+        opt1.reset(new Option6AddrLst(D6O_NAME_SERVERS,
+                                      buf_.begin(), buf_.begin() + 16));
     );
 
     EXPECT_EQ(Option::V6, opt1->getUniverse());
@@ -125,16 +128,17 @@ TEST_F(Option6AddrLstTest, basic) {
     IOAddress addr = addrs[0];
     EXPECT_EQ("2001:db8:1::dead:beef", addr.toText());
 
-    // pack this option
+    // Pack this option
     opt1->pack(outBuf_);
 
     EXPECT_EQ(20, outBuf_.getLength());
     EXPECT_EQ(0, memcmp(expected1, outBuf_.getData(), 20));
 
-    // two addresses
-    Option6AddrLst* opt2 = 0;
+    // Two addresses
+    scoped_ptr<Option6AddrLst> opt2;
     EXPECT_NO_THROW(
-        opt2 = new Option6AddrLst(D6O_SIP_SERVERS_ADDR, buf_.begin(), buf_.begin() + 32);
+        opt2.reset(new Option6AddrLst(D6O_SIP_SERVERS_ADDR,
+                                      buf_.begin(), buf_.begin() + 32));
     );
     EXPECT_EQ(D6O_SIP_SERVERS_ADDR, opt2->getType());
     EXPECT_EQ(36, opt2->len());
@@ -143,17 +147,18 @@ TEST_F(Option6AddrLstTest, basic) {
     EXPECT_EQ("2001:db8:1::dead:beef", addrs[0].toText());
     EXPECT_EQ("ff02::face:b00c", addrs[1].toText());
 
-    // pack this option
+    // Pack this option
     outBuf_.clear();
     opt2->pack(outBuf_);
 
     EXPECT_EQ(36, outBuf_.getLength() );
     EXPECT_EQ(0, memcmp(expected2, outBuf_.getData(), 36));
 
-    // three addresses
-    Option6AddrLst* opt3 = 0;
+    // Three addresses
+    scoped_ptr<Option6AddrLst> opt3;
     EXPECT_NO_THROW(
-        opt3 = new Option6AddrLst(D6O_NIS_SERVERS, buf_.begin(), buf_.begin() + 48);
+        opt3.reset(new Option6AddrLst(D6O_NIS_SERVERS,
+                                      buf_.begin(), buf_.begin() + 48));
     );
 
     EXPECT_EQ(D6O_NIS_SERVERS, opt3->getType());
@@ -164,25 +169,23 @@ TEST_F(Option6AddrLstTest, basic) {
     EXPECT_EQ("ff02::face:b00c", addrs[1].toText());
     EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", addrs[2].toText());
 
-    // pack this option
+    // Pack this option
     outBuf_.clear();
     opt3->pack(outBuf_);
 
     EXPECT_EQ(52, outBuf_.getLength());
     EXPECT_EQ(0, memcmp(expected3, outBuf_.getData(), 52));
 
-    EXPECT_NO_THROW(
-        delete opt1;
-        delete opt2;
-        delete opt3;
-    );
+    EXPECT_NO_THROW(opt1.reset());
+    EXPECT_NO_THROW(opt2.reset());
+    EXPECT_NO_THROW(opt3.reset());
 }
 
 TEST_F(Option6AddrLstTest, constructors) {
 
-    Option6AddrLst* opt1 = 0;
+    scoped_ptr<Option6AddrLst> opt1;
     EXPECT_NO_THROW(
-        opt1 = new Option6AddrLst(1234, IOAddress("::1"));
+        opt1.reset(new Option6AddrLst(1234, IOAddress("::1")));
     );
     EXPECT_EQ(Option::V6, opt1->getUniverse());
     EXPECT_EQ(1234, opt1->getType());
@@ -195,9 +198,9 @@ TEST_F(Option6AddrLstTest, constructors) {
     addrs.push_back(IOAddress(string("fe80::1234")));
     addrs.push_back(IOAddress(string("2001:db8:1::baca")));
 
-    Option6AddrLst* opt2 = 0;
+    scoped_ptr<Option6AddrLst> opt2;
     EXPECT_NO_THROW(
-        opt2 = new Option6AddrLst(5678, addrs);
+        opt2.reset(new Option6AddrLst(5678, addrs));
     );
 
     Option6AddrLst::AddressContainer check = opt2->getAddresses();
@@ -205,16 +208,14 @@ TEST_F(Option6AddrLstTest, constructors) {
     EXPECT_EQ("fe80::1234", check[0].toText());
     EXPECT_EQ("2001:db8:1::baca", check[1].toText());
 
-    EXPECT_NO_THROW(
-        delete opt1;
-        delete opt2;
-    );
+    EXPECT_NO_THROW(opt1.reset());
+    EXPECT_NO_THROW(opt2.reset());
 }
 
 TEST_F(Option6AddrLstTest, setAddress) {
-    Option6AddrLst* opt1 = 0;
+    scoped_ptr<Option6AddrLst> opt1;
     EXPECT_NO_THROW(
-        opt1 = new Option6AddrLst(1234, IOAddress("::1"));
+        opt1.reset(new Option6AddrLst(1234, IOAddress("::1")));
     );
     opt1->setAddress(IOAddress("2001:db8:1::2"));
     /// TODO It used to be ::2 address, but io_address represents
@@ -227,12 +228,10 @@ TEST_F(Option6AddrLstTest, setAddress) {
     /// a test for IOAddress)
 
     Option6AddrLst::AddressContainer addrs = opt1->getAddresses();
-    ASSERT_EQ(1, addrs.size() );
+    ASSERT_EQ(1, addrs.size());
     EXPECT_EQ("2001:db8:1::2", addrs[0].toText());
 
-    EXPECT_NO_THROW(
-        delete opt1;
-    );
+    EXPECT_NO_THROW(opt1.reset());
 }
 
 } // namespace
diff --git a/src/lib/dhcp/tests/option6_ia_unittest.cc b/src/lib/dhcp/tests/option6_ia_unittest.cc
index 24f101d..071edb9 100644
--- a/src/lib/dhcp/tests/option6_ia_unittest.cc
+++ b/src/lib/dhcp/tests/option6_ia_unittest.cc
@@ -20,6 +20,7 @@
 #include <dhcp/option6_iaaddr.h>
 #include <util/buffer.h>
 
+#include <boost/scoped_ptr.hpp>
 #include <gtest/gtest.h>
 
 #include <iostream>
@@ -32,6 +33,7 @@ using namespace isc;
 using namespace isc::dhcp;
 using namespace isc::asiolink;
 using namespace isc::util;
+using boost::scoped_ptr;
 
 namespace {
 class Option6IATest : public ::testing::Test {
@@ -61,11 +63,11 @@ TEST_F(Option6IATest, basic) {
     buf_[10] = 0x02;
     buf_[11] = 0x01;
 
-    // create an option
+    // Create an option
     // unpack() is called from constructor
-    Option6IA* opt = new Option6IA(D6O_IA_NA,
-                                   buf_.begin(),
-                                   buf_.begin() + 12);
+    scoped_ptr<Option6IA> opt(new Option6IA(D6O_IA_NA,
+                                            buf_.begin(),
+                                            buf_.begin() + 12));
 
     EXPECT_EQ(Option::V6, opt->getUniverse());
     EXPECT_EQ(D6O_IA_NA, opt->getType());
@@ -73,10 +75,10 @@ TEST_F(Option6IATest, basic) {
     EXPECT_EQ(0x81020304, opt->getT1());
     EXPECT_EQ(0x84030201, opt->getT2());
 
-    // pack this option again in the same buffer, but in
+    // Pack this option again in the same buffer, but in
     // different place
 
-    // test for pack()
+    // Test for pack()
     opt->pack(outBuf_);
 
     // 12 bytes header + 4 bytes content
@@ -85,31 +87,29 @@ TEST_F(Option6IATest, basic) {
 
     EXPECT_EQ(16, outBuf_.getLength()); // lenght(IA_NA) = 16
 
-    // check if pack worked properly:
+    // Check if pack worked properly:
     InputBuffer out(outBuf_.getData(), outBuf_.getLength());
 
-    // if option type is correct
+    // - if option type is correct
     EXPECT_EQ(D6O_IA_NA, out.readUint16());
 
-    // if option length is correct
+    // - if option length is correct
     EXPECT_EQ(12, out.readUint16());
 
-    // if iaid is correct
+    // - if iaid is correct
     EXPECT_EQ(0xa1a2a3a4, out.readUint32() );
 
-   // if T1 is correct
+   // - if T1 is correct
     EXPECT_EQ(0x81020304, out.readUint32() );
 
-    // if T1 is correct
+    // - if T1 is correct
     EXPECT_EQ(0x84030201, out.readUint32() );
 
-    EXPECT_NO_THROW(
-        delete opt;
-    );
+    EXPECT_NO_THROW(opt.reset());
 }
 
 TEST_F(Option6IATest, simple) {
-    Option6IA* ia = new Option6IA(D6O_IA_NA, 1234);
+    scoped_ptr<Option6IA> ia(new Option6IA(D6O_IA_NA, 1234));
 
     // Check that the values are really different than what we are about
     // to set them to.
@@ -128,9 +128,7 @@ TEST_F(Option6IATest, simple) {
     ia->setIAID(890);
     EXPECT_EQ(890, ia->getIAID());
 
-    EXPECT_NO_THROW(
-        delete ia;
-    );
+    EXPECT_NO_THROW(ia.reset());
 }
 
 
@@ -140,7 +138,7 @@ TEST_F(Option6IATest, suboptions_pack) {
     buf_[1] = 0xfe;
     buf_[2] = 0xfc;
 
-    Option6IA * ia = new Option6IA(D6O_IA_NA, 0x13579ace);
+    scoped_ptr<Option6IA> ia(new Option6IA(D6O_IA_NA, 0x13579ace));
     ia->setT1(0x2345);
     ia->setT2(0x3456);
 
@@ -181,9 +179,7 @@ TEST_F(Option6IATest, suboptions_pack) {
 
     EXPECT_EQ(0, memcmp(outBuf_.getData(), expected, 48));
 
-    EXPECT_NO_THROW(
-        delete ia;
-    );
+    EXPECT_NO_THROW(ia.reset());
 }
 
 
@@ -213,10 +209,11 @@ TEST_F(Option6IATest, suboptions_unpack) {
 
     memcpy(&buf_[0], expected, sizeof(expected));
 
-    Option6IA* ia = 0;
-    EXPECT_NO_THROW({
-        ia = new Option6IA(D6O_IA_NA, buf_.begin() + 4, buf_.begin() + sizeof(expected));
-    });
+    scoped_ptr<Option6IA> ia;
+    EXPECT_NO_THROW(
+        ia.reset(new Option6IA(D6O_IA_NA, buf_.begin() + 4,
+                               buf_.begin() + sizeof(expected)));
+    );
     ASSERT_TRUE(ia);
 
     EXPECT_EQ(D6O_IA_NA, ia->getType());
@@ -227,7 +224,7 @@ TEST_F(Option6IATest, suboptions_unpack) {
     OptionPtr subopt = ia->getOption(D6O_IAADDR);
     ASSERT_NE(OptionPtr(), subopt); // non-NULL
 
-    // checks for address option
+    // Checks for address option
     Option6IAAddr * addr = dynamic_cast<Option6IAAddr*>(subopt.get());
     ASSERT_TRUE(NULL != addr);
 
@@ -237,21 +234,19 @@ TEST_F(Option6IATest, suboptions_unpack) {
     EXPECT_EQ(0x7000, addr->getValid());
     EXPECT_EQ("2001:db8:1234:5678::abcd", addr->getAddress().toText());
 
-    // checks for dummy option
+    // Checks for dummy option
     subopt = ia->getOption(0xcafe);
     ASSERT_TRUE(subopt); // should be non-NULL
 
     EXPECT_EQ(0xcafe, subopt->getType());
     EXPECT_EQ(4, subopt->len());
-    // there should be no data at all
+    // There should be no data at all
     EXPECT_EQ(0, subopt->getData().size());
 
     subopt = ia->getOption(1); // get option 1
     ASSERT_FALSE(subopt); // should be NULL
 
-    EXPECT_NO_THROW(
-        delete ia;
-    );
+    EXPECT_NO_THROW(ia.reset());
 }
 
 }
diff --git a/src/lib/dhcp/tests/option6_iaaddr_unittest.cc b/src/lib/dhcp/tests/option6_iaaddr_unittest.cc
index 8c87d80..e28e2e0 100644
--- a/src/lib/dhcp/tests/option6_iaaddr_unittest.cc
+++ b/src/lib/dhcp/tests/option6_iaaddr_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -19,6 +19,7 @@
 #include <dhcp/option6_iaaddr.h>
 #include <util/buffer.h>
 
+#include <boost/scoped_ptr.hpp>
 #include <gtest/gtest.h>
 
 #include <iostream>
@@ -68,12 +69,12 @@ TEST_F(Option6IAAddrTest, basic) {
     buf_[22] = 0x5e;
     buf_[23] = 0x00; // 3,000,000,000
 
-    // create an option (unpack content)
-    Option6IAAddr* opt = new Option6IAAddr(D6O_IAADDR,
-                                           buf_.begin(),
-                                           buf_.begin() + 24);
+    // Create an option (unpack content)
+    boost::scoped_ptr<Option6IAAddr> opt(new Option6IAAddr(D6O_IAADDR,
+                                                           buf_.begin(),
+                                                           buf_.begin() + 24));
 
-    // pack this option
+    // Pack this option
     opt->pack(outBuf_);
 
     EXPECT_EQ(28, outBuf_.getLength());
@@ -90,19 +91,19 @@ TEST_F(Option6IAAddrTest, basic) {
     EXPECT_EQ(Option::OPTION6_HDR_LEN + Option6IAAddr::OPTION6_IAADDR_LEN,
               opt->len());
 
-    // check if pack worked properly:
+    // Check if pack worked properly:
     const uint8_t* out = (const uint8_t*)outBuf_.getData();
 
-    // if option type is correct
+    // - if option type is correct
     EXPECT_EQ(D6O_IAADDR, out[0]*256 + out[1]);
 
-    // if option length is correct
+    // - if option length is correct
     EXPECT_EQ(24, out[2]*256 + out[3]);
 
-    // if option content is correct
+    // - if option content is correct
     EXPECT_EQ(0, memcmp(out + 4, &buf_[0], 24));
 
-    EXPECT_NO_THROW( delete opt );
+    EXPECT_NO_THROW(opt.reset());
 }
 
 }
diff --git a/src/lib/dhcp/tests/option_unittest.cc b/src/lib/dhcp/tests/option_unittest.cc
index 2ce3bc6..237e73b 100644
--- a/src/lib/dhcp/tests/option_unittest.cc
+++ b/src/lib/dhcp/tests/option_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -20,6 +20,7 @@
 #include <util/buffer.h>
 
 #include <boost/shared_ptr.hpp>
+#include <boost/scoped_ptr.hpp>
 #include <gtest/gtest.h>
 
 #include <iostream>
@@ -31,6 +32,7 @@ using namespace std;
 using namespace isc;
 using namespace isc::dhcp;
 using namespace isc::util;
+using boost::scoped_ptr;
 
 namespace {
 class OptionTest : public ::testing::Test {
@@ -44,50 +46,27 @@ public:
     OutputBuffer outBuf_;
 };
 
-// v4 is not really implemented yet. A simple test will do for now
+// Basic tests for V4 functionality
 TEST_F(OptionTest, v4_basic) {
 
-    Option* opt = 0;
-    EXPECT_NO_THROW(
-        opt = new Option(Option::V4, 17);
-    );
+    scoped_ptr<Option> opt;
+    EXPECT_NO_THROW(opt.reset(new Option(Option::V4, 17)));
 
     EXPECT_EQ(Option::V4, opt->getUniverse());
     EXPECT_EQ(17, opt->getType());
     EXPECT_EQ(0, opt->getData().size());
     EXPECT_EQ(2, opt->len()); // just v4 header
 
-    EXPECT_NO_THROW(
-        delete opt;
-    );
-    opt = 0;
+    EXPECT_NO_THROW(opt.reset());
 
     // V4 options have type 0...255
-    EXPECT_THROW(
-        opt = new Option(Option::V4, 256),
-        BadValue
-    );
-
-    delete opt;
-    opt = 0;
+    EXPECT_THROW(opt.reset(new Option(Option::V4, 256)), BadValue);
 
     // 0 is a special PAD option
-    EXPECT_THROW(
-        opt = new Option(Option::V4, 0),
-        BadValue
-    );
-
-    delete opt;
-    opt = 0;
+    EXPECT_THROW(opt.reset(new Option(Option::V4, 0)), BadValue);
 
     // 255 is a special END option
-    EXPECT_THROW(
-        opt = new Option(Option::V4, 255),
-        BadValue
-    );
-
-    delete opt;
-    opt = 0;
+    EXPECT_THROW(opt.reset(new Option(Option::V4, 255)), BadValue);
 }
 
 const uint8_t dummyPayload[] =
@@ -97,15 +76,12 @@ TEST_F(OptionTest, v4_data1) {
 
     vector<uint8_t> data(dummyPayload, dummyPayload + sizeof(dummyPayload));
 
-    Option* opt = 0;
+    scoped_ptr<Option> opt;
 
-    // create DHCPv4 option of type 123
-    // that contains 4 bytes of data
-    ASSERT_NO_THROW(
-        opt= new Option(Option::V4, 123, data);
-    );
+    // Create DHCPv4 option of type 123 that contains 4 bytes of data.
+    ASSERT_NO_THROW(opt.reset(new Option(Option::V4, 123, data)));
 
-    // check that content is reported properly
+    // Check that content is reported properly
     EXPECT_EQ(123, opt->getType());
     vector<uint8_t> optData = opt->getData();
     ASSERT_EQ(optData.size(), data.size());
@@ -113,31 +89,26 @@ TEST_F(OptionTest, v4_data1) {
     EXPECT_EQ(2, opt->getHeaderLen());
     EXPECT_EQ(6, opt->len());
 
-    // now store that option into a buffer
+    // Now store that option into a buffer
     OutputBuffer buf(100);
-    EXPECT_NO_THROW(
-        opt->pack(buf);
-    );
-
-    // check content of that buffer
+    EXPECT_NO_THROW(opt->pack(buf));
 
+    // Check content of that buffer:
     // 2 byte header + 4 bytes data
     ASSERT_EQ(6, buf.getLength());
 
-    // that's how this option is supposed to look like
+    // That's how this option is supposed to look like
     uint8_t exp[] = { 123, 4, 1, 2, 3, 4 };
 
     /// TODO: use vector<uint8_t> getData() when it will be implemented
     EXPECT_EQ(0, memcmp(exp, buf.getData(), 6));
 
-    // check that we can destroy that option
-    EXPECT_NO_THROW(
-        delete opt;
-    );
+    // Check that we can destroy that option
+    EXPECT_NO_THROW(opt.reset());
 }
 
-// this is almost the same test as v4_data1, but it uses
-// different constructor
+// This is almost the same test as v4_data1, but it uses a different
+// constructor
 TEST_F(OptionTest, v4_data2) {
 
     vector<uint8_t> data(dummyPayload, dummyPayload + sizeof(dummyPayload));
@@ -153,16 +124,16 @@ TEST_F(OptionTest, v4_data2) {
     // ignored, as we pass interators to proper data. Only subset (limited by
     // iterators) of the vector should be used.
     // expData contains expected content (just valid data, without garbage).
-
-    Option* opt = 0;
+    scoped_ptr<Option> opt;
 
     // Create DHCPv4 option of type 123 that contains
     // 4 bytes (sizeof(dummyPayload).
     ASSERT_NO_THROW(
-        opt= new Option(Option::V4, 123, data.begin() + 1, data.end() - 1);
+        opt.reset(new Option(Option::V4, 123, data.begin() + 1,
+                             data.end() - 1));
     );
 
-    // check that content is reported properly
+    // Check that content is reported properly
     EXPECT_EQ(123, opt->getType());
     vector<uint8_t> optData = opt->getData();
     ASSERT_EQ(optData.size(), expData.size());
@@ -170,27 +141,23 @@ TEST_F(OptionTest, v4_data2) {
     EXPECT_EQ(2, opt->getHeaderLen());
     EXPECT_EQ(6, opt->len());
 
-    // now store that option into a buffer
+    // Now store that option into a buffer
     OutputBuffer buf(100);
-    EXPECT_NO_THROW(
-        opt->pack(buf);
-    );
+    EXPECT_NO_THROW(opt->pack(buf));
 
-    // check content of that buffer
+    // Check content of that buffer
 
     // 2 byte header + 4 bytes data
     ASSERT_EQ(6, buf.getLength());
 
-    // that's how this option is supposed to look like
+    // That's how this option is supposed to look like
     uint8_t exp[] = { 123, 4, 1, 2, 3, 4 };
 
     /// TODO: use vector<uint8_t> getData() when it will be implemented
     EXPECT_EQ(0, memcmp(exp, buf.getData(), 6));
 
-    // check that we can destroy that option
-    EXPECT_NO_THROW(
-        delete opt;
-    );
+    // Check that we can destroy that option
+    EXPECT_NO_THROW(opt.reset());
 }
 
 TEST_F(OptionTest, v4_toText) {
@@ -205,29 +172,29 @@ TEST_F(OptionTest, v4_toText) {
     EXPECT_EQ("type=253, len=3: 00:0f:ff", opt.toText());
 }
 
-// tests simple constructor
+// Tests simple constructor
 TEST_F(OptionTest, v6_basic) {
 
-    Option* opt = new Option(Option::V6, 1);
+    scoped_ptr<Option> opt(new Option(Option::V6, 1));
 
     EXPECT_EQ(Option::V6, opt->getUniverse());
     EXPECT_EQ(1, opt->getType());
     EXPECT_EQ(0, opt->getData().size());
-    EXPECT_EQ(4, opt->len()); // just v6 header
+    EXPECT_EQ(4, opt->len()); // Just v6 header
 
-    EXPECT_NO_THROW(
-        delete opt;
-    );
+    EXPECT_NO_THROW(opt.reset());
 }
 
-// tests constructor used in pkt reception
-// option contains actual data
+// Tests constructor used in packet reception.  Option contains actual data
 TEST_F(OptionTest, v6_data1) {
-    for (int i = 0; i < 32; i++)
-        buf_[i] = 100+i;
-    Option* opt = new Option(Option::V6, 333,   //type
-                             buf_.begin() + 3,  // begin offset
-                             buf_.begin() + 10); // end offset (7 bytes of data)
+    for (int i = 0; i < 32; i++) {
+        buf_[i] = 100 + i;
+    }
+
+    // Create option with seven bytes of data.
+    scoped_ptr<Option> opt(new Option(Option::V6, 333,   // Type
+                                      buf_.begin() + 3,  // Begin offset
+                                      buf_.begin() + 10)); // End offset
     EXPECT_EQ(333, opt->getType());
 
     ASSERT_EQ(11, opt->len());
@@ -237,23 +204,20 @@ TEST_F(OptionTest, v6_data1) {
     opt->pack(outBuf_);
     EXPECT_EQ(11, outBuf_.getLength());
 
-    const uint8_t* out = (const uint8_t*)outBuf_.getData();
-    EXPECT_EQ(out[0], 333/256); // type
-    EXPECT_EQ(out[1], 333%256);
+    const uint8_t* out = static_cast<const uint8_t*>(outBuf_.getData());
+    EXPECT_EQ(out[0], 333 / 256); // Type
+    EXPECT_EQ(out[1], 333 % 256);
 
-    EXPECT_EQ(out[2], 0); // len
+    EXPECT_EQ(out[2], 0); // Length
     EXPECT_EQ(out[3], 7);
 
-    // payload
-    EXPECT_EQ(0, memcmp(&buf_[3], out+4, 7) );
+    // Payload
+    EXPECT_EQ(0, memcmp(&buf_[3], out + 4, 7));
 
-    EXPECT_NO_THROW(
-        delete opt;
-    );
+    EXPECT_NO_THROW(opt.reset());
 }
 
-// another text that tests the same thing, just
-// with different input parameters
+// Another test that tests the same thing, just with different input parameters.
 TEST_F(OptionTest, v6_data2) {
 
     buf_[0] = 0xa1;
@@ -261,13 +225,11 @@ TEST_F(OptionTest, v6_data2) {
     buf_[2] = 0xa3;
     buf_[3] = 0xa4;
 
-    // create an option (unpack content)
-    Option* opt = new Option(Option::V6,
-                             D6O_CLIENTID,
-                             buf_.begin(),
-                             buf_.begin() + 4);
+    // Create an option (unpack content)
+    scoped_ptr<Option> opt(new Option(Option::V6, D6O_CLIENTID,
+                                      buf_.begin(), buf_.begin() + 4));
 
-    // pack this option
+    // Pack this option
     opt->pack(outBuf_);
 
     // 4 bytes header + 4 bytes content
@@ -276,35 +238,35 @@ TEST_F(OptionTest, v6_data2) {
 
     EXPECT_EQ(8, outBuf_.getLength());
 
-    // check if pack worked properly:
-    // if option type is correct
-    const uint8_t* out = (const uint8_t*)outBuf_.getData();
+    // Check if pack worked properly:
+    // If option type is correct
+    const uint8_t* out = static_cast<const uint8_t*>(outBuf_.getData());
 
-    EXPECT_EQ(D6O_CLIENTID, out[0]*256 + out[1]);
+    EXPECT_EQ(D6O_CLIENTID, out[0] * 256 + out[1]);
 
-    // if option length is correct
-    EXPECT_EQ(4, out[2]*256 + out[3]);
+    // If option length is correct
+    EXPECT_EQ(4, out[2] * 256 + out[3]);
 
-    // if option content is correct
+    // If option content is correct
     EXPECT_EQ(0, memcmp(&buf_[0], out + 4, 4));
 
-    EXPECT_NO_THROW(
-        delete opt;
-    );
+    EXPECT_NO_THROW(opt.reset());
 }
 
-// check that an option can contain 2 suboptions:
+// Check that an option can contain 2 suboptions:
 // opt1
 //  +----opt2
 //  |
 //  +----opt3
 //
 TEST_F(OptionTest, v6_suboptions1) {
-    for (int i=0; i<128; i++)
-        buf_[i] = 100+i;
-    Option* opt1 = new Option(Option::V6, 65535, //type
-                              buf_.begin(), // 3 bytes of data
-                              buf_.begin() + 3);
+    for (int i = 0; i < 128; i++) {
+        buf_[i] = 100 + i;
+    }
+
+    scoped_ptr<Option> opt1(new Option(Option::V6, 65535, // Type
+                                       buf_.begin(), // 3 bytes of data
+                                       buf_.begin() + 3));
     OptionPtr opt2(new Option(Option::V6, 13));
     OptionPtr opt3(new Option(Option::V6, 7,
                               buf_.begin() + 3,
@@ -328,30 +290,29 @@ TEST_F(OptionTest, v6_suboptions1) {
     opt1->pack(outBuf_);
     EXPECT_EQ(20, outBuf_.getLength());
 
-    // payload
+    // Payload
     EXPECT_EQ(0, memcmp(outBuf_.getData(), expected, 20) );
 
-    EXPECT_NO_THROW(
-        delete opt1;
-    );
+    EXPECT_NO_THROW(opt1.reset());
 }
 
-// check that an option can contain nested suboptions:
+// Check that an option can contain nested suboptions:
 // opt1
 //  +----opt2
 //        |
 //        +----opt3
 //
 TEST_F(OptionTest, v6_suboptions2) {
-    for (int i=0; i<128; i++)
-        buf_[i] = 100+i;
-    Option* opt1 = new Option(Option::V6, 65535, //type
-                              buf_.begin(),
-                              buf_.begin() + 3);
+    for (int i = 0; i < 128; i++) {
+        buf_[i] = 100 + i;
+    }
+
+    scoped_ptr<Option> opt1(new Option(Option::V6, 65535, // Type
+                                       buf_.begin(), buf_.begin() + 3));
     OptionPtr opt2(new Option(Option::V6, 13));
     OptionPtr opt3(new Option(Option::V6, 7,
-                                              buf_.begin() + 3,
-                                              buf_.begin() + 8));
+                              buf_.begin() + 3,
+                              buf_.begin() + 8));
     opt1->addOption(opt2);
     opt2->addOption(opt3);
     // opt3 len = 9 4(header)+5(data)
@@ -367,18 +328,18 @@ TEST_F(OptionTest, v6_suboptions2) {
     opt1->pack(outBuf_);
     EXPECT_EQ(20, outBuf_.getLength());
 
-    // payload
+    // Payload
     EXPECT_EQ(0, memcmp(outBuf_.getData(), expected, 20) );
 
-    EXPECT_NO_THROW(
-        delete opt1;
-    );
+    EXPECT_NO_THROW(opt1.reset());
 }
 
 TEST_F(OptionTest, v6_addgetdel) {
-    for (int i=0; i<128; i++)
-        buf_[i] = 100+i;
-    Option* parent = new Option(Option::V6, 65535); //type
+    for (int i = 0; i < 128; i++) {
+        buf_[i] = 100 + i;
+    }
+
+    scoped_ptr<Option> parent(new Option(Option::V6, 65535)); // Type
     OptionPtr opt1(new Option(Option::V6, 1));
     OptionPtr opt2(new Option(Option::V6, 2));
     OptionPtr opt3(new Option(Option::V6, 2));
@@ -390,28 +351,26 @@ TEST_F(OptionTest, v6_addgetdel) {
     EXPECT_EQ(opt1, parent->getOption(1));
     EXPECT_EQ(opt2, parent->getOption(2));
 
-    // expect NULL
+    // Expect NULL
     EXPECT_EQ(OptionPtr(), parent->getOption(4));
 
-    // now there are 2 options of type 2
+    // Now there are 2 options of type 2
     parent->addOption(opt3);
 
-    // let's delete one of them
+    // Let's delete one of them
     EXPECT_EQ(true, parent->delOption(2));
 
-    // there still should be the other option 2
+    // There still should be the other option 2
     EXPECT_NE(OptionPtr(), parent->getOption(2));
 
-    // let's delete the other option 2
+    // Let's delete the other option 2
     EXPECT_EQ(true, parent->delOption(2));
 
-    // no more options with type=2
+    // No more options with type=2
     EXPECT_EQ(OptionPtr(), parent->getOption(2));
 
-    // let's try to delete - should fail
+    // Let's try to delete - should fail
     EXPECT_TRUE(false ==  parent->delOption(2));
-
-    delete parent;
 }
 
 TEST_F(OptionTest, v6_toText) {
@@ -419,8 +378,7 @@ TEST_F(OptionTest, v6_toText) {
     buf_[1] = 0xf;
     buf_[2] = 0xff;
 
-    OptionPtr opt(new Option(Option::V6, 258,  buf_.begin(), buf_.begin() + 3 ));
-
+    OptionPtr opt(new Option(Option::V6, 258, buf_.begin(), buf_.begin() + 3 ));
     EXPECT_EQ("type=258, len=3: 00:0f:ff", opt->toText());
 }
 
@@ -433,7 +391,7 @@ TEST_F(OptionTest, getUintX) {
     buf_[3] = 0x2;
     buf_[4] = 0x1;
 
-    // five options with varying lengths
+    // Five options with varying lengths
     OptionPtr opt1(new Option(Option::V6, 258, buf_.begin(), buf_.begin() + 1));
     OptionPtr opt2(new Option(Option::V6, 258, buf_.begin(), buf_.begin() + 2));
     OptionPtr opt3(new Option(Option::V6, 258, buf_.begin(), buf_.begin() + 3));
@@ -456,7 +414,7 @@ TEST_F(OptionTest, getUintX) {
     EXPECT_EQ(0x0504, opt4->getUint16());
     EXPECT_EQ(0x05040302, opt4->getUint32());
 
-    // the same as for 4-byte long, just get first 1,2 or 4 bytes
+    // The same as for 4-byte long, just get first 1,2 or 4 bytes
     EXPECT_EQ(5, opt5->getUint8());
     EXPECT_EQ(0x0504, opt5->getUint16());
     EXPECT_EQ(0x05040302, opt5->getUint32());
@@ -468,7 +426,7 @@ TEST_F(OptionTest, setUintX) {
     OptionPtr opt2(new Option(Option::V4, 125));
     OptionPtr opt4(new Option(Option::V4, 125));
 
-    // verify setUint8
+    // Verify setUint8
     opt1->setUint8(255);
     EXPECT_EQ(255, opt1->getUint8());
     opt1->pack(outBuf_);
@@ -477,7 +435,7 @@ TEST_F(OptionTest, setUintX) {
     uint8_t exp1[] = {125, 1, 255};
     EXPECT_TRUE(0 == memcmp(exp1, outBuf_.getData(), 3));
 
-    // verify getUint16
+    // Verify getUint16
     outBuf_.clear();
     opt2->setUint16(12345);
     opt2->pack(outBuf_);
@@ -487,7 +445,7 @@ TEST_F(OptionTest, setUintX) {
     uint8_t exp2[] = {125, 2, 12345/256, 12345%256};
     EXPECT_TRUE(0 == memcmp(exp2, outBuf_.getData(), 4));
 
-    // verify getUint32
+    // Verify getUint32
     outBuf_.clear();
     opt4->setUint32(0x12345678);
     opt4->pack(outBuf_);
@@ -499,8 +457,8 @@ TEST_F(OptionTest, setUintX) {
 }
 
 TEST_F(OptionTest, setData) {
-    // verify data override with new buffer larger than
-    // initial option buffer size
+    // Verify data override with new buffer larger than initial option buffer
+    // size.
     OptionPtr opt1(new Option(Option::V4, 125,
                               buf_.begin(), buf_.begin() + 10));
     buf_.resize(20, 1);
@@ -511,8 +469,8 @@ TEST_F(OptionTest, setData) {
     EXPECT_TRUE(0 == memcmp(&buf_[0], test_data + opt1->getHeaderLen(),
                             buf_.size()));
 
-    // verify data override with new buffer shorter than
-    // initial option buffer size
+    // Verify data override with new buffer shorter than initial option buffer
+    // size.
     OptionPtr opt2(new Option(Option::V4, 125,
                               buf_.begin(), buf_.begin() + 10));
     outBuf_.clear();
@@ -528,15 +486,15 @@ TEST_F(OptionTest, setData) {
 // This test verifies that options can be compared using equal() method.
 TEST_F(OptionTest, equal) {
 
-    // five options with varying lengths
+    // Five options with varying lengths
     OptionPtr opt1(new Option(Option::V6, 258, buf_.begin(), buf_.begin() + 1));
     OptionPtr opt2(new Option(Option::V6, 258, buf_.begin(), buf_.begin() + 2));
     OptionPtr opt3(new Option(Option::V6, 258, buf_.begin(), buf_.begin() + 3));
 
-    // the same content as opt2, but different type
+    // The same content as opt2, but different type
     OptionPtr opt4(new Option(Option::V6, 1, buf_.begin(), buf_.begin() + 2));
 
-    // another instance with the same type and content as opt2
+    // Another instance with the same type and content as opt2
     OptionPtr opt5(new Option(Option::V6, 258, buf_.begin(), buf_.begin() + 2));
 
     EXPECT_TRUE(opt1->equal(opt1));
diff --git a/src/lib/dhcp/tests/pkt4_unittest.cc b/src/lib/dhcp/tests/pkt4_unittest.cc
index 49588e1..5c95f7d 100644
--- a/src/lib/dhcp/tests/pkt4_unittest.cc
+++ b/src/lib/dhcp/tests/pkt4_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2012  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013  Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -35,7 +35,7 @@ using namespace isc;
 using namespace isc::asiolink;
 using namespace isc::dhcp;
 using namespace isc::util;
-// don't import the entire boost namespace.  It will unexpectedly hide uint8_t
+// Don't import the entire boost namespace.  It will unexpectedly hide uint8_t
 // for some systems.
 using boost::scoped_ptr;
 
@@ -44,55 +44,39 @@ namespace {
 TEST(Pkt4Test, constructor) {
 
     ASSERT_EQ(236U, static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN) );
-    Pkt4* pkt = 0;
+    scoped_ptr<Pkt4> pkt;
 
     // Just some dummy payload.
     uint8_t testData[250];
     for (int i = 0; i < 250; i++) {
-        testData[i]=i;
+        testData[i] = i;
     }
 
     // Positive case1. Normal received packet.
-    EXPECT_NO_THROW(
-        pkt = new Pkt4(testData, Pkt4::DHCPV4_PKT_HDR_LEN);
-    );
+    EXPECT_NO_THROW(pkt.reset(new Pkt4(testData, Pkt4::DHCPV4_PKT_HDR_LEN)));
 
     EXPECT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN), pkt->len());
 
-    EXPECT_NO_THROW(
-        delete pkt;
-        pkt = 0;
-    );
+    EXPECT_NO_THROW(pkt.reset());
 
     // Positive case2. Normal outgoing packet.
-    EXPECT_NO_THROW(
-        pkt = new Pkt4(DHCPDISCOVER, 0xffffffff);
-    );
+    EXPECT_NO_THROW(pkt.reset(new Pkt4(DHCPDISCOVER, 0xffffffff)));
 
     // DHCPv4 packet must be at least 236 bytes long, with Message Type
     // Option taking extra 3 bytes it is 239
     EXPECT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN) + 3, pkt->len());
     EXPECT_EQ(DHCPDISCOVER, pkt->getType());
     EXPECT_EQ(0xffffffff, pkt->getTransid());
-    EXPECT_NO_THROW(
-        delete pkt;
-        pkt = 0;
-    );
+    EXPECT_NO_THROW(pkt.reset());
 
     // Negative case. Should drop truncated messages.
     EXPECT_THROW(
-        pkt = new Pkt4(testData, Pkt4::DHCPV4_PKT_HDR_LEN-1),
+        pkt.reset(new Pkt4(testData, Pkt4::DHCPV4_PKT_HDR_LEN - 1)),
         OutOfRange
     );
-    if (pkt) {
-        // Test failed. Exception should have been thrown, but
-        // object was created instead. Let's clean this up.
-        delete pkt;
-        pkt = 0;
-    }
 }
 
-// a sample data
+// Sample data
 const uint8_t dummyOp = BOOTREQUEST;
 const uint8_t dummyHtype = 6;
 const uint8_t dummyHlen = 6;
@@ -109,16 +93,16 @@ const IOAddress dummyGiaddr("255.255.255.255");
 // a dummy MAC address
 const uint8_t dummyMacAddr[] = {0, 1, 2, 3, 4, 5};
 
-// a dummy MAC address, padded with 0s
+// A dummy MAC address, padded with 0s
 const uint8_t dummyChaddr[16] = {0, 1, 2, 3, 4, 5, 0, 0,
                                  0, 0, 0, 0, 0, 0, 0, 0 };
 
-// let's use some creative test content here (128 chars + \0)
+// Let's use some creative test content here (128 chars + \0)
 const uint8_t dummyFile[] = "Lorem ipsum dolor sit amet, consectetur "
     "adipiscing elit. Proin mollis placerat metus, at "
     "lacinia orci ornare vitae. Mauris amet.";
 
-// yet another type of test content (64 chars + \0)
+// Yet another type of test content (64 chars + \0)
 const uint8_t dummySname[] = "Lorem ipsum dolor sit amet, consectetur "
     "adipiscing elit posuere.";
 
@@ -127,12 +111,11 @@ BOOST_STATIC_ASSERT(sizeof(dummySname) == Pkt4::MAX_SNAME_LEN + 1);
 
 /// @brief Generates test packet.
 ///
-/// Allocates and generates test packet, with all fixed
-/// fields set to non-zero values. Content is not always
-/// reasonable.
+/// Allocates and generates test packet, with all fixed fields set to non-zero
+/// values. Content is not always reasonable.
 ///
-/// See generateTestPacket2() function that returns
-/// exactly the same packet in on-wire format.
+/// See generateTestPacket2() function that returns exactly the same packet in
+/// on-wire format.
 ///
 /// @return pointer to allocated Pkt4 object.
 boost::shared_ptr<Pkt4>
@@ -162,12 +145,11 @@ generateTestPacket1() {
 
 /// @brief Generates test packet.
 ///
-/// Allocates and generates on-wire buffer that represents
-/// test packet, with all fixed fields set to non-zero values.
-/// Content is not always reasonable.
+/// Allocates and generates on-wire buffer that represents test packet, with all
+/// fixed fields set to non-zero values.  Content is not always reasonable.
 ///
-/// See generateTestPacket1() function that returns
-/// exactly the same packet as Pkt4 object.
+/// See generateTestPacket1() function that returns exactly the same packet as
+/// Pkt4 object.
 ///
 /// @return pointer to allocated Pkt4 object
 // Returns a vector containing a DHCPv4 packet header.
@@ -206,7 +188,7 @@ TEST(Pkt4Test, fixedFields) {
 
     boost::shared_ptr<Pkt4> pkt = generateTestPacket1();
 
-    // ok, let's check packet values
+    // OK, let's check packet values
     EXPECT_EQ(dummyOp, pkt->getOp());
     EXPECT_EQ(dummyHtype, pkt->getHtype());
     EXPECT_EQ(dummyHlen, pkt->getHlen());
@@ -244,7 +226,7 @@ TEST(Pkt4Test, fixedFieldsPack) {
     // DHCP Message Type Option
     ASSERT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN) + 3, pkt->len());
 
-    // redundant but MUCH easier for debug in gdb
+    // Redundant but MUCH easier for debug in gdb
     const uint8_t* exp = &expectedFormat[0];
     const uint8_t* got = static_cast<const uint8_t*>(pkt->getBuffer().getData());
 
@@ -272,7 +254,7 @@ TEST(Pkt4Test, fixedFieldsUnpack) {
         pkt->unpack()
     );
 
-    // ok, let's check packet values
+    // OK, let's check packet values
     EXPECT_EQ(dummyOp, pkt->getOp());
     EXPECT_EQ(dummyHtype, pkt->getHtype());
     EXPECT_EQ(dummyHlen, pkt->getHlen());
@@ -298,7 +280,7 @@ TEST(Pkt4Test, fixedFieldsUnpack) {
     EXPECT_EQ(DHCPDISCOVER, pkt->getType());
 }
 
-// this test is for hardware addresses (htype, hlen and chaddr fields)
+// This test is for hardware addresses (htype, hlen and chaddr fields)
 TEST(Pkt4Test, hwAddr) {
 
     vector<uint8_t> mac;
@@ -309,7 +291,7 @@ TEST(Pkt4Test, hwAddr) {
     // (growing length back to MAX_CHADDR_LEN).
     mac.resize(Pkt4::MAX_CHADDR_LEN);
 
-    Pkt4* pkt = 0;
+    scoped_ptr<Pkt4> pkt;
     // let's test each hlen, from 0 till 16
     for (int macLen = 0; macLen < Pkt4::MAX_CHADDR_LEN; macLen++) {
         for (int i = 0; i < Pkt4::MAX_CHADDR_LEN; i++) {
@@ -322,8 +304,8 @@ TEST(Pkt4Test, hwAddr) {
         }
 
         // type and transaction doesn't matter in this test
-        pkt = new Pkt4(DHCPOFFER, 1234);
-        pkt->setHWAddr(255-macLen*10, // just weird htype
+        pkt.reset(new Pkt4(DHCPOFFER, 1234));
+        pkt->setHWAddr(255 - macLen * 10, // just weird htype
                        macLen,
                        mac);
         EXPECT_EQ(0, memcmp(expectedChaddr, &pkt->getHWAddr()->hwaddr_[0],
@@ -335,11 +317,11 @@ TEST(Pkt4Test, hwAddr) {
 
         // CHADDR starts at offset 28 in DHCP packet
         const uint8_t* ptr =
-            static_cast<const uint8_t*>(pkt->getBuffer().getData())+28;
+            static_cast<const uint8_t*>(pkt->getBuffer().getData()) + 28;
 
         EXPECT_EQ(0, memcmp(ptr, expectedChaddr, Pkt4::MAX_CHADDR_LEN));
 
-        delete pkt;
+        pkt.reset();
     }
 
     /// TODO: extend this test once options support is implemented. HW address
@@ -368,35 +350,28 @@ TEST(Pkt4Test, msgTypes) {
         {DHCPLEASEACTIVE, BOOTREPLY}
     };
 
-    Pkt4* pkt = 0;
+    scoped_ptr<Pkt4> pkt;
     for (int i = 0; i < sizeof(types) / sizeof(msgType); i++) {
-
-        pkt = new Pkt4(types[i].dhcp, 0);
+        pkt.reset(new Pkt4(types[i].dhcp, 0));
         EXPECT_EQ(types[i].dhcp, pkt->getType());
-
         EXPECT_EQ(types[i].bootp, pkt->getOp());
-
-        delete pkt;
-        pkt = 0;
+        pkt.reset();
     }
 
     EXPECT_THROW(
-        pkt = new Pkt4(100, 0), // there's no message type 100
+        pkt.reset(new Pkt4(100, 0)), // There's no message type 100
         OutOfRange
     );
-    if (pkt) {
-        delete pkt;
-    }
 }
 
-// this test verifies handling of sname field
+// This test verifies handling of sname field
 TEST(Pkt4Test, sname) {
 
     uint8_t sname[Pkt4::MAX_SNAME_LEN];
 
-    Pkt4* pkt = 0;
-    // let's test each sname length, from 0 till 64
-    for (int snameLen=0; snameLen < Pkt4::MAX_SNAME_LEN; snameLen++) {
+    scoped_ptr<Pkt4> pkt;
+    // Let's test each sname length, from 0 till 64
+    for (int snameLen = 0; snameLen < Pkt4::MAX_SNAME_LEN; snameLen++) {
         for (int i = 0; i < Pkt4::MAX_SNAME_LEN; i++) {
             sname[i] = 0;
         }
@@ -404,8 +379,8 @@ TEST(Pkt4Test, sname) {
             sname[i] = i;
         }
 
-        // type and transaction doesn't matter in this test
-        pkt = new Pkt4(DHCPOFFER, 1234);
+        // Type and transaction doesn't matter in this test
+        pkt.reset(new Pkt4(DHCPOFFER, 1234));
         pkt->setSname(sname, snameLen);
 
         EXPECT_EQ(0, memcmp(sname, &pkt->getSname()[0], Pkt4::MAX_SNAME_LEN));
@@ -416,10 +391,10 @@ TEST(Pkt4Test, sname) {
 
         // SNAME starts at offset 44 in DHCP packet
         const uint8_t* ptr =
-            static_cast<const uint8_t*>(pkt->getBuffer().getData())+44;
+            static_cast<const uint8_t*>(pkt->getBuffer().getData()) + 44;
         EXPECT_EQ(0, memcmp(ptr, sname, Pkt4::MAX_SNAME_LEN));
 
-        delete pkt;
+        pkt.reset();
     }
 
     // Check that a null argument generates an exception.
@@ -432,7 +407,7 @@ TEST(Pkt4Test, file) {
 
     uint8_t file[Pkt4::MAX_FILE_LEN];
 
-    Pkt4* pkt = 0;
+    scoped_ptr<Pkt4> pkt;
     // Let's test each file length, from 0 till 128.
     for (int fileLen = 0; fileLen < Pkt4::MAX_FILE_LEN; fileLen++) {
         for (int i = 0; i < Pkt4::MAX_FILE_LEN; i++) {
@@ -443,22 +418,21 @@ TEST(Pkt4Test, file) {
         }
 
         // Type and transaction doesn't matter in this test.
-        pkt = new Pkt4(DHCPOFFER, 1234);
+        pkt.reset(new Pkt4(DHCPOFFER, 1234));
         pkt->setFile(file, fileLen);
 
         EXPECT_EQ(0, memcmp(file, &pkt->getFile()[0], Pkt4::MAX_FILE_LEN));
 
-        //
         EXPECT_NO_THROW(
             pkt->pack();
         );
 
         // FILE starts at offset 108 in DHCP packet.
         const uint8_t* ptr =
-            static_cast<const uint8_t*>(pkt->getBuffer().getData())+108;
+            static_cast<const uint8_t*>(pkt->getBuffer().getData()) + 108;
         EXPECT_EQ(0, memcmp(ptr, file, Pkt4::MAX_FILE_LEN));
 
-        delete pkt;
+        pkt.reset();
     }
 
     // Check that a null argument generates an exception.
@@ -481,13 +455,13 @@ static uint8_t v4Opts[] = {
 };
 
 TEST(Pkt4Test, options) {
-    Pkt4* pkt = new Pkt4(DHCPOFFER, 0);
+    scoped_ptr<Pkt4> pkt(new Pkt4(DHCPOFFER, 0));
 
     vector<uint8_t> payload[5];
     for (int i = 0; i < 5; i++) {
-        payload[i].push_back(i*10);
-        payload[i].push_back(i*10+1);
-        payload[i].push_back(i*10+2);
+        payload[i].push_back(i * 10);
+        payload[i].push_back(i * 10 + 1);
+        payload[i].push_back(i * 10 + 2);
     }
 
     boost::shared_ptr<Option> opt1(new Option(Option::V4, 12, payload[0]));
@@ -509,7 +483,7 @@ TEST(Pkt4Test, options) {
     EXPECT_TRUE(pkt->getOption(254));
     EXPECT_FALSE(pkt->getOption(127)); //  no such option
 
-    // options are unique in DHCPv4. It should not be possible
+    // Options are unique in DHCPv4. It should not be possible
     // to add more than one option of the same type.
     EXPECT_THROW(
         pkt->addOption(opt1),
@@ -521,26 +495,28 @@ TEST(Pkt4Test, options) {
     );
 
     const OutputBuffer& buf = pkt->getBuffer();
-    // check that all options are stored, they should take sizeof(v4Opts),
+    // Check that all options are stored, they should take sizeof(v4Opts),
     // DHCP magic cookie (4 bytes), and OPTION_END added (just one byte)
-    ASSERT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN) + sizeof(DHCP_OPTIONS_COOKIE)
-              + sizeof(v4Opts) + 1, buf.getLength());
+    ASSERT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN) +
+              sizeof(DHCP_OPTIONS_COOKIE) + sizeof(v4Opts) + 1,
+              buf.getLength());
 
-    // that that this extra data actually contain our options
+    // That that this extra data actually contain our options
     const uint8_t* ptr = static_cast<const uint8_t*>(buf.getData());
-    ptr += Pkt4::DHCPV4_PKT_HDR_LEN + sizeof(DHCP_OPTIONS_COOKIE); // rewind to end of fixed part
+
+    // Rewind to end of fixed part.
+    ptr += Pkt4::DHCPV4_PKT_HDR_LEN + sizeof(DHCP_OPTIONS_COOKIE);
+
     EXPECT_EQ(0, memcmp(ptr, v4Opts, sizeof(v4Opts)));
     EXPECT_EQ(DHO_END, static_cast<uint8_t>(*(ptr + sizeof(v4Opts))));
 
     // delOption() checks
-    EXPECT_TRUE(pkt->getOption(12)); // Sanity check: option 12 is still there
-    EXPECT_TRUE(pkt->delOption(12)); // We should be able to remove it
+    EXPECT_TRUE(pkt->getOption(12));  // Sanity check: option 12 is still there
+    EXPECT_TRUE(pkt->delOption(12));  // We should be able to remove it
     EXPECT_FALSE(pkt->getOption(12)); // It should not be there anymore
     EXPECT_FALSE(pkt->delOption(12)); // And removal should fail
 
-    EXPECT_NO_THROW(
-        delete pkt;
-    );
+    EXPECT_NO_THROW(pkt.reset());
 }
 
 TEST(Pkt4Test, unpackOptions) {
@@ -576,42 +552,42 @@ TEST(Pkt4Test, unpackOptions) {
     EXPECT_EQ(12, x->getType());  // this should be option 12
     ASSERT_EQ(3, x->getData().size()); // it should be of length 3
     EXPECT_EQ(5, x->len()); // total option length 5
-    EXPECT_EQ(0, memcmp(&x->getData()[0], v4Opts+2, 3)); // data len=3
+    EXPECT_EQ(0, memcmp(&x->getData()[0], v4Opts + 2, 3)); // data len=3
 
     x = pkt->getOption(14);
     ASSERT_TRUE(x); // option 13 should exist
     EXPECT_EQ(14, x->getType());  // this should be option 13
     ASSERT_EQ(3, x->getData().size()); // it should be of length 3
     EXPECT_EQ(5, x->len()); // total option length 5
-    EXPECT_EQ(0, memcmp(&x->getData()[0], v4Opts+7, 3)); // data len=3
+    EXPECT_EQ(0, memcmp(&x->getData()[0], v4Opts + 7, 3)); // data len=3
 
     x = pkt->getOption(60);
     ASSERT_TRUE(x); // option 60 should exist
     EXPECT_EQ(60, x->getType());  // this should be option 60
     ASSERT_EQ(3, x->getData().size()); // it should be of length 3
     EXPECT_EQ(5, x->len()); // total option length 5
-    EXPECT_EQ(0, memcmp(&x->getData()[0], v4Opts+15, 3)); // data len=3
+    EXPECT_EQ(0, memcmp(&x->getData()[0], v4Opts + 15, 3)); // data len=3
 
     x = pkt->getOption(128);
     ASSERT_TRUE(x); // option 3 should exist
     EXPECT_EQ(128, x->getType());  // this should be option 254
     ASSERT_EQ(3, x->getData().size()); // it should be of length 3
     EXPECT_EQ(5, x->len()); // total option length 5
-    EXPECT_EQ(0, memcmp(&x->getData()[0], v4Opts+20, 3)); // data len=3
+    EXPECT_EQ(0, memcmp(&x->getData()[0], v4Opts + 20, 3)); // data len=3
 
     x = pkt->getOption(254);
     ASSERT_TRUE(x); // option 3 should exist
     EXPECT_EQ(254, x->getType());  // this should be option 254
     ASSERT_EQ(3, x->getData().size()); // it should be of length 3
     EXPECT_EQ(5, x->len()); // total option length 5
-    EXPECT_EQ(0, memcmp(&x->getData()[0], v4Opts+25, 3)); // data len=3
+    EXPECT_EQ(0, memcmp(&x->getData()[0], v4Opts + 25, 3)); // data len=3
 }
 
 // This test verifies methods that are used for manipulating meta fields
 // i.e. fields that are not part of DHCPv4 (e.g. interface name).
 TEST(Pkt4Test, metaFields) {
 
-    Pkt4* pkt = new Pkt4(DHCPOFFER, 1234);
+    scoped_ptr<Pkt4> pkt(new Pkt4(DHCPOFFER, 1234));
     pkt->setIface("loooopback");
     pkt->setIndex(42);
     pkt->setRemoteAddr(IOAddress("1.2.3.4"));
@@ -621,8 +597,6 @@ TEST(Pkt4Test, metaFields) {
     EXPECT_EQ(42, pkt->getIndex());
     EXPECT_EQ("1.2.3.4", pkt->getRemoteAddr().toText());
     EXPECT_EQ("4.3.2.1", pkt->getLocalAddr().toText());
-
-    delete pkt;
 }
 
 TEST(Pkt4Test, Timestamp) {
diff --git a/src/lib/dhcp/tests/pkt6_unittest.cc b/src/lib/dhcp/tests/pkt6_unittest.cc
index b5a745e..d4907d6 100644
--- a/src/lib/dhcp/tests/pkt6_unittest.cc
+++ b/src/lib/dhcp/tests/pkt6_unittest.cc
@@ -22,6 +22,7 @@
 #include <dhcp/option_int.h>
 #include <dhcp/option_int_array.h>
 #include <dhcp/pkt6.h>
+#include <util/range_utilities.h>
 
 #include <boost/date_time/posix_time/posix_time.hpp>
 #include <boost/scoped_ptr.hpp>
@@ -37,6 +38,7 @@ using namespace std;
 using namespace isc;
 using namespace isc::asiolink;
 using namespace isc::dhcp;
+using boost::scoped_ptr;
 
 namespace {
 // empty class for now, but may be extended once Addr6 becomes bigger
@@ -44,16 +46,26 @@ class Pkt6Test : public ::testing::Test {
 public:
     Pkt6Test() {
     }
+
+    /// @brief generates an option with given code (and length) and random content
+    ///
+    /// @param code option code
+    /// @param len data length (data will be randomized)
+    ///
+    /// @return pointer to the new option
+    OptionPtr generateRandomOption(uint16_t code, size_t len = 10) {
+        OptionBuffer data(len);
+        util::fillRandom(data.begin(), data.end());
+        return OptionPtr(new Option(Option::V6, code, data));
+    }
 };
 
 TEST_F(Pkt6Test, constructor) {
     uint8_t data[] = { 0, 1, 2, 3, 4, 5 };
-    Pkt6 * pkt1 = new Pkt6(data, sizeof(data) );
+    scoped_ptr<Pkt6> pkt1(new Pkt6(data, sizeof(data)));
 
     EXPECT_EQ(6, pkt1->getData().size());
     EXPECT_EQ(0, memcmp( &pkt1->getData()[0], data, sizeof(data)));
-
-    delete pkt1;
 }
 
 /// @brief returns captured actual SOLICIT packet
@@ -168,39 +180,36 @@ Pkt6* capture2() {
 }
 
 TEST_F(Pkt6Test, unpack_solicit1) {
-    Pkt6* sol = capture1();
+    scoped_ptr<Pkt6> sol(capture1());
 
     ASSERT_EQ(true, sol->unpack());
 
-    // check for length
+    // Check for length
     EXPECT_EQ(98, sol->len() );
 
-    // check for type
+    // Check for type
     EXPECT_EQ(DHCPV6_SOLICIT, sol->getType() );
 
-    // check that all present options are returned
+    // Check that all present options are returned
     EXPECT_TRUE(sol->getOption(D6O_CLIENTID)); // client-id is present
     EXPECT_TRUE(sol->getOption(D6O_IA_NA));    // IA_NA is present
     EXPECT_TRUE(sol->getOption(D6O_ELAPSED_TIME));  // elapsed is present
     EXPECT_TRUE(sol->getOption(D6O_NAME_SERVERS));
     EXPECT_TRUE(sol->getOption(D6O_ORO));
 
-    // let's check that non-present options are not returned
+    // Let's check that non-present options are not returned
     EXPECT_FALSE(sol->getOption(D6O_SERVERID)); // server-id is missing
     EXPECT_FALSE(sol->getOption(D6O_IA_TA));
     EXPECT_FALSE(sol->getOption(D6O_IAADDR));
-
-    delete sol;
 }
 
 TEST_F(Pkt6Test, packUnpack) {
-
-    Pkt6* parent = new Pkt6(DHCPV6_SOLICIT, 0x020304);
+    scoped_ptr<Pkt6> parent(new Pkt6(DHCPV6_SOLICIT, 0x020304));
 
     OptionPtr opt1(new Option(Option::V6, 1));
     OptionPtr opt2(new Option(Option::V6, 2));
     OptionPtr opt3(new Option(Option::V6, 100));
-    // let's not use zero-length option type 3 as it is IA_NA
+    // Let's not use zero-length option type 3 as it is IA_NA
 
     parent->addOption(opt1);
     parent->addOption(opt2);
@@ -208,7 +217,7 @@ TEST_F(Pkt6Test, packUnpack) {
 
     EXPECT_EQ(DHCPV6_SOLICIT, parent->getType());
 
-    // calculated length should be 16
+    // Calculated length should be 16
     EXPECT_EQ(Pkt6::DHCPV6_PKT_HDR_LEN + 3 * Option::OPTION6_HDR_LEN,
               parent->len());
 
@@ -217,30 +226,28 @@ TEST_F(Pkt6Test, packUnpack) {
     EXPECT_EQ(Pkt6::DHCPV6_PKT_HDR_LEN + 3 * Option::OPTION6_HDR_LEN,
               parent->len());
 
-    // create second packet,based on assembled data from the first one
-    Pkt6* clone = new Pkt6(static_cast<const uint8_t*>(parent->getBuffer().getData()),
-                           parent->getBuffer().getLength());
+    // Create second packet,based on assembled data from the first one
+    scoped_ptr<Pkt6> clone(new Pkt6(
+        static_cast<const uint8_t*>(parent->getBuffer().getData()),
+                                    parent->getBuffer().getLength()));
 
-    // now recreate options list
+    // Now recreate options list
     EXPECT_TRUE( clone->unpack() );
 
     // transid, message-type should be the same as before
     EXPECT_EQ(parent->getTransid(), parent->getTransid());
     EXPECT_EQ(DHCPV6_SOLICIT, clone->getType());
 
-    EXPECT_TRUE( clone->getOption(1));
-    EXPECT_TRUE( clone->getOption(2));
-    EXPECT_TRUE( clone->getOption(100));
-    EXPECT_FALSE( clone->getOption(4));
-
-    delete parent;
-    delete clone;
+    EXPECT_TRUE(clone->getOption(1));
+    EXPECT_TRUE(clone->getOption(2));
+    EXPECT_TRUE(clone->getOption(100));
+    EXPECT_FALSE(clone->getOption(4));
 }
 
 // This test verifies that options can be added (addOption()), retrieved
 // (getOption(), getOptions()) and deleted (delOption()).
 TEST_F(Pkt6Test, addGetDelOptions) {
-    Pkt6* parent = new Pkt6(DHCPV6_SOLICIT, random() );
+    scoped_ptr<Pkt6> parent(new Pkt6(DHCPV6_SOLICIT, random()));
 
     OptionPtr opt1(new Option(Option::V6, 1));
     OptionPtr opt2(new Option(Option::V6, 2));
@@ -253,16 +260,16 @@ TEST_F(Pkt6Test, addGetDelOptions) {
     EXPECT_EQ(opt1, parent->getOption(1));
     EXPECT_EQ(opt2, parent->getOption(2));
 
-    // expect NULL
+    // Expect NULL
     EXPECT_EQ(OptionPtr(), parent->getOption(4));
 
-    // now there are 2 options of type 2
+    // Now there are 2 options of type 2
     parent->addOption(opt3);
 
     Option::OptionCollection options = parent->getOptions(2);
     EXPECT_EQ(2, options.size()); // there should be 2 instances
 
-    // both options must be of type 2 and there must not be
+    // Both options must be of type 2 and there must not be
     // any other type returned
     for (Option::OptionCollection::const_iterator x= options.begin();
          x != options.end(); ++x) {
@@ -278,26 +285,24 @@ TEST_F(Pkt6Test, addGetDelOptions) {
     EXPECT_EQ(1, (*options.begin()).second->getType());
     EXPECT_EQ(opt1, options.begin()->second);
 
-    // let's delete one of them
+    // Let's delete one of them
     EXPECT_EQ(true, parent->delOption(2));
 
-    // there still should be the other option 2
+    // There still should be the other option 2
     EXPECT_NE(OptionPtr(), parent->getOption(2));
 
-    // let's delete the other option 2
+    // Let's delete the other option 2
     EXPECT_EQ(true, parent->delOption(2));
 
-    // no more options with type=2
+    // No more options with type=2
     EXPECT_EQ(OptionPtr(), parent->getOption(2));
 
-    // let's try to delete - should fail
+    // Let's try to delete - should fail
     EXPECT_TRUE(false ==  parent->delOption(2));
 
     // Finally try to get a non-existent option
     options = parent->getOptions(1234);
     EXPECT_EQ(0, options.size());
-
-    delete parent;
 }
 
 TEST_F(Pkt6Test, Timestamp) {
@@ -388,7 +393,7 @@ TEST_F(Pkt6Test, relayUnpack) {
 
     OptionPtr opt;
 
-    // part 1: Check options inserted by the first relay
+    // Part 1: Check options inserted by the first relay
 
     // There should be 2 options in first relay
     EXPECT_EQ(2, msg->relay_info_[0].options_.size());
@@ -401,7 +406,7 @@ TEST_F(Pkt6Test, relayUnpack) {
     // That's a strange interface-id, but this is a real life example
     EXPECT_TRUE(0 == memcmp("ISAM144|299|ipv6|nt:vp:1:110", &data[0], 28));
 
-    // get the remote-id option
+    // Get the remote-id option
     ASSERT_TRUE(opt = msg->getRelayOption(D6O_REMOTE_ID, 0));
     EXPECT_EQ(22, opt->len()); // 18 bytes of data + 4 bytes header
     boost::shared_ptr<OptionCustom> custom = boost::dynamic_pointer_cast<OptionCustom>(opt);
@@ -415,16 +420,16 @@ TEST_F(Pkt6Test, relayUnpack) {
     ASSERT_EQ(sizeof(expected_remote_id), remote_id.size());
     ASSERT_EQ(0, memcmp(expected_remote_id, &remote_id[0], remote_id.size()));
 
-    // part 2: Check options inserted by the second relay
+    // Part 2: Check options inserted by the second relay
 
-    // get the interface-id from the second relay
+    // Get the interface-id from the second relay
     ASSERT_TRUE(opt = msg->getRelayOption(D6O_INTERFACE_ID, 1));
     data = opt->getData();
     EXPECT_EQ(25, opt->len()); // 21 bytes + 4 bytes header
     EXPECT_EQ(data.size(), 21);
     EXPECT_TRUE(0 == memcmp("ISAM144 eth 1/1/05/01", &data[0], 21));
 
-    // get the remote-id option
+    // Get the remote-id option
     ASSERT_TRUE(opt = msg->getRelayOption(D6O_REMOTE_ID, 1));
     EXPECT_EQ(8, opt->len());
     custom = boost::dynamic_pointer_cast<OptionCustom>(opt);
@@ -481,7 +486,7 @@ TEST_F(Pkt6Test, relayUnpack) {
 // packed and then unpacked.
 TEST_F(Pkt6Test, relayPack) {
 
-    boost::scoped_ptr<Pkt6> parent(new Pkt6(DHCPV6_ADVERTISE, 0x020304));
+    scoped_ptr<Pkt6> parent(new Pkt6(DHCPV6_ADVERTISE, 0x020304));
 
     Pkt6::RelayInfo relay1;
     relay1.msg_type_ = DHCPV6_RELAY_REPL;
@@ -490,16 +495,17 @@ TEST_F(Pkt6Test, relayPack) {
     relay1.peeraddr_ = IOAddress("fe80::abcd");
 
     uint8_t relay_opt_data[] = { 1, 2, 3, 4, 5, 6, 7, 8};
-    vector<uint8_t> relay_data(relay_opt_data, relay_opt_data + sizeof(relay_opt_data));
+    vector<uint8_t> relay_data(relay_opt_data,
+                               relay_opt_data + sizeof(relay_opt_data));
 
     OptionPtr optRelay1(new Option(Option::V6, 200, relay_data));
 
-    relay1.options_.insert(pair<int, boost::shared_ptr<Option> >(optRelay1->getType(), optRelay1));
+    relay1.options_.insert(make_pair(optRelay1->getType(), optRelay1));
 
     OptionPtr opt1(new Option(Option::V6, 100));
     OptionPtr opt2(new Option(Option::V6, 101));
     OptionPtr opt3(new Option(Option::V6, 102));
-    // let's not use zero-length option type 3 as it is IA_NA
+    // Let's not use zero-length option type 3 as it is IA_NA
 
     parent->addRelayInfo(relay1);
 
@@ -512,17 +518,17 @@ TEST_F(Pkt6Test, relayPack) {
     EXPECT_TRUE(parent->pack());
 
     EXPECT_EQ(Pkt6::DHCPV6_PKT_HDR_LEN + 3 * Option::OPTION6_HDR_LEN // ADVERTISE
-              + Pkt6::DHCPV6_RELAY_HDR_LEN // relay header
-              + Option::OPTION6_HDR_LEN // relay-msg
+              + Pkt6::DHCPV6_RELAY_HDR_LEN // Relay header
+              + Option::OPTION6_HDR_LEN // Relay-msg
               + optRelay1->len(),
               parent->len());
 
-    // create second packet,based on assembled data from the first one
-    boost::scoped_ptr<Pkt6> clone(new Pkt6(static_cast<const uint8_t*>(
-                                           parent->getBuffer().getData()),
-                                           parent->getBuffer().getLength()));
+    // Create second packet,based on assembled data from the first one
+    scoped_ptr<Pkt6> clone(new Pkt6(static_cast<const uint8_t*>(
+                                    parent->getBuffer().getData()),
+                                    parent->getBuffer().getLength()));
 
-    // now recreate options list
+    // Now recreate options list
     EXPECT_TRUE( clone->unpack() );
 
     // transid, message-type should be the same as before
@@ -552,4 +558,112 @@ TEST_F(Pkt6Test, relayPack) {
     EXPECT_EQ(0, memcmp(relay_opt_data, relay_opt_data, sizeof(relay_opt_data)));
 }
 
+
+// This test verified that options added by relays to the message can be
+// accessed and retrieved properly
+TEST_F(Pkt6Test, getAnyRelayOption) {
+
+    boost::scoped_ptr<Pkt6> msg(new Pkt6(DHCPV6_ADVERTISE, 0x020304));
+    msg->addOption(generateRandomOption(300));
+
+    // generate options for relay1
+    Pkt6::RelayInfo relay1;
+
+    // generate 3 options with code 200,201,202 and random content
+    OptionPtr relay1_opt1(generateRandomOption(200));
+    OptionPtr relay1_opt2(generateRandomOption(201));
+    OptionPtr relay1_opt3(generateRandomOption(202));
+
+    relay1.options_.insert(make_pair(200, relay1_opt1));
+    relay1.options_.insert(make_pair(201, relay1_opt2));
+    relay1.options_.insert(make_pair(202, relay1_opt3));
+    msg->addRelayInfo(relay1);
+
+    // generate options for relay2
+    Pkt6::RelayInfo relay2;
+    OptionPtr relay2_opt1(new Option(Option::V6, 100));
+    OptionPtr relay2_opt2(new Option(Option::V6, 101));
+    OptionPtr relay2_opt3(new Option(Option::V6, 102));
+    OptionPtr relay2_opt4(new Option(Option::V6, 200)); // the same code as relay1_opt3
+    relay2.options_.insert(make_pair(100, relay2_opt1));
+    relay2.options_.insert(make_pair(101, relay2_opt2));
+    relay2.options_.insert(make_pair(102, relay2_opt3));
+    relay2.options_.insert(make_pair(200, relay2_opt4));
+    msg->addRelayInfo(relay2);
+
+    // generate options for relay3
+    Pkt6::RelayInfo relay3;
+    OptionPtr relay3_opt1(generateRandomOption(200, 7));
+    relay3.options_.insert(make_pair(200, relay3_opt1));
+    msg->addRelayInfo(relay3);
+
+    // Ok, so we now have a packet that traversed the following network:
+    // client---relay3---relay2---relay1---server
+
+    // First check that the getAnyRelayOption does not confuse client options
+    // and relay options
+    // 300 is a client option, present in the message itself.
+    OptionPtr opt = msg->getAnyRelayOption(300, Pkt6::RELAY_SEARCH_FROM_CLIENT);
+    EXPECT_FALSE(opt);
+    opt = msg->getAnyRelayOption(300, Pkt6::RELAY_SEARCH_FROM_SERVER);
+    EXPECT_FALSE(opt);
+    opt = msg->getAnyRelayOption(300, Pkt6::RELAY_GET_FIRST);
+    EXPECT_FALSE(opt);
+    opt = msg->getAnyRelayOption(300, Pkt6::RELAY_GET_LAST);
+    EXPECT_FALSE(opt);
+
+    // Option 200 is added in every relay.
+
+    // We want to get that one inserted by relay3 (first match, starting from
+    // closest to the client.
+    opt = msg->getAnyRelayOption(200, Pkt6::RELAY_SEARCH_FROM_CLIENT);
+    ASSERT_TRUE(opt);
+    EXPECT_TRUE(opt->equal(relay3_opt1));
+
+    // We want to ge that one inserted by relay1 (first match, starting from
+    // closest to the server.
+    opt = msg->getAnyRelayOption(200, Pkt6::RELAY_SEARCH_FROM_SERVER);
+    ASSERT_TRUE(opt);
+    EXPECT_TRUE(opt->equal(relay1_opt1));
+
+    // We just want option from the first relay (closest to the client)
+    opt = msg->getAnyRelayOption(200, Pkt6::RELAY_GET_FIRST);
+    ASSERT_TRUE(opt);
+    EXPECT_TRUE(opt->equal(relay3_opt1));
+
+    // We just want option from the last relay (closest to the server)
+    opt = msg->getAnyRelayOption(200, Pkt6::RELAY_GET_LAST);
+    ASSERT_TRUE(opt);
+    EXPECT_TRUE(opt->equal(relay1_opt1));
+
+    // Let's try to ask for something that is inserted by the middle relay
+    // only.
+    opt = msg->getAnyRelayOption(100, Pkt6::RELAY_SEARCH_FROM_SERVER);
+    ASSERT_TRUE(opt);
+    EXPECT_TRUE(opt->equal(relay2_opt1));
+
+    opt = msg->getAnyRelayOption(100, Pkt6::RELAY_SEARCH_FROM_CLIENT);
+    ASSERT_TRUE(opt);
+    EXPECT_TRUE(opt->equal(relay2_opt1));
+
+    opt = msg->getAnyRelayOption(100, Pkt6::RELAY_GET_FIRST);
+    EXPECT_FALSE(opt);
+
+    opt = msg->getAnyRelayOption(100, Pkt6::RELAY_GET_LAST);
+    EXPECT_FALSE(opt);
+
+    // Finally, try to get an option that does not exist
+    opt = msg->getAnyRelayOption(500, Pkt6::RELAY_GET_FIRST);
+    EXPECT_FALSE(opt);
+
+    opt = msg->getAnyRelayOption(500, Pkt6::RELAY_GET_LAST);
+    EXPECT_FALSE(opt);
+
+    opt = msg->getAnyRelayOption(500, Pkt6::RELAY_SEARCH_FROM_SERVER);
+    EXPECT_FALSE(opt);
+
+    opt = msg->getAnyRelayOption(500, Pkt6::RELAY_SEARCH_FROM_CLIENT);
+    EXPECT_FALSE(opt);
+}
+
 }
diff --git a/src/lib/dhcpsrv/cfgmgr.cc b/src/lib/dhcpsrv/cfgmgr.cc
index b5e83e3..592efb7 100644
--- a/src/lib/dhcpsrv/cfgmgr.cc
+++ b/src/lib/dhcpsrv/cfgmgr.cc
@@ -40,8 +40,7 @@ CfgMgr::addOptionSpace4(const OptionSpacePtr& space) {
         isc_throw(InvalidOptionSpace, "option space " << space->getName()
                   << " already added.");
     }
-    spaces4_.insert(std::pair<std::string,
-                              OptionSpacePtr>(space->getName(), space));
+    spaces4_.insert(make_pair(space->getName(), space));
 }
 
 void
@@ -55,8 +54,7 @@ CfgMgr::addOptionSpace6(const OptionSpacePtr& space) {
         isc_throw(InvalidOptionSpace, "option space " << space->getName()
                   << " already added.");
     }
-    spaces6_.insert(std::pair<std::string,
-                              OptionSpacePtr>(space->getName(), space));
+    spaces6_.insert(make_pair(space->getName(), space));
 }
 
 void
@@ -147,7 +145,7 @@ CfgMgr::getSubnet6(const isc::asiolink::IOAddress& hint) {
 
     // If there's only one subnet configured, let's just use it
     // The idea is to keep small deployments easy. In a small network - one
-    // router that also runs DHCPv6 server. Users specifies a single pool and
+    // router that also runs DHCPv6 server. User specifies a single pool and
     // expects it to just work. Without this, the server would complain that it
     // doesn't have IP address on its interfaces that matches that
     // configuration. Such requirement makes sense in IPv4, but not in IPv6.
@@ -178,14 +176,30 @@ CfgMgr::getSubnet6(const isc::asiolink::IOAddress& hint) {
     return (Subnet6Ptr());
 }
 
-Subnet6Ptr CfgMgr::getSubnet6(OptionPtr /*interfaceId*/) {
-    /// @todo: Implement get subnet6 by interface-id (for relayed traffic)
-    isc_throw(NotImplemented, "Relayed DHCPv6 traffic is not supported yet.");
+Subnet6Ptr CfgMgr::getSubnet6(OptionPtr iface_id_option) {
+    if (!iface_id_option) {
+        return (Subnet6Ptr());
+    }
+
+    // Let's iterate over all subnets and for those that have interface-id
+    // defined, check if the interface-id is equal to what we are looking for
+    for (Subnet6Collection::iterator subnet = subnets6_.begin();
+         subnet != subnets6_.end(); ++subnet) {
+        if ( (*subnet)->getInterfaceId() &&
+             ((*subnet)->getInterfaceId()->equal(iface_id_option))) {
+            LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+                      DHCPSRV_CFGMGR_SUBNET6_IFACE_ID)
+                .arg((*subnet)->toText());
+            return (*subnet);
+        }
+    }
+    return (Subnet6Ptr());
 }
 
 void CfgMgr::addSubnet6(const Subnet6Ptr& subnet) {
     /// @todo: Check that this new subnet does not cross boundaries of any
     /// other already defined subnet.
+    /// @todo: Check that there is no subnet with the same interface-id
     LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_ADD_SUBNET6)
               .arg(subnet->toText());
     subnets6_.push_back(subnet);
diff --git a/src/lib/dhcpsrv/cfgmgr.h b/src/lib/dhcpsrv/cfgmgr.h
index 00f7e25..05c1752 100644
--- a/src/lib/dhcpsrv/cfgmgr.h
+++ b/src/lib/dhcpsrv/cfgmgr.h
@@ -174,7 +174,6 @@ public:
     /// @param interface_id content of interface-id option returned by a relay
     ///
     /// @return a subnet object
-    /// @todo This method is not currently supported.
     Subnet6Ptr getSubnet6(OptionPtr interface_id);
 
     /// @brief adds an IPv6 subnet
diff --git a/src/lib/dhcpsrv/dhcpsrv_messages.mes b/src/lib/dhcpsrv/dhcpsrv_messages.mes
index 9b8a0ce..b2a7807 100644
--- a/src/lib/dhcpsrv/dhcpsrv_messages.mes
+++ b/src/lib/dhcpsrv/dhcpsrv_messages.mes
@@ -105,7 +105,14 @@ This is a debug message reporting that the DHCP configuration manager
 has returned the specified IPv6 subnet for a packet received over
 given interface.  This particular subnet was selected, because it
 was specified as being directly reachable over given interface. (see
-'interface' parameter in subnet6 definition).
+'interface' parameter in the subnet6 definition).
+
+% DHCPSRV_CFGMGR_SUBNET6_IFACE_ID selected subnet %1 (interface-id match) for incoming packet
+This is a debug message reporting that the DHCP configuration manager
+has returned the specified IPv6 subnet for a received packet. This particular
+subnet was selected, because value of interface-id option matched what was
+configured in server's interface-id option for that selected subnet6.
+(see 'interface-id' parameter in the subnet6 definition).
 
 % DHCPSRV_CLOSE_DB closing currently open %1 database
 This is a debug message, issued when the DHCP server closes the currently
diff --git a/src/lib/dhcpsrv/subnet.h b/src/lib/dhcpsrv/subnet.h
index 4c7cbcc..107ff1d 100644
--- a/src/lib/dhcpsrv/subnet.h
+++ b/src/lib/dhcpsrv/subnet.h
@@ -463,6 +463,19 @@ public:
     /// @return network interface name for directly attached subnets or ""
     std::string getIface() const;
 
+    /// @brief sets interface-id option (if defined)
+    ///
+    /// @param ifaceid pointer to interface-id option
+    void setInterfaceId(const OptionPtr& ifaceid) {
+        interface_id_ = ifaceid;
+    }
+
+    /// @brief returns interface-id value (if specified)
+    /// @return interface-id option (if defined)
+    OptionPtr getInterfaceId() const {
+        return interface_id_;
+    }
+
 protected:
 
     /// @brief Check if option is valid and can be added to a subnet.
@@ -478,6 +491,9 @@ protected:
         return (isc::asiolink::IOAddress("::"));
     }
 
+    /// @brief specifies optional interface-id
+    OptionPtr interface_id_;
+
     /// @brief collection of pools in that list
     Pool6Collection pools_;
 
diff --git a/src/lib/dhcpsrv/tests/alloc_engine_unittest.cc b/src/lib/dhcpsrv/tests/alloc_engine_unittest.cc
index 144d7b5..05f3741 100644
--- a/src/lib/dhcpsrv/tests/alloc_engine_unittest.cc
+++ b/src/lib/dhcpsrv/tests/alloc_engine_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -149,12 +149,12 @@ public:
     ///
     /// @param lease lease to be checked
     void checkLease4(const Lease4Ptr& lease) {
-        // that is belongs to the right subnet
+        // Check that is belongs to the right subnet
         EXPECT_EQ(lease->subnet_id_, subnet_->getID());
         EXPECT_TRUE(subnet_->inRange(lease->addr_));
         EXPECT_TRUE(subnet_->inPool(lease->addr_));
 
-        // that it have proper parameters
+        // Check that it has proper parameters
         EXPECT_EQ(subnet_->getValid(), lease->valid_lft_);
         EXPECT_EQ(subnet_->getT1(), lease->t1_);
         EXPECT_EQ(subnet_->getT2(), lease->t2_);
@@ -177,11 +177,11 @@ public:
         factory_.destroy();
     }
 
-    ClientIdPtr clientid_;    ///< client-identifier (value used in tests)
-    HWAddrPtr hwaddr_;        ///< hardware address (value used in tests)
-    Subnet4Ptr subnet_;       ///< subnet4 (used in tests)
-    Pool4Ptr pool_;           ///< pool belonging to subnet_
-    LeaseMgrFactory factory_; ///< pointer to LeaseMgr factory
+    ClientIdPtr clientid_;    ///< Client-identifier (value used in tests)
+    HWAddrPtr hwaddr_;        ///< Hardware address (value used in tests)
+    Subnet4Ptr subnet_;       ///< Subnet4 (used in tests)
+    Pool4Ptr pool_;           ///< Pool belonging to subnet_
+    LeaseMgrFactory factory_; ///< Pointer to LeaseMgr factory
 };
 
 // This test checks if the Allocation Engine can be instantiated and that it
@@ -205,10 +205,10 @@ TEST_F(AllocEngine6Test, simpleAlloc6) {
     Lease6Ptr lease = engine->allocateAddress6(subnet_, duid_, iaid_, IOAddress("::"),
                                                false);
 
-    // check that we got a lease
+    // Check that we got a lease
     ASSERT_TRUE(lease);
 
-    // do all checks on the lease
+    // Do all checks on the lease
     checkLease6(lease);
 
     // Check that the lease is indeed in LeaseMgr
@@ -228,10 +228,10 @@ TEST_F(AllocEngine6Test, fakeAlloc6) {
     Lease6Ptr lease = engine->allocateAddress6(subnet_, duid_, iaid_, IOAddress("::"),
                                                true);
 
-    // check that we got a lease
+    // Check that we got a lease
     ASSERT_TRUE(lease);
 
-    // do all checks on the lease
+    // Do all checks on the lease
     checkLease6(lease);
 
     // Check that the lease is NOT in LeaseMgr
@@ -250,13 +250,13 @@ TEST_F(AllocEngine6Test, allocWithValidHint6) {
                                                IOAddress("2001:db8:1::15"),
                                                false);
 
-    // check that we got a lease
+    // Check that we got a lease
     ASSERT_TRUE(lease);
 
-    // we should get what we asked for
+    // We should get what we asked for
     EXPECT_EQ(lease->addr_.toText(), "2001:db8:1::15");
 
-    // do all checks on the lease
+    // Do all checks on the lease
     checkLease6(lease);
 
     // Check that the lease is indeed in LeaseMgr
@@ -274,29 +274,29 @@ TEST_F(AllocEngine6Test, allocWithUsedHint6) {
     ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
     ASSERT_TRUE(engine);
 
-    // let's create a lease and put it in the LeaseMgr
+    // Let's create a lease and put it in the LeaseMgr
     DuidPtr duid2 = boost::shared_ptr<DUID>(new DUID(vector<uint8_t>(8, 0xff)));
     time_t now = time(NULL);
     Lease6Ptr used(new Lease6(Lease6::LEASE_IA_NA, IOAddress("2001:db8:1::1f"),
                               duid2, 1, 2, 3, 4, now, subnet_->getID()));
     ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used));
 
-    // another client comes in and request an address that is in pool, but
+    // Another client comes in and request an address that is in pool, but
     // unfortunately it is used already. The same address must not be allocated
     // twice.
     Lease6Ptr lease = engine->allocateAddress6(subnet_, duid_, iaid_,
                                                IOAddress("2001:db8:1::1f"),
                                                false);
-    // check that we got a lease
+    // Check that we got a lease
     ASSERT_TRUE(lease);
 
-    // allocated address must be different
+    // Allocated address must be different
     EXPECT_TRUE(used->addr_.toText() != lease->addr_.toText());
 
-    // we should NOT get what we asked for, because it is used already
+    // We should NOT get what we asked for, because it is used already
     EXPECT_TRUE(lease->addr_.toText() != "2001:db8:1::1f");
 
-    // do all checks on the lease
+    // Do all checks on the lease
     checkLease6(lease);
 
     // Check that the lease is indeed in LeaseMgr
@@ -320,13 +320,13 @@ TEST_F(AllocEngine6Test, allocBogusHint6) {
     Lease6Ptr lease = engine->allocateAddress6(subnet_, duid_, iaid_,
                                                IOAddress("3000::abc"),
                                                false);
-    // check that we got a lease
+    // Check that we got a lease
     ASSERT_TRUE(lease);
 
-    // we should NOT get what we asked for, because it is used already
+    // We should NOT get what we asked for, because it is used already
     EXPECT_TRUE(lease->addr_.toText() != "3000::abc");
 
-    // do all checks on the lease
+    // Do all checks on the lease
     checkLease6(lease);
 
     // Check that the lease is indeed in LeaseMgr
@@ -372,7 +372,7 @@ TEST_F(AllocEngine6Test, IterativeAllocator) {
 // in all pools in specified subnet. It also must not pick the same address twice
 // unless it runs out of pool space and must start over.
 TEST_F(AllocEngine6Test, IterativeAllocator_manyPools6) {
-    NakedAllocEngine::IterativeAllocator* alloc = new NakedAllocEngine::IterativeAllocator();
+    NakedAllocEngine::IterativeAllocator alloc;
 
     // let's start from 2, as there is 2001:db8:1::10 - 2001:db8:1::20 pool already.
     for (int i = 2; i < 10; ++i) {
@@ -383,18 +383,17 @@ TEST_F(AllocEngine6Test, IterativeAllocator_manyPools6) {
 
         Pool6Ptr pool(new Pool6(Pool6::TYPE_IA, IOAddress(min.str()),
                                 IOAddress(max.str())));
-        // cout << "Adding pool: " << min.str() << "-" << max.str() << endl;
         subnet_->addPool(pool);
     }
 
-    int total = 17 + 8*9; // first pool (::10 - ::20) has 17 addresses in it,
-                          // there are 8 extra pools with 9 addresses in each.
+    int total = 17 + 8 * 9; // First pool (::10 - ::20) has 17 addresses in it,
+                            // there are 8 extra pools with 9 addresses in each.
 
     // Let's keep picked addresses here and check their uniqueness.
     std::set<IOAddress> generated_addrs;
     int cnt = 0;
     while (++cnt) {
-        IOAddress candidate = alloc->pickAddress(subnet_, duid_, IOAddress("::"));
+        IOAddress candidate = alloc.pickAddress(subnet_, duid_, IOAddress("::"));
         EXPECT_TRUE(subnet_->inPool(candidate));
 
         // One way to easily verify that the iterative allocator really works is
@@ -403,13 +402,13 @@ TEST_F(AllocEngine6Test, IterativeAllocator_manyPools6) {
         // cout << candidate.toText() << endl;
 
         if (generated_addrs.find(candidate) == generated_addrs.end()) {
-            // we haven't had this
+            // We haven't had this.
             generated_addrs.insert(candidate);
         } else {
-            // we have seen this address before. That should mean that we
+            // We have seen this address before. That should mean that we
             // iterated over all addresses.
             if (generated_addrs.size() == total) {
-                // we have exactly the number of address in all pools
+                // We have exactly the number of address in all pools.
                 break;
             }
             ADD_FAILURE() << "Too many or not enough unique addresses generated.";
@@ -421,8 +420,6 @@ TEST_F(AllocEngine6Test, IterativeAllocator_manyPools6) {
             break;
         }
     }
-
-    delete alloc;
 }
 
 // This test checks if really small pools are working
@@ -449,7 +446,7 @@ TEST_F(AllocEngine6Test, smallPool6) {
 
     EXPECT_EQ("2001:db8:1::ad", lease->addr_.toText());
 
-    // do all checks on the lease
+    // Do all checks on the lease
     checkLease6(lease);
 
     // Check that the lease is indeed in LeaseMgr
@@ -782,7 +779,7 @@ TEST_F(AllocEngine4Test, IterativeAllocator) {
 // in all pools in specified subnet. It also must not pick the same address twice
 // unless it runs out of pool space and must start over.
 TEST_F(AllocEngine4Test, IterativeAllocator_manyPools4) {
-    NakedAllocEngine::IterativeAllocator* alloc = new NakedAllocEngine::IterativeAllocator();
+    NakedAllocEngine::IterativeAllocator alloc;
 
     // Let's start from 2, as there is 2001:db8:1::10 - 2001:db8:1::20 pool already.
     for (int i = 2; i < 10; ++i) {
@@ -804,7 +801,7 @@ TEST_F(AllocEngine4Test, IterativeAllocator_manyPools4) {
     std::set<IOAddress> generated_addrs;
     int cnt = 0;
     while (++cnt) {
-        IOAddress candidate = alloc->pickAddress(subnet_, clientid_, IOAddress("0.0.0.0"));
+        IOAddress candidate = alloc.pickAddress(subnet_, clientid_, IOAddress("0.0.0.0"));
         EXPECT_TRUE(subnet_->inPool(candidate));
 
         // One way to easily verify that the iterative allocator really works is
@@ -816,10 +813,10 @@ TEST_F(AllocEngine4Test, IterativeAllocator_manyPools4) {
             // We haven't had this
             generated_addrs.insert(candidate);
         } else {
-            // we have seen this address before. That should mean that we
+            // We have seen this address before. That should mean that we
             // iterated over all addresses.
             if (generated_addrs.size() == total) {
-                // we have exactly the number of address in all pools
+                // We have exactly the number of address in all pools
                 break;
             }
             ADD_FAILURE() << "Too many or not enough unique addresses generated.";
@@ -831,8 +828,6 @@ TEST_F(AllocEngine4Test, IterativeAllocator_manyPools4) {
             break;
         }
     }
-
-    delete alloc;
 }
 
 
@@ -964,7 +959,7 @@ TEST_F(AllocEngine4Test, requestReuseExpiredLease4) {
     time_t now = time(NULL) - 500; // Allocated 500 seconds ago
     Lease4Ptr lease(new Lease4(addr, clientid2, sizeof(clientid2), hwaddr2, sizeof(hwaddr2),
                                495, 100, 200, now, subnet_->getID()));
-    // lease was assigned 500 seconds ago, but its valid lifetime is 495, so it
+    // Lease was assigned 500 seconds ago, but its valid lifetime is 495, so it
     // is expired already
     ASSERT_TRUE(lease->expired());
     ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
@@ -1025,4 +1020,4 @@ TEST_F(AllocEngine4Test, renewLease4) {
     detailCompareLease(lease, from_mgr);
 }
 
-}; // end of anonymous namespace
+}; // End of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc b/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc
index be31bab..ba2f290 100644
--- a/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc
+++ b/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc
@@ -17,6 +17,7 @@
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/dhcp_config_parser.h>
 #include <exceptions/exceptions.h>
+#include <dhcp/dhcp6.h>
 
 #include <gtest/gtest.h>
 
@@ -37,7 +38,7 @@ using boost::scoped_ptr;
 
 namespace {
 
-// This test verifies that BooleanStorage functions properly. 
+// This test verifies that BooleanStorage functions properly.
 TEST(ValueStorageTest, BooleanTesting) {
     BooleanStorage testStore;
 
@@ -48,7 +49,7 @@ TEST(ValueStorageTest, BooleanTesting) {
     EXPECT_FALSE(testStore.getParam("firstBool"));
     EXPECT_TRUE(testStore.getParam("secondBool"));
 
-    // Verify that we can update paramaters. 
+    // Verify that we can update parameters.
     testStore.setParam("firstBool", true);
     testStore.setParam("secondBool", false);
 
@@ -65,7 +66,7 @@ TEST(ValueStorageTest, BooleanTesting) {
     // Verify that looking for a parameter that never existed throws.
     ASSERT_THROW(testStore.getParam("bogusBool"), isc::dhcp::DhcpConfigError);
 
-    // Verify that attempting to delete a parameter that never existed does not throw. 
+    // Verify that attempting to delete a parameter that never existed does not throw.
     EXPECT_NO_THROW(testStore.delParam("bogusBool"));
 
     // Verify that we can empty the list.
@@ -74,21 +75,21 @@ TEST(ValueStorageTest, BooleanTesting) {
 
 }
 
-// This test verifies that Uint32Storage functions properly. 
+// This test verifies that Uint32Storage functions properly.
 TEST(ValueStorageTest, Uint32Testing) {
     Uint32Storage testStore;
 
     uint32_t intOne = 77;
     uint32_t intTwo = 33;
 
-    // Verify that we can add and retrieve parameters. 
+    // Verify that we can add and retrieve parameters.
     testStore.setParam("firstInt", intOne);
     testStore.setParam("secondInt", intTwo);
 
     EXPECT_EQ(testStore.getParam("firstInt"), intOne);
     EXPECT_EQ(testStore.getParam("secondInt"), intTwo);
 
-    // Verify that we can update parameters. 
+    // Verify that we can update parameters.
     testStore.setParam("firstInt", --intOne);
     testStore.setParam("secondInt", ++intTwo);
 
@@ -105,7 +106,7 @@ TEST(ValueStorageTest, Uint32Testing) {
     // Verify that looking for a parameter that never existed throws.
     ASSERT_THROW(testStore.getParam("bogusInt"), isc::dhcp::DhcpConfigError);
 
-    // Verify that attempting to delete a parameter that never existed does not throw. 
+    // Verify that attempting to delete a parameter that never existed does not throw.
     EXPECT_NO_THROW(testStore.delParam("bogusInt"));
 
     // Verify that we can empty the list.
@@ -113,7 +114,7 @@ TEST(ValueStorageTest, Uint32Testing) {
     EXPECT_THROW(testStore.getParam("secondInt"), isc::dhcp::DhcpConfigError);
 }
 
-// This test verifies that StringStorage functions properly. 
+// This test verifies that StringStorage functions properly.
 TEST(ValueStorageTest, StringTesting) {
     StringStorage testStore;
 
@@ -127,7 +128,7 @@ TEST(ValueStorageTest, StringTesting) {
     EXPECT_EQ(testStore.getParam("firstString"), stringOne);
     EXPECT_EQ(testStore.getParam("secondString"), stringTwo);
 
-    // Verify that we can update parameters. 
+    // Verify that we can update parameters.
     stringOne.append("-boo");
     stringTwo.append("-boo");
 
@@ -147,7 +148,7 @@ TEST(ValueStorageTest, StringTesting) {
     // Verify that looking for a parameter that never existed throws.
     ASSERT_THROW(testStore.getParam("bogusString"), isc::dhcp::DhcpConfigError);
 
-    // Verify that attempting to delete a parameter that never existed does not throw. 
+    // Verify that attempting to delete a parameter that never existed does not throw.
     EXPECT_NO_THROW(testStore.delParam("bogusString"));
 
     // Verify that we can empty the list.
@@ -165,6 +166,16 @@ public:
         CfgMgr::instance().deleteSubnets6();
     }
 
+    /// @brief generates interface-id option based on provided text
+    ///
+    /// @param text content of the option to be created
+    ///
+    /// @return pointer to the option object created
+    OptionPtr generateInterfaceId(const string& text) {
+        OptionBuffer buffer(text.begin(), text.end());
+        return OptionPtr(new Option(Option::V6, D6O_INTERFACE_ID, buffer));
+    }
+
     ~CfgMgrTest() {
         // clean up after the test
         CfgMgr::instance().deleteSubnets4();
@@ -406,6 +417,95 @@ TEST_F(CfgMgrTest, subnet6) {
     EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("4000::123")));
 }
 
+// This test verifies if the configuration manager is able to hold, select
+// and return valid subnets, based on interface names.
+TEST_F(CfgMgrTest, subnet6Interface) {
+    CfgMgr& cfg_mgr = CfgMgr::instance();
+
+    Subnet6Ptr subnet1(new Subnet6(IOAddress("2000::"), 48, 1, 2, 3, 4));
+    Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"), 48, 1, 2, 3, 4));
+    Subnet6Ptr subnet3(new Subnet6(IOAddress("4000::"), 48, 1, 2, 3, 4));
+    subnet1->setIface("foo");
+    subnet2->setIface("bar");
+    subnet3->setIface("foobar");
+
+    // There shouldn't be any subnet configured at this stage
+    EXPECT_FALSE(cfg_mgr.getSubnet6("foo"));
+
+    cfg_mgr.addSubnet6(subnet1);
+
+    // Now we have only one subnet, any request will be served from it
+    EXPECT_EQ(subnet1, cfg_mgr.getSubnet6("foo"));
+
+    // Check that the interface name is checked even when there is
+    // only one subnet defined.
+    EXPECT_FALSE(cfg_mgr.getSubnet6("bar"));
+
+    // If we have only a single subnet and the request came from a local
+    // address, let's use that subnet
+    EXPECT_EQ(subnet1, cfg_mgr.getSubnet6(IOAddress("fe80::dead:beef")));
+
+    cfg_mgr.addSubnet6(subnet2);
+    cfg_mgr.addSubnet6(subnet3);
+
+    EXPECT_EQ(subnet3, cfg_mgr.getSubnet6("foobar"));
+    EXPECT_EQ(subnet2, cfg_mgr.getSubnet6("bar"));
+    EXPECT_FALSE(cfg_mgr.getSubnet6("xyzzy")); // no such interface
+
+    // Check that deletion of the subnets works.
+    cfg_mgr.deleteSubnets6();
+    EXPECT_FALSE(cfg_mgr.getSubnet6("foo"));
+    EXPECT_FALSE(cfg_mgr.getSubnet6("bar"));
+    EXPECT_FALSE(cfg_mgr.getSubnet6("foobar"));
+}
+
+// This test verifies if the configuration manager is able to hold, select
+// and return valid leases, based on interface-id option values
+TEST_F(CfgMgrTest, subnet6InterfaceId) {
+    CfgMgr& cfg_mgr = CfgMgr::instance();
+
+    Subnet6Ptr subnet1(new Subnet6(IOAddress("2000::"), 48, 1, 2, 3, 4));
+    Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"), 48, 1, 2, 3, 4));
+    Subnet6Ptr subnet3(new Subnet6(IOAddress("4000::"), 48, 1, 2, 3, 4));
+
+    // interface-id options used in subnets 1,2, and 3
+    OptionPtr ifaceid1 = generateInterfaceId("relay1.eth0");
+    OptionPtr ifaceid2 = generateInterfaceId("VL32");
+    // That's a strange interface-id, but this is a real life example
+    OptionPtr ifaceid3 = generateInterfaceId("ISAM144|299|ipv6|nt:vp:1:110");
+
+    // bogus interface-id
+    OptionPtr ifaceid_bogus = generateInterfaceId("non-existent");
+
+    subnet1->setInterfaceId(ifaceid1);
+    subnet2->setInterfaceId(ifaceid2);
+    subnet3->setInterfaceId(ifaceid3);
+
+    // There shouldn't be any subnet configured at this stage
+    EXPECT_FALSE(cfg_mgr.getSubnet6(ifaceid1));
+
+    cfg_mgr.addSubnet6(subnet1);
+
+    // If we have only a single subnet and the request came from a local
+    // address, let's use that subnet
+    EXPECT_EQ(subnet1, cfg_mgr.getSubnet6(ifaceid1));
+    EXPECT_FALSE(cfg_mgr.getSubnet6(ifaceid2));
+
+    cfg_mgr.addSubnet6(subnet2);
+    cfg_mgr.addSubnet6(subnet3);
+
+    EXPECT_EQ(subnet3, cfg_mgr.getSubnet6(ifaceid3));
+    EXPECT_EQ(subnet2, cfg_mgr.getSubnet6(ifaceid2));
+    EXPECT_FALSE(cfg_mgr.getSubnet6(ifaceid_bogus));
+
+    // Check that deletion of the subnets works.
+    cfg_mgr.deleteSubnets6();
+    EXPECT_FALSE(cfg_mgr.getSubnet6(ifaceid1));
+    EXPECT_FALSE(cfg_mgr.getSubnet6(ifaceid2));
+    EXPECT_FALSE(cfg_mgr.getSubnet6(ifaceid3));
+}
+
+
 // This test verifies that new DHCPv4 option spaces can be added to
 // the configuration manager and that duplicated option space is
 // rejected.
diff --git a/src/lib/dhcpsrv/tests/lease_mgr_unittest.cc b/src/lib/dhcpsrv/tests/lease_mgr_unittest.cc
index 6103a96..124301a 100644
--- a/src/lib/dhcpsrv/tests/lease_mgr_unittest.cc
+++ b/src/lib/dhcpsrv/tests/lease_mgr_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -312,8 +312,8 @@ TEST(Lease4, OperatorEquals) {
 
     // Check when the leases are equal.
     Lease4 lease1(ADDRESS, HWADDR, sizeof(HWADDR),
-                  CLIENTID, sizeof(CLIENTID), VALID_LIFETIME, current_time, 0, 0,
-                  SUBNET_ID);
+                  CLIENTID, sizeof(CLIENTID), VALID_LIFETIME, current_time, 0,
+                  0, SUBNET_ID);
     Lease4 lease2(ADDRESS, HWADDR, sizeof(HWADDR),
                   CLIENTID, sizeof(CLIENTID), VALID_LIFETIME, current_time, 0, 0,
                   SUBNET_ID);
@@ -440,8 +440,8 @@ TEST(Lease6, Lease6Constructor) {
     // Other values
     uint8_t llt[] = {0, 1, 2, 3, 4, 5, 6, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf};
     DuidPtr duid(new DUID(llt, sizeof(llt)));
-    uint32_t iaid = 7; // just a number
-    SubnetID subnet_id = 8; // just another number
+    uint32_t iaid = 7;      // Just a number
+    SubnetID subnet_id = 8; // Just another number
 
     for (int i = 0; i < sizeof(ADDRESS) / sizeof(ADDRESS[0]); ++i) {
         IOAddress addr(ADDRESS[i]);
@@ -462,9 +462,10 @@ TEST(Lease6, Lease6Constructor) {
 
     // Lease6 must be instantiated with a DUID, not with NULL pointer
     IOAddress addr(ADDRESS[0]);
-    EXPECT_THROW(new Lease6(Lease6::LEASE_IA_NA, addr,
-                            DuidPtr(), iaid, 100, 200, 50, 80,
-                            subnet_id), InvalidOperation);
+    Lease6Ptr lease2;
+    EXPECT_THROW(lease2.reset(new Lease6(Lease6::LEASE_IA_NA, addr,
+                                         DuidPtr(), iaid, 100, 200, 50, 80,
+                                         subnet_id)), InvalidOperation);
 }
 
 /// @brief Lease6 Equality Test
@@ -611,21 +612,21 @@ TEST(Lease6, Lease6Expired) {
     const IOAddress addr("2001:db8:1::456");
     const uint8_t duid_array[] = {0, 1, 2, 3, 4, 5, 6, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf};
     const DuidPtr duid(new DUID(duid_array, sizeof(duid_array)));
-    const uint32_t iaid = 7; // just a number
-    const SubnetID subnet_id = 8; // just another number
+    const uint32_t iaid = 7;        // Just a number
+    const SubnetID subnet_id = 8;   // Just another number
     Lease6 lease(Lease6::LEASE_IA_NA, addr, duid, iaid, 100, 200, 50, 80,
                                subnet_id);
 
-    // case 1: a second before expiration
+    // Case 1: a second before expiration
     lease.cltt_ = time(NULL) - 100;
     lease.valid_lft_ = 101;
     EXPECT_FALSE(lease.expired());
 
-    // case 2: the lease will expire after this second is concluded
+    // Case 2: the lease will expire after this second is concluded
     lease.cltt_ = time(NULL) - 101;
     EXPECT_FALSE(lease.expired());
 
-    // case 3: the lease is expired
+    // Case 3: the lease is expired
     lease.cltt_ = time(NULL) - 102;
     EXPECT_TRUE(lease.expired());
 }
diff --git a/src/lib/dhcpsrv/tests/schema_copy.h b/src/lib/dhcpsrv/tests/schema_copy.h
index 48a11ca..9ebd057 100644
--- a/src/lib/dhcpsrv/tests/schema_copy.h
+++ b/src/lib/dhcpsrv/tests/schema_copy.h
@@ -51,6 +51,10 @@ const char* create_statement[] = {
         "subnet_id INT UNSIGNED"
         ") ENGINE = INNODB",
 
+    "CREATE INDEX lease4_by_hwaddr_subnet_id ON lease4 (hwaddr, subnet_id)",
+
+    "CREATE INDEX lease4_by_client_id_subnet_id ON lease4 (client_id, subnet_id)",
+
     "CREATE TABLE lease6 ("
         "address VARCHAR(39) PRIMARY KEY NOT NULL,"
         "duid VARBINARY(128),"
@@ -63,6 +67,8 @@ const char* create_statement[] = {
         "prefix_len TINYINT UNSIGNED"
         ") ENGINE = INNODB",
 
+    "CREATE INDEX lease6_by_iaid_subnet_id_duid ON lease6 (iaid, subnet_id, duid)",
+
     "CREATE TABLE lease6_types ("
         "lease_type TINYINT PRIMARY KEY NOT NULL,"
         "name VARCHAR(5)"
diff --git a/src/lib/dhcpsrv/tests/subnet_unittest.cc b/src/lib/dhcpsrv/tests/subnet_unittest.cc
index 1f0ef8b..fbcebcb 100644
--- a/src/lib/dhcpsrv/tests/subnet_unittest.cc
+++ b/src/lib/dhcpsrv/tests/subnet_unittest.cc
@@ -16,6 +16,7 @@
 
 #include <asiolink/io_address.h>
 #include <dhcp/option.h>
+#include <dhcp/dhcp6.h>
 #include <dhcpsrv/subnet.h>
 #include <exceptions/exceptions.h>
 
@@ -516,4 +517,19 @@ TEST(Subnet6Test, iface) {
     EXPECT_EQ("en1", subnet.getIface());
 }
 
+// This trivial test checks if the interface-id option can be set and
+// later retrieved for a subnet6 object.
+TEST(Subnet6Test, interfaceId) {
+    // Create as subnet to add options to it.
+    Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4));
+
+    EXPECT_FALSE(subnet->getInterfaceId());
+
+    OptionPtr option(new Option(Option::V6, D6O_INTERFACE_ID, OptionBuffer(10, 0xFF)));
+    subnet->setInterfaceId(option);
+
+    EXPECT_EQ(option, subnet->getInterfaceId());
+
+}
+
 };
diff --git a/src/lib/dns/gen-rdatacode.py.in b/src/lib/dns/gen-rdatacode.py.in
index a7c0cbc..3fd3b33 100755
--- a/src/lib/dns/gen-rdatacode.py.in
+++ b/src/lib/dns/gen-rdatacode.py.in
@@ -1,6 +1,6 @@
 #!@PYTHON@
 
-# Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
+# Copyright (C) 2010-2013  Internet Systems Consortium, Inc. ("ISC")
 #
 # Permission to use, copy, modify, and/or distribute this software for any
 # purpose with or without fee is hereby granted, provided that the above
@@ -24,38 +24,6 @@ from os.path import getmtime
 import re
 import sys
 
-# new_rdata_factory_users[] is a list of tuples of the form (rrtype,
-# rrclass). Items in the list use the (new) RdataFactory class, and
-# items which are not in the list use OldRdataFactory class.
-# Note: rrtype and rrclass must be specified in lowercase in
-# new_rdata_factory_users.
-#
-# Example:
-#     new_rdata_factory_users = [('a', 'in'), ('a', 'ch'), ('soa', 'generic')]
-new_rdata_factory_users = [('a', 'in'), ('aaaa', 'in'),
-                           ('afsdb', 'generic'),
-                           ('cname', 'generic'),
-                           ('dlv', 'generic'),
-                           ('dname', 'generic'),
-                           ('dnskey', 'generic'),
-                           ('ds', 'generic'),
-                           ('hinfo', 'generic'),
-                           ('minfo', 'generic'),
-                           ('mx', 'generic'),
-                           ('naptr', 'generic'),
-                           ('ns', 'generic'),
-                           ('nsec', 'generic'),
-                           ('nsec3', 'generic'),
-                           ('nsec3param', 'generic'),
-                           ('ptr', 'generic'),
-                           ('rp', 'generic'),
-                           ('soa', 'generic'),
-                           ('spf', 'generic'),
-                           ('srv', 'in'),
-                           ('tsig', 'any'),
-                           ('txt', 'generic')
-                          ]
-
 re_typecode = re.compile('([\da-z\-]+)_(\d+)')
 classcode2txt = {}
 typecode2txt = {}
@@ -65,7 +33,7 @@ meta_types = {
     # Real meta types.  We won't have Rdata implement for them, but we need
     # RRType constants.
     '251': 'ixfr', '252': 'axfr', '255': 'any',
-    # Obsolete types.  We probalby won't implement Rdata for them, but it's
+    # Obsolete types.  We probably won't implement Rdata for them, but it's
     # better to have RRType constants.
     '3': 'md', '4': 'mf', '7': 'mb', '8': 'mg', '9': 'mr', '30': 'nxt',
     '38': 'a6', '254': 'maila',
@@ -374,28 +342,15 @@ def generate_rrparam(fileprefix, basemtime):
         indent = ' ' * 8
         typeandclassparams += indent
 
-        # By default, we use OldRdataFactory (see bug #2497). If you
-        # want to pick RdataFactory for a particular type, add it to
-        # new_rdata_factory_users.  Note that we explicitly generate (for
-        # optimization) class-independent ("generic") factories for class IN
-        # for optimization.
-        if (((type_txt.lower(), class_txt.lower()) in
-             new_rdata_factory_users) or
-            ((class_txt.lower() == 'in') and
-             ((type_txt.lower(), 'generic') in new_rdata_factory_users))):
-            rdf_class = 'RdataFactory'
-        else:
-            rdf_class = 'OldRdataFactory'
-
         if class_tuple[1] != 'generic':
             typeandclassparams += 'add("' + type_utxt + '", '
             typeandclassparams += str(type_code) + ', "' + class_utxt
             typeandclassparams += '", ' + str(class_code)
-            typeandclassparams += ', RdataFactoryPtr(new ' + rdf_class + '<'
+            typeandclassparams += ', RdataFactoryPtr(new ' + 'RdataFactory' + '<'
             typeandclassparams += class_txt + '::' + type_utxt + '>()));\n'
         else:
             typeandclassparams += 'add("' + type_utxt + '", ' + str(type_code)
-            typeandclassparams += ', RdataFactoryPtr(new ' + rdf_class + '<'
+            typeandclassparams += ', RdataFactoryPtr(new ' + 'RdataFactory' + '<'
             typeandclassparams += class_txt + '::' + type_utxt + '>()));\n'
 
     typeandclassparams += indent + '// Meta and non-implemented RR types\n'
diff --git a/src/lib/dns/rdata/ch_3/a_1.cc b/src/lib/dns/rdata/ch_3/a_1.cc
index 3d13a9e..8b0e442 100644
--- a/src/lib/dns/rdata/ch_3/a_1.cc
+++ b/src/lib/dns/rdata/ch_3/a_1.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2010-2013  Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -31,6 +31,11 @@ A::A(const std::string&) {
     // TBD
 }
 
+A::A(MasterLexer&, const Name*,
+     MasterLoader::Options, MasterLoaderCallbacks&) {
+    // TBD
+}
+
 A::A(InputBuffer&, size_t) {
     // TBD
 }
diff --git a/src/lib/dns/rdata/generic/opt_41.cc b/src/lib/dns/rdata/generic/opt_41.cc
index d64effb..136bdf9 100644
--- a/src/lib/dns/rdata/generic/opt_41.cc
+++ b/src/lib/dns/rdata/generic/opt_41.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2010-2013  Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -27,10 +27,26 @@ using namespace isc::util;
 // BEGIN_ISC_NAMESPACE
 // BEGIN_RDATA_NAMESPACE
 
+/// \brief Constructor from string.
+///
+/// This constructor cannot be used, and always throws an exception.
+///
+/// \throw InvalidRdataText OPT RR cannot be constructed from text.
 OPT::OPT(const std::string&) {
     isc_throw(InvalidRdataText, "OPT RR cannot be constructed from text");
 }
 
+/// \brief Constructor with a context of MasterLexer.
+///
+/// This constructor cannot be used, and always throws an exception.
+///
+/// \throw InvalidRdataText OPT RR cannot be constructed from text.
+OPT::OPT(MasterLexer&, const Name*,
+       MasterLoader::Options, MasterLoaderCallbacks&)
+{
+    isc_throw(InvalidRdataText, "OPT RR cannot be constructed from text");
+}
+
 OPT::OPT(InputBuffer& buffer, size_t rdata_len) {
     // setPosition() will throw against a short buffer anyway, but it's safer
     // to check it explicitly here.
@@ -48,6 +64,7 @@ OPT::OPT(const OPT&) : Rdata() {
 
 std::string
 OPT::toText() const {
+    // OPT records do not have a text format.
     return ("");
 }
 
diff --git a/src/lib/dns/rdata/generic/rrsig_46.cc b/src/lib/dns/rdata/generic/rrsig_46.cc
index e0137b9..8e826d7 100644
--- a/src/lib/dns/rdata/generic/rrsig_46.cc
+++ b/src/lib/dns/rdata/generic/rrsig_46.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2010-2013  Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -26,9 +26,9 @@
 #include <dns/messagerenderer.h>
 #include <dns/name.h>
 #include <dns/rrtype.h>
-#include <dns/rrttl.h>
 #include <dns/rdata.h>
 #include <dns/rdataclass.h>
+#include <dns/rdata/generic/detail/lexer_util.h>
 
 #include <stdio.h>
 #include <time.h>
@@ -36,6 +36,7 @@
 using namespace std;
 using namespace isc::util;
 using namespace isc::util::encode;
+using isc::dns::rdata::generic::detail::createNameFromLexer;
 
 // BEGIN_ISC_NAMESPACE
 // BEGIN_RDATA_NAMESPACE
@@ -52,8 +53,8 @@ const size_t RRSIG_MINIMUM_LEN = 2 * sizeof(uint8_t) + 2 * sizeof(uint16_t) +
 struct RRSIGImpl {
     // straightforward representation of RRSIG RDATA fields
     RRSIGImpl(const RRType& covered, uint8_t algorithm, uint8_t labels,
-              uint32_t originalttl, uint32_t timeexpire, uint32_t timeinception,
-              uint16_t tag, const Name& signer,
+              uint32_t originalttl, uint32_t timeexpire,
+              uint32_t timeinception, uint16_t tag, const Name& signer,
               const vector<uint8_t>& signature) :
         covered_(covered), algorithm_(algorithm), labels_(labels),
         originalttl_(originalttl), timeexpire_(timeexpire),
@@ -72,38 +73,125 @@ struct RRSIGImpl {
     const vector<uint8_t> signature_;
 };
 
-RRSIG::RRSIG(const std::string& rrsig_str) :
-    impl_(NULL)
-{
-    istringstream iss(rrsig_str);
-    string covered_txt, signer_txt, expire_txt, inception_txt;
-    unsigned int algorithm, labels;
-    uint32_t originalttl;
-    uint16_t tag;
-    stringbuf signaturebuf;
-
-    iss >> covered_txt >> algorithm >> labels >> originalttl
-        >> expire_txt >> inception_txt >> tag >> signer_txt
-        >> &signaturebuf;
-    if (iss.bad() || iss.fail()) {
-        isc_throw(InvalidRdataText, "Invalid RRSIG text");
-    }
+// helper function for string and lexer constructors
+RRSIGImpl*
+RRSIG::constructFromLexer(MasterLexer& lexer, const Name* origin) {
+    const RRType covered(lexer.getNextToken(MasterToken::STRING).getString());
+    const uint32_t algorithm =
+        lexer.getNextToken(MasterToken::NUMBER).getNumber();
     if (algorithm > 0xff) {
         isc_throw(InvalidRdataText, "RRSIG algorithm out of range");
     }
+    const uint32_t labels =
+        lexer.getNextToken(MasterToken::NUMBER).getNumber();
     if (labels > 0xff) {
         isc_throw(InvalidRdataText, "RRSIG labels out of range");
     }
-
-    const uint32_t timeexpire = timeFromText32(expire_txt);
-    const uint32_t timeinception = timeFromText32(inception_txt);
+    const uint32_t originalttl =
+        lexer.getNextToken(MasterToken::NUMBER).getNumber();
+    const uint32_t timeexpire =
+        timeFromText32(lexer.getNextToken(MasterToken::STRING).getString());
+    const uint32_t timeinception =
+        timeFromText32(lexer.getNextToken(MasterToken::STRING).getString());
+    const uint32_t tag =
+        lexer.getNextToken(MasterToken::NUMBER).getNumber();
+    if (tag > 0xffff) {
+        isc_throw(InvalidRdataText, "RRSIG key tag out of range");
+    }
+    const Name& signer = createNameFromLexer(lexer, origin);
+
+    string signature_txt;
+    string signature_part;
+    // Whitespace is allowed within base64 text, so read to the end of input.
+    while (true) {
+        const MasterToken& token =
+            lexer.getNextToken(MasterToken::STRING, true);
+        if ((token.getType() == MasterToken::END_OF_FILE) ||
+            (token.getType() == MasterToken::END_OF_LINE)) {
+            break;
+        }
+        token.getString(signature_part);
+        signature_txt.append(signature_part);
+    }
+    lexer.ungetToken();
 
     vector<uint8_t> signature;
-    decodeBase64(signaturebuf.str(), signature);
+    // missing signature is okay
+    if (signature_txt.size() > 0) {
+        decodeBase64(signature_txt, signature);
+    }
 
-    impl_ = new RRSIGImpl(RRType(covered_txt), algorithm, labels,
-                          originalttl, timeexpire, timeinception, tag,
-                          Name(signer_txt), signature);
+    return (new RRSIGImpl(covered, algorithm, labels,
+                          originalttl, timeexpire, timeinception,
+                          static_cast<uint16_t>(tag), signer, signature));
+}
+
+/// \brief Constructor from string.
+///
+/// The given string must represent a valid RRSIG RDATA.  There can be extra
+/// space characters at the beginning or end of the text (which are simply
+/// ignored), but other extra text, including a new line, will make the
+/// construction fail with an exception.
+///
+/// The Signer's Name must be absolute since there's no parameter that
+/// specifies the origin name; if this is not absolute, \c MissingNameOrigin
+/// exception will be thrown.  This must not be represented as a quoted
+/// string.
+///
+/// See the construction that takes \c MasterLexer for other fields.
+///
+/// \throw Others Exception from the Name constructor.
+/// \throw InvalidRdataText Other general syntax errors.
+RRSIG::RRSIG(const std::string& rrsig_str) :
+    impl_(NULL)
+{
+    // We use auto_ptr here because if there is an exception in this
+    // constructor, the destructor is not called and there could be a
+    // leak of the RRSIGImpl that constructFromLexer() returns.
+    std::auto_ptr<RRSIGImpl> impl_ptr(NULL);
+
+    try {
+        std::istringstream iss(rrsig_str);
+        MasterLexer lexer;
+        lexer.pushSource(iss);
+
+        impl_ptr.reset(constructFromLexer(lexer, NULL));
+
+        if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+            isc_throw(InvalidRdataText, "extra input text for RRSIG: "
+                      << rrsig_str);
+        }
+    } catch (const MasterLexer::LexerError& ex) {
+        isc_throw(InvalidRdataText, "Failed to construct RRSIG from '" <<
+                  rrsig_str << "': " << ex.what());
+    }
+
+    impl_ = impl_ptr.release();
+}
+
+/// \brief Constructor with a context of MasterLexer.
+///
+/// The \c lexer should point to the beginning of valid textual representation
+/// of an RRSIG RDATA.  The Signer's Name fields can be non absolute if \c
+/// origin is non NULL, in which case \c origin is used to make it absolute.
+/// This must not be represented as a quoted string.
+///
+/// The Original TTL field is a valid decimal representation of an unsigned
+/// 32-bit integer. Note that alternate textual representations of \c RRTTL,
+/// such as "1H" for 3600 seconds, are not allowed here.
+///
+/// \throw MasterLexer::LexerError General parsing error such as missing field.
+/// \throw Other Exceptions from the Name constructor if
+/// construction of textual fields as these objects fail.
+///
+/// \param lexer A \c MasterLexer object parsing a master file for the
+/// RDATA to be created
+/// \param origin If non NULL, specifies the origin of Signer's Name when
+/// it is non absolute.
+RRSIG::RRSIG(MasterLexer& lexer, const Name* origin,
+             MasterLoader::Options, MasterLoaderCallbacks&) :
+    impl_(constructFromLexer(lexer, origin))
+{
 }
 
 RRSIG::RRSIG(InputBuffer& buffer, size_t rdata_len) {
diff --git a/src/lib/dns/rdata/generic/rrsig_46.h b/src/lib/dns/rdata/generic/rrsig_46.h
index b32c17f..de72f64 100644
--- a/src/lib/dns/rdata/generic/rrsig_46.h
+++ b/src/lib/dns/rdata/generic/rrsig_46.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2010-2013  Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -18,7 +18,6 @@
 
 #include <dns/name.h>
 #include <dns/rrtype.h>
-#include <dns/rrttl.h>
 #include <dns/rdata.h>
 
 // BEGIN_HEADER_GUARD
@@ -32,6 +31,12 @@
 
 struct RRSIGImpl;
 
+/// \brief \c rdata::RRSIG class represents the RRSIG RDATA as defined %in
+/// RFC4034.
+///
+/// This class implements the basic interfaces inherited from the abstract
+/// \c rdata::Rdata class, and provides trivial accessors specific to the
+/// RRSIG RDATA.
 class RRSIG : public Rdata {
 public:
     // BEGIN_COMMON_MEMBERS
@@ -42,6 +47,9 @@ public:
     // specialized methods
     const RRType& typeCovered() const;
 private:
+    // helper function for string and lexer constructors
+    RRSIGImpl* constructFromLexer(MasterLexer& lexer, const Name* origin);
+
     RRSIGImpl* impl_;
 };
 
diff --git a/src/lib/dns/rdata/generic/sshfp_44.cc b/src/lib/dns/rdata/generic/sshfp_44.cc
index 64b3cc1..2843f10 100644
--- a/src/lib/dns/rdata/generic/sshfp_44.cc
+++ b/src/lib/dns/rdata/generic/sshfp_44.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
@@ -35,95 +35,186 @@ using namespace isc::util::encode;
 // BEGIN_ISC_NAMESPACE
 // BEGIN_RDATA_NAMESPACE
 
-SSHFP::SSHFP(InputBuffer& buffer, size_t rdata_len) {
-    if (rdata_len < 2) {
-        isc_throw(InvalidRdataLength, "SSHFP record too short");
-    }
-
-    algorithm_ = buffer.readUint8();
-    fingerprint_type_ = buffer.readUint8();
-
-    rdata_len -= 2;
-    if (rdata_len > 0) {
-        fingerprint_.resize(rdata_len);
-        buffer.readData(&fingerprint_[0], rdata_len);
-    }
-}
-
-SSHFP::SSHFP(const std::string& sshfp_str) {
-    std::istringstream iss(sshfp_str);
-    std::stringbuf fingerprintbuf;
-    uint32_t algorithm, fingerprint_type;
-
-    iss >> algorithm >> fingerprint_type;
-    if (iss.bad() || iss.fail()) {
-        isc_throw(InvalidRdataText, "Invalid SSHFP text");
-    }
-
+struct SSHFPImpl {
+    // straightforward representation of SSHFP RDATA fields
+    SSHFPImpl(uint8_t algorithm, uint8_t fingerprint_type,
+              vector<uint8_t>& fingerprint) :
+        algorithm_(algorithm),
+        fingerprint_type_(fingerprint_type),
+        fingerprint_(fingerprint)
+    {}
+
+    uint8_t algorithm_;
+    uint8_t fingerprint_type_;
+    const vector<uint8_t> fingerprint_;
+};
+
+// helper function for string and lexer constructors
+SSHFPImpl*
+SSHFP::constructFromLexer(MasterLexer& lexer) {
+    const uint32_t algorithm =
+        lexer.getNextToken(MasterToken::NUMBER).getNumber();
     if (algorithm > 255) {
         isc_throw(InvalidRdataText, "SSHFP algorithm number out of range");
     }
 
+    const uint32_t fingerprint_type =
+        lexer.getNextToken(MasterToken::NUMBER).getNumber();
     if (fingerprint_type > 255) {
         isc_throw(InvalidRdataText, "SSHFP fingerprint type out of range");
     }
 
-    iss >> &fingerprintbuf;
+    const MasterToken& token = lexer.getNextToken(MasterToken::STRING, true);
+    vector<uint8_t> fingerprint;
+    if ((token.getType() != MasterToken::END_OF_FILE) &&
+        (token.getType() != MasterToken::END_OF_LINE)) {
+        decodeHex(token.getString(), fingerprint);
+    }
+
+    return (new SSHFPImpl(algorithm, fingerprint_type, fingerprint));
+}
+
+/// \brief Constructor from string.
+///
+/// The given string must represent a valid SSHFP RDATA.  There can be
+/// extra space characters at the beginning or end of the text (which
+/// are simply ignored), but other extra text, including a new line,
+/// will make the construction fail with an exception.
+///
+/// The Algorithm and Fingerprint Type fields must be within their valid
+/// ranges, but are not contrained to the values defined in RFC4255.
+///
+/// The Fingerprint field may be absent, but if present it must contain a
+/// valid hex encoding of the fingerprint.
+///
+/// \throw InvalidRdataText if any fields are missing, out of their valid
+/// ranges, or incorrect.
+///
+/// \param sshfp_str A string containing the RDATA to be created
+SSHFP::SSHFP(const string& sshfp_str) :
+    impl_(NULL)
+{
+    // We use auto_ptr here because if there is an exception in this
+    // constructor, the destructor is not called and there could be a
+    // leak of the SSHFPImpl that constructFromLexer() returns.
+    std::auto_ptr<SSHFPImpl> impl_ptr(NULL);
+
     try {
-        decodeHex(fingerprintbuf.str(), fingerprint_);
+        std::istringstream ss(sshfp_str);
+        MasterLexer lexer;
+        lexer.pushSource(ss);
+
+        impl_ptr.reset(constructFromLexer(lexer));
+
+        if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+            isc_throw(InvalidRdataText, "extra input text for SSHFP: "
+                      << sshfp_str);
+        }
+    } catch (const MasterLexer::LexerError& ex) {
+        isc_throw(InvalidRdataText, "Failed to construct SSHFP from '" <<
+                  sshfp_str << "': " << ex.what());
     } catch (const isc::BadValue& e) {
         isc_throw(InvalidRdataText,
                   "Bad SSHFP fingerprint: " << e.what());
     }
 
-    algorithm_ = algorithm;
-    fingerprint_type_ = fingerprint_type;
+    impl_ = impl_ptr.release();
 }
 
-SSHFP::SSHFP(uint8_t algorithm, uint8_t fingerprint_type,
-             const std::string& fingerprint)
+/// \brief Constructor with a context of MasterLexer.
+///
+/// The \c lexer should point to the beginning of valid textual representation
+/// of an SSHFP RDATA.
+///
+/// \throw MasterLexer::LexerError General parsing error such as missing field.
+/// \throw InvalidRdataText Fields are out of their valid range, or are
+/// incorrect.
+/// \throw BadValue Fingerprint is not a valid hex string.
+///
+/// \param lexer A \c MasterLexer object parsing a master file for the
+/// RDATA to be created
+SSHFP::SSHFP(MasterLexer& lexer, const Name*,
+             MasterLoader::Options, MasterLoaderCallbacks&) :
+    impl_(NULL)
 {
-    algorithm_ = algorithm;
-    fingerprint_type_ = fingerprint_type;
+    impl_ = constructFromLexer(lexer);
+}
 
+/// \brief Constructor from InputBuffer.
+///
+/// The passed buffer must contain a valid SSHFP RDATA.
+///
+/// The Algorithm and Fingerprint Type fields are not checked for unknown
+/// values.  It is okay for the fingerprint data to be missing (see the
+/// description of the constructor from string).
+SSHFP::SSHFP(InputBuffer& buffer, size_t rdata_len) {
+    if (rdata_len < 2) {
+        isc_throw(InvalidRdataLength, "SSHFP record too short");
+    }
+
+    const uint8_t algorithm = buffer.readUint8();
+    const uint8_t fingerprint_type = buffer.readUint8();
+
+    vector<uint8_t> fingerprint;
+    rdata_len -= 2;
+    if (rdata_len > 0) {
+        fingerprint.resize(rdata_len);
+        buffer.readData(&fingerprint[0], rdata_len);
+    }
+
+    impl_ = new SSHFPImpl(algorithm, fingerprint_type, fingerprint);
+}
+
+SSHFP::SSHFP(uint8_t algorithm, uint8_t fingerprint_type,
+             const string& fingerprint_txt) :
+    impl_(NULL)
+{
+    vector<uint8_t> fingerprint;
     try {
-        decodeHex(fingerprint, fingerprint_);
+        decodeHex(fingerprint_txt, fingerprint);
     } catch (const isc::BadValue& e) {
         isc_throw(InvalidRdataText, "Bad SSHFP fingerprint: " << e.what());
     }
+
+    impl_ = new SSHFPImpl(algorithm, fingerprint_type, fingerprint);
 }
 
 SSHFP::SSHFP(const SSHFP& other) :
-  Rdata(), algorithm_(other.algorithm_),
-  fingerprint_type_(other.fingerprint_type_),
-  fingerprint_(other.fingerprint_)
+        Rdata(), impl_(new SSHFPImpl(*other.impl_))
 {}
 
+SSHFP::~SSHFP() {
+    delete impl_;
+}
+
 void
 SSHFP::toWire(OutputBuffer& buffer) const {
-    buffer.writeUint8(algorithm_);
-    buffer.writeUint8(fingerprint_type_);
+    buffer.writeUint8(impl_->algorithm_);
+    buffer.writeUint8(impl_->fingerprint_type_);
 
-    if (!fingerprint_.empty()) {
-        buffer.writeData(&fingerprint_[0], fingerprint_.size());
+    if (!impl_->fingerprint_.empty()) {
+        buffer.writeData(&impl_->fingerprint_[0],
+                         impl_->fingerprint_.size());
     }
 }
 
 void
 SSHFP::toWire(AbstractMessageRenderer& renderer) const {
-    renderer.writeUint8(algorithm_);
-    renderer.writeUint8(fingerprint_type_);
+    renderer.writeUint8(impl_->algorithm_);
+    renderer.writeUint8(impl_->fingerprint_type_);
 
-    if (!fingerprint_.empty()) {
-        renderer.writeData(&fingerprint_[0], fingerprint_.size());
+    if (!impl_->fingerprint_.empty()) {
+        renderer.writeData(&impl_->fingerprint_[0],
+                           impl_->fingerprint_.size());
     }
 }
 
 string
 SSHFP::toText() const {
-    return (lexical_cast<string>(static_cast<int>(algorithm_)) +
-            " " + lexical_cast<string>(static_cast<int>(fingerprint_type_)) +
-            (fingerprint_.empty() ? "" : " " + encodeHex(fingerprint_)));
+    return (lexical_cast<string>(static_cast<int>(impl_->algorithm_)) + " " +
+            lexical_cast<string>(static_cast<int>(impl_->fingerprint_type_)) +
+            (impl_->fingerprint_.empty() ? "" :
+             " " + encodeHex(impl_->fingerprint_)));
 }
 
 int
@@ -133,24 +224,26 @@ SSHFP::compare(const Rdata& other) const {
     /* This doesn't really make any sort of sense, but in the name of
        consistency... */
 
-    if (algorithm_ < other_sshfp.algorithm_) {
+    if (impl_->algorithm_ < other_sshfp.impl_->algorithm_) {
         return (-1);
-    } else if (algorithm_ > other_sshfp.algorithm_) {
+    } else if (impl_->algorithm_ > other_sshfp.impl_->algorithm_) {
         return (1);
     }
 
-    if (fingerprint_type_ < other_sshfp.fingerprint_type_) {
+    if (impl_->fingerprint_type_ < other_sshfp.impl_->fingerprint_type_) {
         return (-1);
-    } else if (fingerprint_type_ > other_sshfp.fingerprint_type_) {
+    } else if (impl_->fingerprint_type_ >
+               other_sshfp.impl_->fingerprint_type_) {
         return (1);
     }
 
-    const size_t this_len = fingerprint_.size();
-    const size_t other_len = other_sshfp.fingerprint_.size();
+    const size_t this_len = impl_->fingerprint_.size();
+    const size_t other_len = other_sshfp.impl_->fingerprint_.size();
     const size_t cmplen = min(this_len, other_len);
 
     if (cmplen > 0) {
-        const int cmp = memcmp(&fingerprint_[0], &other_sshfp.fingerprint_[0],
+        const int cmp = memcmp(&impl_->fingerprint_[0],
+                               &other_sshfp.impl_->fingerprint_[0],
                                cmplen);
         if (cmp != 0) {
             return (cmp);
@@ -168,17 +261,17 @@ SSHFP::compare(const Rdata& other) const {
 
 uint8_t
 SSHFP::getAlgorithmNumber() const {
-    return (algorithm_);
+    return (impl_->algorithm_);
 }
 
 uint8_t
 SSHFP::getFingerprintType() const {
-    return (fingerprint_type_);
+    return (impl_->fingerprint_type_);
 }
 
 size_t
 SSHFP::getFingerprintLen() const {
-    return (fingerprint_.size());
+    return (impl_->fingerprint_.size());
 }
 
 // END_RDATA_NAMESPACE
diff --git a/src/lib/dns/rdata/generic/sshfp_44.h b/src/lib/dns/rdata/generic/sshfp_44.h
index d9ebea4..2f884ba 100644
--- a/src/lib/dns/rdata/generic/sshfp_44.h
+++ b/src/lib/dns/rdata/generic/sshfp_44.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
@@ -28,12 +28,16 @@
 
 // BEGIN_RDATA_NAMESPACE
 
+struct SSHFPImpl;
+
 class SSHFP : public Rdata {
 public:
     // BEGIN_COMMON_MEMBERS
     // END_COMMON_MEMBERS
 
-    SSHFP(uint8_t algorithm, uint8_t fingerprint_type, const std::string& fingerprint);
+    SSHFP(uint8_t algorithm, uint8_t fingerprint_type,
+          const std::string& fingerprint);
+    ~SSHFP();
 
     ///
     /// Specialized methods
@@ -43,11 +47,9 @@ public:
     size_t getFingerprintLen() const;
 
 private:
-    /// Note: this is a prototype version; we may reconsider
-    /// this representation later.
-    uint8_t algorithm_;
-    uint8_t fingerprint_type_;
-    std::vector<uint8_t> fingerprint_;
+    SSHFPImpl* constructFromLexer(MasterLexer& lexer);
+
+    SSHFPImpl* impl_;
 };
 
 // END_RDATA_NAMESPACE
diff --git a/src/lib/dns/rdata/hs_4/a_1.cc b/src/lib/dns/rdata/hs_4/a_1.cc
index 3d13a9e..8b0e442 100644
--- a/src/lib/dns/rdata/hs_4/a_1.cc
+++ b/src/lib/dns/rdata/hs_4/a_1.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2010-2013  Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -31,6 +31,11 @@ A::A(const std::string&) {
     // TBD
 }
 
+A::A(MasterLexer&, const Name*,
+     MasterLoader::Options, MasterLoaderCallbacks&) {
+    // TBD
+}
+
 A::A(InputBuffer&, size_t) {
     // TBD
 }
diff --git a/src/lib/dns/rdata/in_1/dhcid_49.cc b/src/lib/dns/rdata/in_1/dhcid_49.cc
index 7745161..184866c 100644
--- a/src/lib/dns/rdata/in_1/dhcid_49.cc
+++ b/src/lib/dns/rdata/in_1/dhcid_49.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013  Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -15,8 +15,6 @@
 #include <stdint.h>
 #include <string.h>
 
-#include <string>
-
 #include <exceptions/exceptions.h>
 
 #include <util/buffer.h>
@@ -28,53 +26,77 @@
 
 using namespace std;
 using namespace isc::util;
+using namespace isc::util::encode;
 
 // BEGIN_ISC_NAMESPACE
 // BEGIN_RDATA_NAMESPACE
 
+void
+DHCID::constructFromLexer(MasterLexer& lexer) {
+    string digest_txt = lexer.getNextToken(MasterToken::STRING).getString();
+
+    // Whitespace is allowed within base64 text, so read to the end of input.
+    string digest_part;
+    while (true) {
+        const MasterToken& token =
+            lexer.getNextToken(MasterToken::STRING, true);
+        if ((token.getType() == MasterToken::END_OF_FILE) ||
+            (token.getType() == MasterToken::END_OF_LINE)) {
+            break;
+        }
+        token.getString(digest_part);
+        digest_txt.append(digest_part);
+    }
+    lexer.ungetToken();
+
+    decodeBase64(digest_txt, digest_);
+}
+
 /// \brief Constructor from string.
 ///
 /// \param dhcid_str A base-64 representation of the DHCID binary data.
-/// The data is considered to be opaque, but a sanity check is performed.
-///
-/// <b>Exceptions</b>
 ///
-/// \c dhcid_str must be a valid  BASE-64 string, otherwise an exception
-/// of class \c isc::BadValue will be thrown;
-/// the binary data should consist of at leat of 3 octets as per RFC4701:
-///           < 2 octets >    Identifier type code
-///           < 1 octet >     Digest type code
-///           < n octets >    Digest (length depends on digest type)
-/// If the data is less than 3 octets (i.e. it cannot contain id type code and
-/// digest type code), an exception of class \c InvalidRdataLength is thrown.
+/// \throw InvalidRdataText if the string could not be parsed correctly.
 DHCID::DHCID(const std::string& dhcid_str) {
-    istringstream iss(dhcid_str);
-    stringbuf digestbuf;
-
-    iss >> &digestbuf;
-    isc::util::encode::decodeBase64(digestbuf.str(), digest_);
-
-    // RFC4701 states DNS software should consider the RDATA section to
-    // be opaque, but there must be at least three bytes in the data:
-    // < 2 octets >    Identifier type code
-    // < 1 octet >     Digest type code
-    if (digest_.size() < 3) {
-        isc_throw(InvalidRdataLength, "DHCID length " << digest_.size() <<
-                  " too short, need at least 3 bytes");
+    try {
+        std::istringstream iss(dhcid_str);
+        MasterLexer lexer;
+        lexer.pushSource(iss);
+
+        constructFromLexer(lexer);
+
+        if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+            isc_throw(InvalidRdataText, "extra input text for DHCID: "
+                      << dhcid_str);
+        }
+    } catch (const MasterLexer::LexerError& ex) {
+        isc_throw(InvalidRdataText, "Failed to construct DHCID from '" <<
+                  dhcid_str << "': " << ex.what());
     }
 }
 
+/// \brief Constructor with a context of MasterLexer.
+///
+/// The \c lexer should point to the beginning of valid textual representation
+/// of a DHCID RDATA.
+///
+/// \throw BadValue if the text is not valid base-64.
+/// \throw MasterLexer::LexerError General parsing error such as missing field.
+///
+/// \param lexer A \c MasterLexer object parsing a master file for the
+/// RDATA to be created
+DHCID::DHCID(MasterLexer& lexer, const Name*,
+             MasterLoader::Options, MasterLoaderCallbacks&) {
+    constructFromLexer(lexer);
+}
+
 /// \brief Constructor from wire-format data.
 ///
 /// \param buffer A buffer storing the wire format data.
 /// \param rdata_len The length of the RDATA in bytes
-///
-/// <b>Exceptions</b>
-/// \c InvalidRdataLength is thrown if \c rdata_len is than minimum of 3 octets
 DHCID::DHCID(InputBuffer& buffer, size_t rdata_len) {
-    if (rdata_len < 3) {
-        isc_throw(InvalidRdataLength, "DHCID length " << rdata_len <<
-                  " too short, need at least 3 bytes");
+    if (rdata_len == 0) {
+        isc_throw(InvalidRdataLength, "Missing DHCID rdata");
     }
 
     digest_.resize(rdata_len);
@@ -112,7 +134,7 @@ DHCID::toWire(AbstractMessageRenderer& renderer) const {
 /// \return A string representation of \c DHCID.
 string
 DHCID::toText() const {
-    return (isc::util::encode::encodeBase64(digest_));
+    return (encodeBase64(digest_));
 }
 
 /// \brief Compare two instances of \c DHCID RDATA.
diff --git a/src/lib/dns/rdata/in_1/dhcid_49.h b/src/lib/dns/rdata/in_1/dhcid_49.h
index 90f5fab..81041b0 100644
--- a/src/lib/dns/rdata/in_1/dhcid_49.h
+++ b/src/lib/dns/rdata/in_1/dhcid_49.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013  Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -43,6 +43,9 @@ public:
     const std::vector<uint8_t>& getDigest() const;
 
 private:
+    // helper for string and lexer constructors
+    void constructFromLexer(MasterLexer& lexer);
+
     /// \brief Private data representation
     ///
     /// Opaque data at least 3 octets long as per RFC4701.
diff --git a/src/lib/dns/tests/message_unittest.cc b/src/lib/dns/tests/message_unittest.cc
index 835aa48..8aaebaa 100644
--- a/src/lib/dns/tests/message_unittest.cc
+++ b/src/lib/dns/tests/message_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2010-2013  Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -98,7 +98,7 @@ protected:
         rrset_rrsig = RRsetPtr(new RRset(test_name, RRClass::IN(),
                                         RRType::RRSIG(), RRTTL(3600)));
         rrset_rrsig->addRdata(generic::RRSIG("AAAA 5 3 7200 20100322084538 "
-                                             "20100220084538 1 example.com "
+                                             "20100220084538 1 example.com. "
                                              "FAKEFAKEFAKEFAKE"));
         rrset_aaaa->addRRsig(rrset_rrsig);
     }
diff --git a/src/lib/dns/tests/rdata_dhcid_unittest.cc b/src/lib/dns/tests/rdata_dhcid_unittest.cc
index 8d56c0e..77baccd 100644
--- a/src/lib/dns/tests/rdata_dhcid_unittest.cc
+++ b/src/lib/dns/tests/rdata_dhcid_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013  Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -12,6 +12,8 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
+#include <exceptions/exceptions.h>
+
 #include <util/buffer.h>
 #include <dns/rdataclass.h>
 #include <util/encode/base64.h>
@@ -23,6 +25,7 @@
 
 using isc::UnitTestUtil;
 using namespace std;
+using namespace isc;
 using namespace isc::dns;
 using namespace isc::util;
 using namespace isc::util::encode;
@@ -30,25 +33,69 @@ using namespace isc::dns::rdata;
 
 namespace {
 
-const string string_dhcid(
-                   "0LIg0LvQtdGB0YMg0YDQvtC00LjQu9Cw0YHRjCDRkdC70L7Rh9C60LA=");
-
-const in::DHCID rdata_dhcid(string_dhcid);
-
 class Rdata_DHCID_Test : public RdataTest {
+protected:
+    Rdata_DHCID_Test() :
+        dhcid_txt("0LIg0LvQtdGB0YMg0YDQvtC00LjQu9Cw0YHRjCDRkdC70L7Rh9C60LA="),
+        rdata_dhcid(dhcid_txt)
+    {}
+
+    void checkFromText_None(const string& rdata_str) {
+        checkFromText<in::DHCID, isc::Exception, isc::Exception>(
+            rdata_str, rdata_dhcid, false, false);
+    }
+
+    void checkFromText_BadValue(const string& rdata_str) {
+        checkFromText<in::DHCID, BadValue, BadValue>(
+            rdata_str, rdata_dhcid, true, true);
+    }
+
+    void checkFromText_LexerError(const string& rdata_str) {
+        checkFromText
+            <in::DHCID, InvalidRdataText, MasterLexer::LexerError>(
+                rdata_str, rdata_dhcid, true, true);
+    }
+
+    void checkFromText_BadString(const string& rdata_str) {
+        checkFromText
+            <in::DHCID, InvalidRdataText, isc::Exception>(
+                rdata_str, rdata_dhcid, true, false);
+    }
+
+    const string dhcid_txt;
+    const in::DHCID rdata_dhcid;
 };
 
-TEST_F(Rdata_DHCID_Test, createFromString) {
-    const in::DHCID rdata_dhcid2(string_dhcid);
-    EXPECT_EQ(0, rdata_dhcid2.compare(rdata_dhcid));
-}
+TEST_F(Rdata_DHCID_Test, fromText) {
+    EXPECT_EQ(dhcid_txt, rdata_dhcid.toText());
+
+    // Space in digest data is OK
+    checkFromText_None(
+            "0LIg0LvQtdGB0YMg 0YDQvtC00LjQu9Cw 0YHRjCDRkdC70L7R h9C60LA=");
+
+    // Multi-line digest data is OK, if enclosed in parentheses
+    checkFromText_None(
+            "( 0LIg0LvQtdGB0YMg0YDQvtC00LjQu9Cw\n0YHRjCDRkdC70L7R h9C60LA= )");
 
-TEST_F(Rdata_DHCID_Test, badBase64) {
-    EXPECT_THROW(const in::DHCID rdata_dhcid_bad("00"), isc::BadValue);
+    // Trailing garbage. This should cause only the string constructor
+    // to fail, but the lexer constructor must be able to continue
+    // parsing from it.
+    checkFromText_BadString(
+            "0LIg0LvQtdGB0YMg0YDQvtC00LjQu9Cw0YHRjCDRkdC70L7Rh9C60LA="
+            " ; comment\n"
+            "AAIBY2/AuCccgoJbsaxcQc9TUapptP69lOjxfNuVAA2kjEA=");
 }
 
-TEST_F(Rdata_DHCID_Test, badLength) {
-    EXPECT_THROW(const in::DHCID rdata_dhcid_bad("MDA="), InvalidRdataLength);
+TEST_F(Rdata_DHCID_Test, badText) {
+    // missing digest data
+    checkFromText_LexerError("");
+
+    // invalid base64
+    checkFromText_BadValue("EEeeeeeeEEEeeeeeeGaaahAAAAAAAAHHHHHHHHHHH!=");
+
+    // unterminated multi-line base64
+    checkFromText_LexerError(
+            "( 0LIg0LvQtdGB0YMg0YDQvtC00LjQu9Cw\n0YHRjCDRkdC70L7R h9C60LA=");
 }
 
 TEST_F(Rdata_DHCID_Test, copy) {
@@ -60,17 +107,17 @@ TEST_F(Rdata_DHCID_Test, createFromWire) {
     EXPECT_EQ(0, rdata_dhcid.compare(
                   *rdataFactoryFromFile(RRType("DHCID"), RRClass("IN"),
                                         "rdata_dhcid_fromWire")));
+
+    InputBuffer buffer(NULL, 0);
+    EXPECT_THROW(in::DHCID(buffer, 0), InvalidRdataLength);
+
     // TBD: more tests
 }
 
 TEST_F(Rdata_DHCID_Test, createFromLexer) {
     EXPECT_EQ(0, rdata_dhcid.compare(
         *test::createRdataUsingLexer(RRType::DHCID(), RRClass::IN(),
-                                     string_dhcid)));
-
-    // Exceptions cause NULL to be returned.
-    EXPECT_FALSE(test::createRdataUsingLexer(RRType::DHCID(), RRClass::IN(),
-                                             "00"));
+                                     dhcid_txt)));
 }
 
 TEST_F(Rdata_DHCID_Test, toWireRenderer) {
@@ -92,13 +139,13 @@ TEST_F(Rdata_DHCID_Test, toWireBuffer) {
 }
 
 TEST_F(Rdata_DHCID_Test, toText) {
-    EXPECT_EQ(string_dhcid, rdata_dhcid.toText());
+    EXPECT_EQ(dhcid_txt, rdata_dhcid.toText());
 }
 
 TEST_F(Rdata_DHCID_Test, getDHCIDDigest) {
-    const string string_dhcid1(encodeBase64(rdata_dhcid.getDigest()));
+    const string dhcid_txt1(encodeBase64(rdata_dhcid.getDigest()));
 
-    EXPECT_EQ(string_dhcid, string_dhcid1);
+    EXPECT_EQ(dhcid_txt, dhcid_txt1);
 }
 
 TEST_F(Rdata_DHCID_Test, compare) {
@@ -117,6 +164,6 @@ TEST_F(Rdata_DHCID_Test, compare) {
     EXPECT_GT(rdata_dhcid3.compare(rdata_dhcid2), 0);
 
     // comparison attempt between incompatible RR types should be rejected
-    EXPECT_THROW(rdata_dhcid.compare(*rdata_nomatch), bad_cast); 
+    EXPECT_THROW(rdata_dhcid.compare(*rdata_nomatch), bad_cast);
 }
 }
diff --git a/src/lib/dns/tests/rdata_ds_like_unittest.cc b/src/lib/dns/tests/rdata_ds_like_unittest.cc
index c797199..ae6a360 100644
--- a/src/lib/dns/tests/rdata_ds_like_unittest.cc
+++ b/src/lib/dns/tests/rdata_ds_like_unittest.cc
@@ -47,15 +47,15 @@ template<> RRTYPE<generic::DLV>::RRTYPE() : RRType(RRType::DLV()) {}
 template <class DS_LIKE>
 class Rdata_DS_LIKE_Test : public RdataTest {
 protected:
-    static DS_LIKE const rdata_ds_like;
+    Rdata_DS_LIKE_Test() :
+        ds_like_txt("12892 5 2 F1E184C0E1D615D20EB3C223ACED3B03C773DD952D"
+                    "5F0EB5C777586DE18DA6B5"),
+        rdata_ds_like(ds_like_txt)
+    {}
+    const string ds_like_txt;
+    const DS_LIKE rdata_ds_like;
 };
 
-string ds_like_txt("12892 5 2 F1E184C0E1D615D20EB3C223ACED3B03C773DD952D"
-                   "5F0EB5C777586DE18DA6B5");
-
-template <class DS_LIKE>
-DS_LIKE const Rdata_DS_LIKE_Test<DS_LIKE>::rdata_ds_like(ds_like_txt);
-
 // The list of types we want to test.
 typedef testing::Types<generic::DS, generic::DLV> Implementations;
 
@@ -70,7 +70,7 @@ TYPED_TEST(Rdata_DS_LIKE_Test, createFromText) {
 }
 
 TYPED_TEST(Rdata_DS_LIKE_Test, toText_DS_LIKE) {
-    EXPECT_EQ(ds_like_txt, this->rdata_ds_like.toText());
+    EXPECT_EQ(this->ds_like_txt, this->rdata_ds_like.toText());
 }
 
 TYPED_TEST(Rdata_DS_LIKE_Test, badText_DS_LIKE) {
@@ -96,7 +96,7 @@ TYPED_TEST(Rdata_DS_LIKE_Test, createFromWire_DS_LIKE) {
 TYPED_TEST(Rdata_DS_LIKE_Test, createFromLexer_DS_LIKE) {
     EXPECT_EQ(0, this->rdata_ds_like.compare(
         *test::createRdataUsingLexer(RRTYPE<TypeParam>(), RRClass::IN(),
-                                     ds_like_txt)));
+                                     this->ds_like_txt)));
 
     // Whitespace is okay
     EXPECT_EQ(0, this->rdata_ds_like.compare(
@@ -121,13 +121,13 @@ TYPED_TEST(Rdata_DS_LIKE_Test, createFromLexer_DS_LIKE) {
 }
 
 TYPED_TEST(Rdata_DS_LIKE_Test, assignment_DS_LIKE) {
-    TypeParam copy((string(ds_like_txt)));
+    TypeParam copy(this->ds_like_txt);
     copy = this->rdata_ds_like;
     EXPECT_EQ(0, copy.compare(this->rdata_ds_like));
 
     // Check if the copied data is valid even after the original is deleted
     TypeParam* copy2 = new TypeParam(this->rdata_ds_like);
-    TypeParam copy3((string(ds_like_txt)));
+    TypeParam copy3(this->ds_like_txt);
     copy3 = *copy2;
     delete copy2;
     EXPECT_EQ(0, copy3.compare(this->rdata_ds_like));
@@ -143,7 +143,7 @@ TYPED_TEST(Rdata_DS_LIKE_Test, getTag_DS_LIKE) {
 
 TYPED_TEST(Rdata_DS_LIKE_Test, toWireRenderer) {
     Rdata_DS_LIKE_Test<TypeParam>::renderer.skip(2);
-    TypeParam rdata_ds_like(ds_like_txt);
+    TypeParam rdata_ds_like(this->ds_like_txt);
     rdata_ds_like.toWire(this->renderer);
 
     vector<unsigned char> data;
@@ -156,7 +156,7 @@ TYPED_TEST(Rdata_DS_LIKE_Test, toWireRenderer) {
 }
 
 TYPED_TEST(Rdata_DS_LIKE_Test, toWireBuffer) {
-    TypeParam rdata_ds_like(ds_like_txt);
+    TypeParam rdata_ds_like(this->ds_like_txt);
     rdata_ds_like.toWire(this->obuffer);
 }
 
@@ -179,8 +179,33 @@ string ds_like_txt6("12892 5 2 F2E184C0E1D615D20EB3C223ACED3B03C773DD952D"
                    "5F0EB5C777586DE18DA6B555");
 
 TYPED_TEST(Rdata_DS_LIKE_Test, compare) {
+    const string ds_like_txt1(
+        "12892 5 2 F1E184C0E1D615D20EB3C223ACED3B03C773DD952D"
+        "5F0EB5C777586DE18DA6B5");
+    // different tag
+    const string ds_like_txt2(
+        "12893 5 2 F1E184C0E1D615D20EB3C223ACED3B03C773DD952D"
+        "5F0EB5C777586DE18DA6B5");
+    // different algorithm
+    const string ds_like_txt3(
+        "12892 6 2 F1E184C0E1D615D20EB3C223ACED3B03C773DD952D"
+        "5F0EB5C777586DE18DA6B5");
+    // different digest type
+    const string ds_like_txt4(
+        "12892 5 3 F1E184C0E1D615D20EB3C223ACED3B03C773DD952D"
+        "5F0EB5C777586DE18DA6B5");
+    // different digest
+    const string ds_like_txt5(
+        "12892 5 2 F2E184C0E1D615D20EB3C223ACED3B03C773DD952D"
+        "5F0EB5C777586DE18DA6B5");
+    // different digest length
+    const string ds_like_txt6(
+        "12892 5 2 F2E184C0E1D615D20EB3C223ACED3B03C773DD952D"
+        "5F0EB5C777586DE18DA6B555");
+
     // trivial case: self equivalence
-    EXPECT_EQ(0, TypeParam(ds_like_txt).compare(TypeParam(ds_like_txt)));
+    EXPECT_EQ(0, TypeParam(this->ds_like_txt).
+              compare(TypeParam(this->ds_like_txt)));
 
     // non-equivalence tests
     EXPECT_LT(TypeParam(ds_like_txt1).compare(TypeParam(ds_like_txt2)), 0);
diff --git a/src/lib/dns/tests/rdata_opt_unittest.cc b/src/lib/dns/tests/rdata_opt_unittest.cc
index 5699259..20ccfe4 100644
--- a/src/lib/dns/tests/rdata_opt_unittest.cc
+++ b/src/lib/dns/tests/rdata_opt_unittest.cc
@@ -84,6 +84,6 @@ TEST_F(Rdata_OPT_Test, compare) {
                                         "rdata_opt_fromWire", 2)));
 
     // comparison attempt between incompatible RR types should be rejected
-    EXPECT_THROW(rdata_opt.compare(*RdataTest::rdata_nomatch), bad_cast); 
+    EXPECT_THROW(rdata_opt.compare(*RdataTest::rdata_nomatch), bad_cast);
 }
 }
diff --git a/src/lib/dns/tests/rdata_rrsig_unittest.cc b/src/lib/dns/tests/rdata_rrsig_unittest.cc
index d758ff3..2d075ec 100644
--- a/src/lib/dns/tests/rdata_rrsig_unittest.cc
+++ b/src/lib/dns/tests/rdata_rrsig_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2010-2013  Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -36,7 +36,7 @@ using namespace isc::dns::rdata;
 
 namespace {
 class Rdata_RRSIG_Test : public RdataTest {
-public:
+protected:
     Rdata_RRSIG_Test() :
         rrsig_txt("A 5 4 43200 20100223214617 20100222214617 8496 isc.org. "
                   "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
@@ -46,6 +46,49 @@ public:
         rdata_rrsig(rrsig_txt)
     {}
 
+    void checkFromText_None(const string& rdata_str) {
+        checkFromText<generic::RRSIG, isc::Exception, isc::Exception>(
+            rdata_str, rdata_rrsig, false, false);
+    }
+
+    void checkFromText_InvalidText(const string& rdata_str) {
+        checkFromText<generic::RRSIG, InvalidRdataText, InvalidRdataText>(
+            rdata_str, rdata_rrsig, true, true);
+    }
+
+    void checkFromText_InvalidType(const string& rdata_str) {
+        checkFromText<generic::RRSIG, InvalidRRType, InvalidRRType>(
+            rdata_str, rdata_rrsig, true, true);
+    }
+
+    void checkFromText_InvalidTime(const string& rdata_str) {
+        checkFromText<generic::RRSIG, InvalidTime, InvalidTime>(
+            rdata_str, rdata_rrsig, true, true);
+    }
+
+    void checkFromText_BadValue(const string& rdata_str) {
+        checkFromText<generic::RRSIG, BadValue, BadValue>(
+            rdata_str, rdata_rrsig, true, true);
+    }
+
+    void checkFromText_LexerError(const string& rdata_str) {
+        checkFromText
+            <generic::RRSIG, InvalidRdataText, MasterLexer::LexerError>(
+                rdata_str, rdata_rrsig, true, true);
+    }
+
+    void checkFromText_MissingOrigin(const string& rdata_str) {
+        checkFromText
+            <generic::RRSIG, MissingNameOrigin, MissingNameOrigin>(
+                rdata_str, rdata_rrsig, true, true);
+    }
+
+    void checkFromText_BadString(const string& rdata_str) {
+        checkFromText
+            <generic::RRSIG, InvalidRdataText, isc::Exception>(
+                rdata_str, rdata_rrsig, true, false);
+    }
+
     const string rrsig_txt;
     const generic::RRSIG rdata_rrsig;
 };
@@ -53,52 +96,196 @@ public:
 TEST_F(Rdata_RRSIG_Test, fromText) {
     EXPECT_EQ(rrsig_txt, rdata_rrsig.toText());
     EXPECT_EQ(isc::dns::RRType::A(), rdata_rrsig.typeCovered());
+
+    // Missing signature is OK
+    EXPECT_NO_THROW(const generic::RRSIG sig(
+              "A 5 4 43200 20100223214617 20100222214617 8496 isc.org."));
+
+    // Space in signature data is OK
+    checkFromText_None(
+              "A 5 4 43200 20100223214617 20100222214617 8496 isc.org. "
+              "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz "
+              "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/ "
+              "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU "
+              "f49t+sXKPzbipN9g+s1ZPiIyofc=");
+
+    // Multi-line signature data is OK, if enclosed in parentheses
+    checkFromText_None(
+              "A 5 4 43200 20100223214617 20100222214617 8496 isc.org. "
+              "( evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz\n"
+              "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/\n"
+              "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU\n"
+              "f49t+sXKPzbipN9g+s1ZPiIyofc= )");
+
+    // Trailing garbage. This should cause only the string constructor
+    // to fail, but the lexer constructor must be able to continue
+    // parsing from it.
+    checkFromText_BadString(
+              "A 5 4 43200 20100223214617 20100222214617 8496 isc.org. "
+              "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
+              "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
+              "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
+              "f49t+sXKPzbipN9g+s1ZPiIyofc= ; comment\n"
+              "A 5 4 43200 20100223214617 20100222214617 8496 isc.org. "
+              "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
+              "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
+              "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
+              "f49t+sXKPzbipN9g+s1ZPiIyofc=");
+}
+
+TEST_F(Rdata_RRSIG_Test, badText_missingFields) {
+    checkFromText_LexerError("A");
+    checkFromText_LexerError("A 5");
+    checkFromText_LexerError("A 5 4");
+    checkFromText_LexerError("A 5 4 43200");
+    checkFromText_LexerError("A 5 4 43200 20100223214617");
+    checkFromText_LexerError("A 5 4 43200 20100223214617 20100222214617");
+    checkFromText_LexerError("A 5 4 43200 20100223214617 20100222214617 "
+                              "8496");
+}
+
+TEST_F(Rdata_RRSIG_Test, badText_coveredType) {
+    checkFromText_InvalidType("SPORK");
+}
+
+TEST_F(Rdata_RRSIG_Test, badText_algorithm) {
+    checkFromText_InvalidText(
+                     "A 555 4 43200 "
+                     "20100223214617 20100222214617 8496 isc.org. "
+                     "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
+                     "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
+                     "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
+                     "f49t+sXKPzbipN9g+s1ZPiIyofc=");
+    checkFromText_LexerError(
+                     "A FIVE 4 43200 "
+                     "20100223214617 20100222214617 8496 isc.org. "
+                     "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
+                     "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
+                     "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
+                     "f49t+sXKPzbipN9g+s1ZPiIyofc=");
 }
 
-TEST_F(Rdata_RRSIG_Test, badText) {
-    EXPECT_THROW(const generic::RRSIG sig("SPORK"), InvalidRdataText);
-    EXPECT_THROW(const generic::RRSIG sig("A 555 4 43200 "
+TEST_F(Rdata_RRSIG_Test, badText_labels) {
+    checkFromText_InvalidText(
+                     "A 5 4444 43200 "
                      "20100223214617 20100222214617 8496 isc.org. "
                      "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
                      "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
                      "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
-                     "f49t+sXKPzbipN9g+s1ZPiIyofc="), InvalidRdataText);
-    EXPECT_THROW(const generic::RRSIG sig("A 5 4444 43200 "
+                     "f49t+sXKPzbipN9g+s1ZPiIyofc=");
+    checkFromText_LexerError(
+                     "A 5 FOUR 43200 "
                      "20100223214617 20100222214617 8496 isc.org. "
                      "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
                      "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
                      "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
-                     "f49t+sXKPzbipN9g+s1ZPiIyofc="), InvalidRdataText);
-    EXPECT_THROW(const generic::RRSIG sig("A 5 4 999999999999 "
+                     "f49t+sXKPzbipN9g+s1ZPiIyofc=");
+}
+
+TEST_F(Rdata_RRSIG_Test, badText_ttl) {
+    checkFromText_LexerError(
+                     "A 5 4 999999999999 "
                      "20100223214617 20100222214617 8496 isc.org. "
                      "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
                      "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
                      "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
-                     "f49t+sXKPzbipN9g+s1ZPiIyofc="), InvalidRdataText);
-    EXPECT_THROW(const generic::RRSIG sig("A 5 4 43200 "
-                     "20100223 20100227 8496 isc.org. "
+                     "f49t+sXKPzbipN9g+s1ZPiIyofc=");
+    checkFromText_LexerError(
+                     "A 5 4 TTL "
+                     "20100223214617 20100222214617 8496 isc.org. "
                      "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
                      "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
                      "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
-                     "f49t+sXKPzbipN9g+s1ZPiIyofc="), InvalidTime);
-    EXPECT_THROW(const generic::RRSIG sig("A 5 4 43200 "
+                     "f49t+sXKPzbipN9g+s1ZPiIyofc=");
+
+    // alternate form of TTL is not okay
+    checkFromText_LexerError(
+              "A 5 4 12H 20100223214617 20100222214617 8496 isc.org. "
+              "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz "
+              "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/ "
+              "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU "
+              "f49t+sXKPzbipN9g+s1ZPiIyofc=");
+}
+
+TEST_F(Rdata_RRSIG_Test, badText_expiration) {
+    checkFromText_InvalidTime(
+                     "A 5 4 43200 "
+                     "201002232 20100222214617 8496 isc.org. "
+                     "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
+                     "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
+                     "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
+                     "f49t+sXKPzbipN9g+s1ZPiIyofc=");
+    checkFromText_InvalidTime(
+                     "A 5 4 43200 "
+                     "EXPIRATION 20100222214617 8496 isc.org. "
+                     "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
+                     "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
+                     "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
+                     "f49t+sXKPzbipN9g+s1ZPiIyofc=");
+}
+
+TEST_F(Rdata_RRSIG_Test, badText_inception) {
+    checkFromText_InvalidTime(
+                     "A 5 4 43200 "
+                     "20100223214617 20100227 8496 isc.org. "
+                     "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
+                     "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
+                     "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
+                     "f49t+sXKPzbipN9g+s1ZPiIyofc=");
+    checkFromText_InvalidTime(
+                     "A 5 4 43200 "
+                     "20100223214617 INCEPTION 8496 isc.org. "
+                     "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
+                     "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
+                     "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
+                     "f49t+sXKPzbipN9g+s1ZPiIyofc=");
+}
+
+TEST_F(Rdata_RRSIG_Test, badText_keytag) {
+    checkFromText_InvalidText(
+                     "A 5 4 43200 "
                      "20100223214617 20100222214617 999999 isc.org. "
                      "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
                      "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
                      "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
-                     "f49t+sXKPzbipN9g+s1ZPiIyofc="), InvalidRdataText);
-    EXPECT_THROW(const generic::RRSIG sig(
+                     "f49t+sXKPzbipN9g+s1ZPiIyofc=");
+    checkFromText_LexerError(
                      "A 5 4 43200 "
-                     "20100223214617 20100222214617 8496 isc.org. "
-                     "EEeeeeeeEEEeeeeeeGaaahAAAAAAAAHHHHHHHHHHH!="),
-                 BadValue);     // bad base64 input
+                     "20100223214617 20100222214617 TAG isc.org. "
+                     "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
+                     "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
+                     "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
+                     "f49t+sXKPzbipN9g+s1ZPiIyofc=");
+}
+
+TEST_F(Rdata_RRSIG_Test, badText_signer) {
+    checkFromText_MissingOrigin(
+                     "A 5 4 43200 "
+                     "20100223214617 20100222214617 8496 isc.org "
+                     "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
+                     "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
+                     "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
+                     "f49t+sXKPzbipN9g+s1ZPiIyofc=");
 }
 
-TEST_F(Rdata_RRSIG_Test, DISABLED_badText) {
-    // this currently fails
+TEST_F(Rdata_RRSIG_Test, badText_signature) {
+    checkFromText_BadValue(
+                     "A 5 4 43200 "
+                     "20100223214617 20100222214617 8496 isc.org. "
+                     "EEeeeeeeEEEeeeeeeGaaahAAAAAAAAHHHHHHHHHHH!=");
+
     // no space between the tag and signer
-    EXPECT_THROW(generic::RRSIG("A 5 4 43200 20100223214617 20100222214617 "
-                                "8496isc.org. ofc="), InvalidRdataText);
+    checkFromText_LexerError(
+                     "A 5 4 43200 20100223214617 20100222214617 "
+                     "8496isc.org. ofc=");
+
+    // unterminated multi-line base64
+    checkFromText_LexerError(
+              "A 5 4 43200 20100223214617 20100222214617 8496 isc.org. "
+              "( evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz\n"
+              "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/\n"
+              "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU\n"
+              "f49t+sXKPzbipN9g+s1ZPiIyofc=");
 }
 
 TEST_F(Rdata_RRSIG_Test, createFromLexer) {
diff --git a/src/lib/dns/tests/rdata_sshfp_unittest.cc b/src/lib/dns/tests/rdata_sshfp_unittest.cc
index 6c13ad9..0b32ded 100644
--- a/src/lib/dns/tests/rdata_sshfp_unittest.cc
+++ b/src/lib/dns/tests/rdata_sshfp_unittest.cc
@@ -71,7 +71,7 @@ TEST_F(Rdata_SSHFP_Test, createFromText) {
 TEST_F(Rdata_SSHFP_Test, createFromLexer) {
     EXPECT_EQ(0, rdata_sshfp.compare(
         *test::createRdataUsingLexer(RRType::SSHFP(), RRClass::IN(),
-                                     "2 1 123456789abcdef67890123456789abcdef67890")));
+                                     sshfp_txt)));
 }
 
 TEST_F(Rdata_SSHFP_Test, algorithmTypes) {
@@ -103,7 +103,8 @@ TEST_F(Rdata_SSHFP_Test, algorithmTypes) {
 TEST_F(Rdata_SSHFP_Test, badText) {
     EXPECT_THROW(const generic::SSHFP rdata_sshfp("1"), InvalidRdataText);
     EXPECT_THROW(const generic::SSHFP rdata_sshfp("BUCKLE MY SHOES"), InvalidRdataText);
-    EXPECT_THROW(const generic::SSHFP rdata_sshfp("1 2 foo bar"), InvalidRdataText);
+    EXPECT_THROW(const generic::SSHFP rdata_sshfp("1 2 foo"), InvalidRdataText);
+    EXPECT_THROW(const generic::SSHFP rdata_sshfp("1 2 12ab bar"), InvalidRdataText);
 }
 
 TEST_F(Rdata_SSHFP_Test, copy) {
diff --git a/src/lib/dns/tests/rrcollator_unittest.cc b/src/lib/dns/tests/rrcollator_unittest.cc
index e66f87c..9123dc5 100644
--- a/src/lib/dns/tests/rrcollator_unittest.cc
+++ b/src/lib/dns/tests/rrcollator_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013  Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -59,10 +59,10 @@ protected:
         txt_rdata_(createRdata(RRType::TXT(), rrclass_, "test")),
         sig_rdata1_(createRdata(RRType::RRSIG(), rrclass_,
                                 "A 5 3 3600 20000101000000 20000201000000 "
-                                "12345 example.com. FAKE\n")),
+                                "12345 example.com. FAKE")),
         sig_rdata2_(createRdata(RRType::RRSIG(), rrclass_,
                                 "NS 5 3 3600 20000101000000 20000201000000 "
-                                "12345 example.com. FAKE\n"))
+                                "12345 example.com. FAKE"))
     {}
 
     void checkRRset(const Name& expected_name, const RRClass& expected_class,
diff --git a/src/lib/dns/tests/rrset_unittest.cc b/src/lib/dns/tests/rrset_unittest.cc
index d16ce3c..a605caf 100644
--- a/src/lib/dns/tests/rrset_unittest.cc
+++ b/src/lib/dns/tests/rrset_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2010-2013  Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -289,7 +289,7 @@ protected:
         rrset_rrsig = RRsetPtr(new RRset(test_name, RRClass::IN(),
                                          RRType::RRSIG(), RRTTL(3600)));
         rrset_rrsig->addRdata(generic::RRSIG("AAAA 5 3 7200 20100322084538 "
-                                             "20100220084538 1 example.com "
+                                             "20100220084538 1 example.com. "
                                              "FAKEFAKEFAKEFAKE"));
         rrset_aaaa->addRRsig(rrset_rrsig);
     }
diff --git a/src/lib/python/isc/statistics/counters.py b/src/lib/python/isc/statistics/counters.py
index 8138ab6..279c14b 100644
--- a/src/lib/python/isc/statistics/counters.py
+++ b/src/lib/python/isc/statistics/counters.py
@@ -217,8 +217,8 @@ class Counters():
         zones/example.com./ixfrreqv6
         zones/example.com./xfrsuccess
         zones/example.com./xfrfail
-        zones/example.com./time_to_ixfr
-        zones/example.com./time_to_axfr
+        zones/example.com./last_ixfr_duration
+        zones/example.com./last_axfr_duration
         ixfr_running
         axfr_running
         socket/unixdomain/open
@@ -327,7 +327,9 @@ class Counters():
     def start_timer(self, *args):
         """Starts a timer which is identified by args and keeps it
         running until stop_timer() is called. It acquires a lock to
-        support multi-threaded use."""
+        support multi-threaded use. If the specified timer is already
+        started but not yet stopped, the last start time is
+        overwritten."""
         identifier = _concat(*args)
         with self._rlock:
             if self._disabled: return
diff --git a/src/lib/python/isc/statistics/tests/counters_test.py b/src/lib/python/isc/statistics/tests/counters_test.py
index 395a959..5567dda 100644
--- a/src/lib/python/isc/statistics/tests/counters_test.py
+++ b/src/lib/python/isc/statistics/tests/counters_test.py
@@ -197,7 +197,7 @@ class BaseTestCounters():
         # for per-zone counters
         for name in self.counters._zones_item_list:
             args = (self._perzone_prefix, TEST_ZONE_NAME_STR, name)
-            if name.find('time_to_') == 0:
+            if name.find('last_') == 0 and name.endswith('_duration'):
                 self.counters.start_timer(*args)
                 self.counters.stop_timer(*args)
                 self.assertGreaterEqual(self.counters.get(*args), 0.0)
@@ -278,7 +278,7 @@ class BaseTestCounters():
         # setting all counters to zero
         for name in self.counters._zones_item_list:
             args = (self._perzone_prefix, TEST_ZONE_NAME_STR, name)
-            if name.find('time_to_') == 0:
+            if name.find('last_') == 0 and name.endswith('_duration'):
                 zero = 0.0
             else:
                 zero = 0
diff --git a/src/lib/python/isc/statistics/tests/testdata/test_spec3.spec b/src/lib/python/isc/statistics/tests/testdata/test_spec3.spec
index c97a09a..6c06f69 100644
--- a/src/lib/python/isc/statistics/tests/testdata/test_spec3.spec
+++ b/src/lib/python/isc/statistics/tests/testdata/test_spec3.spec
@@ -19,8 +19,8 @@
 	    "ixfrreqv6": 0,
 	    "xfrsuccess": 0,
 	    "xfrfail": 0,
-	    "time_to_ixfr": 0.0,
-	    "time_to_axfr": 0.0
+	    "last_ixfr_duration": 0.0,
+	    "last_axfr_duration": 0.0
           }
         },
         "item_title": "Zone names",
@@ -98,20 +98,20 @@
               "item_description": "Number of zone transfer requests failed"
             },
             {
-              "item_name": "time_to_ixfr",
+              "item_name": "last_ixfr_duration",
               "item_type": "real",
               "item_optional": false,
               "item_default": 0.0,
-              "item_title": "Time to IXFR",
-              "item_description": "Elapsed time in seconds to do the last IXFR"
+              "item_title": "Last IXFR duration",
+              "item_description": "Duration of the last IXFR. 0.0 means no successful IXFR done."
             },
             {
-              "item_name": "time_to_axfr",
+              "item_name": "last_axfr_duration",
               "item_type": "real",
               "item_optional": false,
               "item_default": 0.0,
-              "item_title": "Time to AXFR",
-              "item_description": "Elapsed time in seconds to do the last AXFR"
+              "item_title": "Last AXFR duration",
+              "item_description": "Duration of the last AXFR. 0.0 means no successful AXFR done."
             }
           ]
         }
diff --git a/tests/lettuce/configurations/xfrin/retransfer_master.conf.orig b/tests/lettuce/configurations/xfrin/retransfer_master.conf.orig
index 1b2953d..d7ea9a5 100644
--- a/tests/lettuce/configurations/xfrin/retransfer_master.conf.orig
+++ b/tests/lettuce/configurations/xfrin/retransfer_master.conf.orig
@@ -33,9 +33,6 @@
             "port": 47806
         } ]
     },
-    "Stats": {
-        "poll-interval": 1
-    },
     "Init": {
         "components": {
             "b10-auth": { "kind": "needed", "special": "auth" },
diff --git a/tests/lettuce/configurations/xfrin/retransfer_master_v4.conf.orig b/tests/lettuce/configurations/xfrin/retransfer_master_v4.conf.orig
new file mode 100644
index 0000000..755c91b
--- /dev/null
+++ b/tests/lettuce/configurations/xfrin/retransfer_master_v4.conf.orig
@@ -0,0 +1,45 @@
+{
+    "version": 3,
+    "Logging": {
+        "loggers": [ {
+            "debuglevel": 99,
+            "severity": "DEBUG",
+            "name": "*"
+        } ]
+    },
+    "Auth": {
+        "database_file": "data/example.org.sqlite3",
+        "listen_on": [ {
+            "address": "127.0.0.1",
+            "port": 47809
+        } ]
+    },
+    "data_sources": {
+        "classes": {
+            "IN": [{
+                "type": "sqlite3",
+                "params": {
+                    "database_file": "data/example.org.sqlite3"
+                }
+            }]
+        }
+    },
+    "Xfrout": {
+        "zone_config": [ {
+            "origin": "example.org"
+        } ],
+        "also_notify": [ {
+            "address": "127.0.0.1",
+            "port": 47806
+        } ]
+    },
+    "Init": {
+        "components": {
+            "b10-auth": { "kind": "needed", "special": "auth" },
+            "b10-xfrout": { "address": "Xfrout", "kind": "dispensable" },
+            "b10-zonemgr": { "address": "Zonemgr", "kind": "dispensable" },
+            "b10-stats": { "address": "Stats", "kind": "dispensable" },
+            "b10-cmdctl": { "special": "cmdctl", "kind": "needed" }
+        }
+    }
+}
diff --git a/tests/lettuce/configurations/xfrin/retransfer_slave_notify.conf.orig b/tests/lettuce/configurations/xfrin/retransfer_slave_notify.conf.orig
index a5c22b1..3040b6c 100644
--- a/tests/lettuce/configurations/xfrin/retransfer_slave_notify.conf.orig
+++ b/tests/lettuce/configurations/xfrin/retransfer_slave_notify.conf.orig
@@ -42,6 +42,7 @@
             "b10-auth": { "kind": "needed", "special": "auth" },
             "b10-xfrin": { "address": "Xfrin", "kind": "dispensable" },
             "b10-zonemgr": { "address": "Zonemgr", "kind": "dispensable" },
+            "b10-stats": { "address": "Stats", "kind": "dispensable" },
             "b10-cmdctl": { "special": "cmdctl", "kind": "needed" }
         }
     }
diff --git a/tests/lettuce/configurations/xfrin/retransfer_slave_notify_v4.conf b/tests/lettuce/configurations/xfrin/retransfer_slave_notify_v4.conf
new file mode 100644
index 0000000..67ebfd3
--- /dev/null
+++ b/tests/lettuce/configurations/xfrin/retransfer_slave_notify_v4.conf
@@ -0,0 +1,49 @@
+{
+    "version": 3,
+    "Logging": {
+        "loggers": [ {
+            "debuglevel": 99,
+            "severity": "DEBUG",
+            "name": "*"
+        } ]
+    },
+    "Auth": {
+        "database_file": "data/xfrin-notify.sqlite3",
+        "listen_on": [ {
+            "address": "127.0.0.1",
+            "port": 47806
+        } ]
+    },
+    "data_sources": {
+        "classes": {
+            "IN": [{
+                "type": "sqlite3",
+                "params": {
+                    "database_file": "data/xfrin-notify.sqlite3"
+                }
+            }]
+        }
+    },
+    "Xfrin": {
+        "zones": [ {
+            "name": "example.org",
+            "master_addr": "127.0.0.1",
+            "master_port": 47809
+        } ]
+    },
+    "Zonemgr": {
+        "secondary_zones": [ {
+            "name": "example.org",
+            "class": "IN"
+        } ]
+    },
+    "Init": {
+        "components": {
+            "b10-auth": { "kind": "needed", "special": "auth" },
+            "b10-xfrin": { "address": "Xfrin", "kind": "dispensable" },
+            "b10-zonemgr": { "address": "Zonemgr", "kind": "dispensable" },
+            "b10-stats": { "address": "Stats", "kind": "dispensable" },
+            "b10-cmdctl": { "special": "cmdctl", "kind": "needed" }
+        }
+    }
+}
diff --git a/tests/lettuce/features/terrain/bind10_control.py b/tests/lettuce/features/terrain/bind10_control.py
index d32cd20..e288aa9 100644
--- a/tests/lettuce/features/terrain/bind10_control.py
+++ b/tests/lettuce/features/terrain/bind10_control.py
@@ -389,67 +389,6 @@ def query_statistics(step, statistics, name, cmdctl_port):
         % (port_str, name,\
                ' name=%s' % statistics if statistics else ''))
 
-def find_value(dictionary, key):
-    """A helper method. Recursively find a value corresponding to the
-    key of the dictionary and returns it. Returns None if the
-    dictionary is not dict type."""
-    if type(dictionary) is not dict:
-        return
-    if key in dictionary:
-        return dictionary[key]
-    else:
-        for v in dictionary.values():
-            return find_value(v, key)
-
- at step('the statistics counter (\S+)(?: in the category (\S+))?'+ \
-          '(?: for the zone (\S+))? should be' + \
-          '(?:( greater than| less than| between))? (\-?\d+)(?: and (\-?\d+))?')
-def check_statistics(step, counter, category, zone, gtltbt, number, upper):
-    """
-    check the output of bindctl for statistics of specified counter
-    and zone.
-    Parameters:
-    counter ('counter <counter>'): The counter name of statistics.
-    category ('category <category>', optional): The category of counter.
-    zone ('zone <zone>', optional): The zone name.
-    gtltbt (' greater than'|' less than'|' between', optional): greater than
-          <number> or less than <number> or between <number> and <upper>.
-    number ('<number>): The expect counter number. <number> is assumed
-          to be an unsigned integer.
-    upper ('<upper>, optional): The expect upper counter number when
-          using 'between'.
-    """
-    output = parse_bindctl_output_as_data_structure()
-    found = None
-    category_str = ""
-    zone_str = ""
-    depth = []
-    if category:
-        depth.insert(0, category)
-        category_str = " for category %s" % category
-    if zone:
-        depth.insert(0, zone)
-        zone_str = " for zone %s" % zone
-    for level in depth:
-        output = find_value(output, level)
-    found = find_value(output, counter)
-    assert found is not None, \
-        'Not found statistics counter %s%s%s' % \
-            (counter, category_str, zone_str)
-    msg = "Got %s, expected%s %s as counter %s%s" % \
-        (found, gtltbt, number, counter, zone_str)
-    if gtltbt and 'between' in gtltbt and upper:
-        msg = "Got %s, expected%s %s and %s as counter %s%s" % \
-            (found, gtltbt, number, upper, counter, zone_str)
-        assert int(number) <= int(found) \
-            and int(found) <= int(upper), msg
-    elif gtltbt and 'greater' in gtltbt:
-        assert int(found) > int(number), msg
-    elif gtltbt and 'less' in gtltbt:
-        assert int(found) < int(number), msg
-    else:
-        assert int(found) == int(number), msg
-
 @step('statistics counters are 0 in category (\S+)( except for the' + \
           ' following items)?')
 def check_statistics_items(step, category, has_except_for):
@@ -461,9 +400,14 @@ def check_statistics_items(step, category, has_except_for):
         with the multiline part.
 
     Expected values of items are taken from the multiline part of the step in
-    the scenario. The multiline part has two columns: item_name and item_value.
-    item_name is a relative name to category. item_value is an expected value
-    for item_name.
+    the scenario. The multiline part has at most four columns: item_name,
+    item_value, min_value, and max_value. item_name is a relative name
+    to category. item_value is an expected value for
+    item_name. min_value and max_value are expected to be used when
+    item_value cannot be specified to be item_value. min_value is the
+    minimum value in the expected range, and max_value is the maximum
+    value in the expected range. Values would be examined if they are
+    in columns corresponding to these.
     """
 
     def flatten(dictionary, prefix=''):
@@ -480,15 +424,55 @@ def check_statistics_items(step, category, has_except_for):
         # fetch step tables in the scnario as hashes
         for item in step.hashes:
             name = category+'.'+item['item_name']
-            value = item['item_value']
             assert stats.has_key(name), \
                 'Statistics item %s was not found' % (name)
             found = stats[name]
-            assert int(found) == int(value), \
-                'Statistics item %s has unexpected value %s (expect %s)' % \
+            if 'item_value' in item and item['item_value']:
+                value = item['item_value']
+                assert int(found) == int(value), \
+                    'Statistics item %s has unexpected value %s (expect %s)' % \
+                    (name, found, value)
+            if 'min_value' in item and item['min_value']:
+                value = item['min_value']
+                assert float(value) <= float(found), \
+                    'Statistics item %s has unexpected value %s (expect %s or greater than)' % \
+                    (name, found, value)
+            if 'max_value' in item and item['max_value']:
+                value = item['max_value']
+                assert float(found) <= float(value), \
+                    'Statistics item %s has unexpected value %s (expect %s or less than)' % \
                     (name, found, value)
             del(stats[name])
     for name, found in stats.items():
         assert int(found) == 0, \
             'Statistics item %s has unexpected value %s (expect %s)' % \
                 (name, found, 0)
+
+ at step('check initial statistics(?:( not)? containing (\S+))? for (\S+)'
+      '( with cmdctl port \d+)?( except for the following items)?')
+def check_init_statistics(step, notv, string, name, cmdctl_port, has_except_for):
+    """Checks the initial statistics for the module. Also checks a
+    string is contained or not contained in them. Statistics counters
+      other than zero can follow below.
+    Parameters:
+    notv ('not'): reverse the check (fail if string is found)
+    string ('containing <string>') string to look for
+    name ('module <name>'): The name of the module (case sensitive!)
+    cmdctl_port ('with cmdctl port <portnr>', optional): cmdctl port to send
+                the command to.
+    has_except_for ('except for the following items'): checks values of items
+        with the multiline part.
+    """
+    query_str = 'query statistics of bind10 module ' + name
+    if cmdctl_port:
+        query_str = query_str + cmdctl_port
+    notcontain_str = 'last bindctl output should%s contain "%s"'
+    check_str = 'statistics counters are 0 in category .' + name
+    if has_except_for:
+        check_str = check_str + has_except_for + "\n" \
+            + step.represent_hashes()
+    step.given(query_str)
+    step.given(notcontain_str % (' not', 'error'))
+    if string is not None:
+        step.given(notcontain_str % (notv, string))
+    step.given(check_str)
diff --git a/tests/lettuce/features/terrain/terrain.py b/tests/lettuce/features/terrain/terrain.py
index 2f01723..b861442 100644
--- a/tests/lettuce/features/terrain/terrain.py
+++ b/tests/lettuce/features/terrain/terrain.py
@@ -68,6 +68,8 @@ copylist = [
      "configurations/ddns/noddns.config"],
     ["configurations/xfrin/retransfer_master.conf.orig",
      "configurations/xfrin/retransfer_master.conf"],
+    ["configurations/xfrin/retransfer_master_v4.conf.orig",
+     "configurations/xfrin/retransfer_master_v4.conf"],
     ["configurations/xfrin/retransfer_master_nons.conf.orig",
      "configurations/xfrin/retransfer_master_nons.conf"],
     ["configurations/xfrin/retransfer_slave.conf.orig",
diff --git a/tests/lettuce/features/xfrin_notify_handling.feature b/tests/lettuce/features/xfrin_notify_handling.feature
index c14254f..375a8a9 100644
--- a/tests/lettuce/features/xfrin_notify_handling.feature
+++ b/tests/lettuce/features/xfrin_notify_handling.feature
@@ -1,5 +1,10 @@
 Feature: Xfrin incoming notify handling
-    Tests for Xfrin incoming notify handling.
+    Tests for Xfrin incoming notify handling. They also test
+    statistics counters incremented, which are related to notifying
+    and transferring by Xfrout and receiveing by Xfrin. Some cases are
+    considered: Transferring is done via IPv4 or IPv6 transport. A
+    transfer request from Xfrin is rejected by Xfrout. The master
+    server or slave server is unreachable.
 
     Scenario: Handle incoming notify
     Given I have bind10 running with configuration xfrin/retransfer_master.conf with cmdctl port 47804 as master
@@ -20,33 +25,125 @@ Feature: Xfrin incoming notify handling
     A query for www.example.org to [::1]:47806 should have rcode NXDOMAIN
 
     #
-    # Test for statistics
+    # Test1 for Xfrout statistics
+    #
+    check initial statistics not containing example.org for Xfrout with cmdctl port 47804 except for the following items
+      | item_name                | item_max | item_min |
+      | socket.unixdomain.open   |        1 |        0 |
+    # Note: .Xfrout.socket.unixdomain.open can be either expected to
+    # be 0 or 1 here.  The reason is: if b10-xfrout has started up and is
+    # ready for a request from b10-stats, then b10-stats does request
+    # to b10-xfrout and the value results in 1. Otherwise if
+    # b10-xfrout is starting and isn't yet ready, then b10-stats
+    # doesn't request to b10-xfrout and the value still remains to be the
+    # default value(0).
+
+    #
+    # Test2 for Xfrin statistics
+    #
+    check initial statistics not containing example.org for Xfrin
+
+    When I send bind10 with cmdctl port 47804 the command Xfrout notify example.org IN
+    Then wait for new master stderr message XFROUT_NOTIFY_COMMAND
+    Then wait for new bind10 stderr message AUTH_RECEIVED_NOTIFY
+    # From this point we can't reliably 'wait for new' because the ordering
+    # of logs from different processes is unpredictable.  But these
+    # should be okay in this case.
+    Then wait for bind10 stderr message ZONEMGR_RECEIVE_NOTIFY
+    Then wait for bind10 stderr message XFRIN_XFR_TRANSFER_STARTED
+    Then wait for bind10 stderr message XFRIN_TRANSFER_SUCCESS not XFRIN_XFR_PROCESS_FAILURE
+    Then wait for bind10 stderr message ZONEMGR_RECEIVE_XFRIN_SUCCESS
+    Then wait for master stderr message NOTIFY_OUT_REPLY_RECEIVED
+
+    A query for www.example.org to [::1]:47806 should have rcode NOERROR
+    # Make sure handling statistics command handling checked below is
+    # after this query
+    And wait for bind10 stderr message AUTH_SEND_NORMAL_RESPONSE
+
+    #
+    # Test3 for Xfrout statistics
     #
-    # check for initial statistics
+    # check statistics change
     #
+
+    # wait until the last stats requesting is finished
     When I query statistics zones of bind10 module Xfrout with cmdctl port 47804
+    # note that this does not 100% guarantee the stats updated Xfrout
+    # statistics.  But there doesn't seem to be a better log message that
+    # suggests this event.
+    wait for new master stderr message XFROUT_RECEIVED_GETSTATS_COMMAND
     last bindctl output should not contain "error"
-    last bindctl output should not contain "example.org."
-    Then the statistics counter notifyoutv4 for the zone _SERVER_ should be 0
-    Then the statistics counter notifyoutv6 for the zone _SERVER_ should be 0
-    Then the statistics counter xfrrej for the zone _SERVER_ should be 0
-    Then the statistics counter xfrreqdone for the zone _SERVER_ should be 0
-
-    When I query statistics ixfr_running of bind10 module Xfrout with cmdctl port 47804
-    Then the statistics counter ixfr_running should be 0
 
-    When I query statistics axfr_running of bind10 module Xfrout with cmdctl port 47804
-    Then the statistics counter axfr_running should be 0
+    When I query statistics zones of bind10 module Xfrout with cmdctl port 47804
+    The statistics counters are 0 in category .Xfrout.zones except for the following items
+      | item_name                | item_value |
+      | _SERVER_.notifyoutv6     |          1 |
+      | _SERVER_.xfrreqdone      |          1 |
+      | example.org..notifyoutv6 |          1 |
+      | example.org..xfrreqdone  |          1 |
 
     When I query statistics socket of bind10 module Xfrout with cmdctl port 47804
-    Then the statistics counter open should be between 0 and 1
-    Then the statistics counter openfail should be 0
-    Then the statistics counter close should be 0
-    Then the statistics counter bindfail should be 0
-    Then the statistics counter acceptfail should be 0
-    Then the statistics counter accept should be 0
-    Then the statistics counter senderr should be 0
-    Then the statistics counter recverr should be 0
+    The statistics counters are 0 in category .Xfrout.socket.unixdomain except for the following items
+      | item_name | item_value |
+      | open      |          1 |
+      | accept    |          1 |
+
+    #
+    # Test4 for Xfrin statistics
+    #
+    # check statistics change
+    #
+
+    # wait until the last stats requesting is finished
+    When I query statistics zones of bind10 module Xfrin with cmdctl
+    wait for new bind10 stderr message XFRIN_RECEIVED_COMMAND
+    last bindctl output should not contain "error"
+
+    When I query statistics zones of bind10 module Xfrin with cmdctl
+    The statistics counters are 0 in category .Xfrin.zones except for the following items
+      | item_name                       | item_value | min_value |
+      | _SERVER_.soaoutv6               |          1 |           |
+      | _SERVER_.axfrreqv6              |          1 |           |
+      | _SERVER_.xfrsuccess             |          1 |           |
+      | _SERVER_.last_axfr_duration     |            |       0.0 |
+      | example.org..soaoutv6           |          1 |           |
+      | example.org..axfrreqv6          |          1 |           |
+      | example.org..xfrsuccess         |          1 |           |
+      | example.org..last_axfr_duration |            |       0.0 |
+
+    #
+    # Test for handling incoming notify only in IPv4
+    #
+    Scenario: Handle incoming notify (IPv4)
+    Given I have bind10 running with configuration xfrin/retransfer_master_v4.conf with cmdctl port 47804 as master
+    And wait for master stderr message BIND10_STARTED_CC
+    And wait for master stderr message CMDCTL_STARTED
+    And wait for master stderr message AUTH_SERVER_STARTED
+    And wait for master stderr message XFROUT_STARTED
+    And wait for master stderr message ZONEMGR_STARTED
+    And wait for master stderr message STATS_STARTING
+
+    And I have bind10 running with configuration xfrin/retransfer_slave_notify_v4.conf
+    And wait for bind10 stderr message BIND10_STARTED_CC
+    And wait for bind10 stderr message CMDCTL_STARTED
+    And wait for bind10 stderr message AUTH_SERVER_STARTED
+    And wait for bind10 stderr message XFRIN_STARTED
+    And wait for bind10 stderr message ZONEMGR_STARTED
+
+    A query for www.example.org to 127.0.0.1:47806 should have rcode NXDOMAIN
+
+    #
+    # Test1 for Xfrout statistics
+    #
+    check initial statistics not containing example.org for Xfrout with cmdctl port 47804 except for the following items
+      | item_name                | item_max | item_min |
+      | socket.unixdomain.open   |        1 |        0 |
+    # Note: See above about .Xfrout.socket.unixdomain.open.
+
+    #
+    # Test2 for Xfrin statistics
+    #
+    check initial statistics not containing example.org for Xfrin
 
     When I send bind10 with cmdctl port 47804 the command Xfrout notify example.org IN
     Then wait for new master stderr message XFROUT_NOTIFY_COMMAND
@@ -60,43 +157,61 @@ Feature: Xfrin incoming notify handling
     Then wait for bind10 stderr message ZONEMGR_RECEIVE_XFRIN_SUCCESS
     Then wait for master stderr message NOTIFY_OUT_REPLY_RECEIVED
 
-    A query for www.example.org to [::1]:47806 should have rcode NOERROR
+    A query for www.example.org to 127.0.0.1:47806 should have rcode NOERROR
     # Make sure handling statistics command handling checked below is
     # after this query
     And wait for bind10 stderr message AUTH_SEND_NORMAL_RESPONSE
 
     #
-    # Test for statistics
+    # Test3 for Xfrout statistics
     #
-    # check for statistics change
+    # check statistics change
     #
 
     # wait until the last stats requesting is finished
+    When I query statistics zones of bind10 module Xfrout with cmdctl port 47804
     # note that this does not 100% guarantee the stats updated Xfrout
     # statistics.  But there doesn't seem to be a better log message that
     # suggests this event.
     wait for new master stderr message XFROUT_RECEIVED_GETSTATS_COMMAND
+    last bindctl output should not contain "error"
 
     When I query statistics zones of bind10 module Xfrout with cmdctl port 47804
-    last bindctl output should not contain "error"
-    Then the statistics counter notifyoutv4 for the zone _SERVER_ should be 0
-    Then the statistics counter notifyoutv4 for the zone example.org. should be 0
-    Then the statistics counter notifyoutv6 for the zone _SERVER_ should be 1
-    Then the statistics counter notifyoutv6 for the zone example.org. should be 1
-    Then the statistics counter xfrrej for the zone _SERVER_ should be 0
-    Then the statistics counter xfrrej for the zone example.org. should be 0
-    Then the statistics counter xfrreqdone for the zone _SERVER_ should be 1
-    Then the statistics counter xfrreqdone for the zone example.org. should be 1
+    The statistics counters are 0 in category .Xfrout.zones except for the following items
+      | item_name                | item_value |
+      | _SERVER_.notifyoutv4     |          1 |
+      | _SERVER_.xfrreqdone      |          1 |
+      | example.org..notifyoutv4 |          1 |
+      | example.org..xfrreqdone  |          1 |
 
     When I query statistics socket of bind10 module Xfrout with cmdctl port 47804
-    Then the statistics counter open should be 1
-    Then the statistics counter openfail should be 0
-    Then the statistics counter close should be 0
-    Then the statistics counter bindfail should be 0
-    Then the statistics counter acceptfail should be 0
-    Then the statistics counter accept should be 1
-    Then the statistics counter senderr should be 0
-    Then the statistics counter recverr should be 0
+    The statistics counters are 0 in category .Xfrout.socket.unixdomain except for the following items
+      | item_name | item_value |
+      | open      |          1 |
+      | accept    |          1 |
+
+    #
+    # Test4 for Xfrin statistics
+    #
+    # check statistics change
+    #
+
+    # wait until the last stats requesting is finished
+    When I query statistics zones of bind10 module Xfrin with cmdctl
+    wait for new bind10 stderr message XFRIN_RECEIVED_COMMAND
+    last bindctl output should not contain "error"
+
+    When I query statistics zones of bind10 module Xfrin with cmdctl
+    The statistics counters are 0 in category .Xfrin.zones except for the following items
+      | item_name                       | item_value | min_value |
+      | _SERVER_.soaoutv4               |          1 |           |
+      | _SERVER_.axfrreqv4              |          1 |           |
+      | _SERVER_.xfrsuccess             |          1 |           |
+      | _SERVER_.last_axfr_duration     |            |       0.0 |
+      | example.org..soaoutv4           |          1 |           |
+      | example.org..axfrreqv4          |          1 |           |
+      | example.org..xfrsuccess         |          1 |           |
+      | example.org..last_axfr_duration |            |       0.0 |
 
     #
     # Test for Xfr request rejected
@@ -120,33 +235,123 @@ Feature: Xfrin incoming notify handling
     A query for www.example.org to [::1]:47806 should have rcode NXDOMAIN
 
     #
-    # Test1 for statistics
+    # Test1 for Xfrout statistics
     #
-    # check for initial statistics
+    check initial statistics not containing example.org for Xfrout with cmdctl port 47804 except for the following items
+      | item_name                | item_max | item_min |
+      | socket.unixdomain.open   |        1 |        0 |
+    # Note: See above about .Xfrout.socket.unixdomain.open.
+
     #
+    # Test2 for Xfrin statistics
+    #
+    check initial statistics not containing example.org for Xfrin
+
+    #
+    # set transfer_acl rejection
+    # Local xfr requests from Xfrin module would be rejected here.
+    #
+    When I send bind10 the following commands with cmdctl port 47804
+    """
+    config set Xfrout/zone_config[0]/transfer_acl [{"action":  "REJECT", "from": "::1"}]
+    config commit
+    """
+    last bindctl output should not contain Error
+
+    When I send bind10 with cmdctl port 47804 the command Xfrout notify example.org IN
+    Then wait for new master stderr message XFROUT_NOTIFY_COMMAND
+    Then wait for new bind10 stderr message AUTH_RECEIVED_NOTIFY
+    # can't use 'wait for new' below.
+    Then wait for bind10 stderr message ZONEMGR_RECEIVE_NOTIFY
+    Then wait for bind10 stderr message XFRIN_XFR_TRANSFER_STARTED
+    Then wait for bind10 stderr message XFRIN_XFR_TRANSFER_PROTOCOL_VIOLATION not XFRIN_TRANSFER_SUCCESS
+    Then wait for bind10 stderr message ZONEMGR_RECEIVE_XFRIN_FAILED not ZONEMGR_RECEIVE_XFRIN_SUCCESS
+    Then wait for master stderr message NOTIFY_OUT_REPLY_RECEIVED
+
+    A query for www.example.org to [::1]:47806 should have rcode NXDOMAIN
+
+    #
+    # Test3 for Xfrout statistics
+    #
+    # check statistics change
+    #
+
+    # wait until the last stats requesting is finished
     When I query statistics zones of bind10 module Xfrout with cmdctl port 47804
+    wait for new master stderr message XFROUT_RECEIVED_GETSTATS_COMMAND
     last bindctl output should not contain "error"
-    last bindctl output should not contain "example.org."
-    Then the statistics counter notifyoutv4 for the zone _SERVER_ should be 0
-    Then the statistics counter notifyoutv6 for the zone _SERVER_ should be 0
-    Then the statistics counter xfrrej for the zone _SERVER_ should be 0
-    Then the statistics counter xfrreqdone for the zone _SERVER_ should be 0
-
-    When I query statistics ixfr_running of bind10 module Xfrout with cmdctl port 47804
-    Then the statistics counter ixfr_running should be 0
 
-    When I query statistics axfr_running of bind10 module Xfrout with cmdctl port 47804
-    Then the statistics counter axfr_running should be 0
+    When I query statistics zones of bind10 module Xfrout with cmdctl port 47804
+    The statistics counters are 0 in category .Xfrout.zones except for the following items
+      | item_name                | item_value | min_value | max_value |
+      | _SERVER_.notifyoutv6     |          1 |           |           |
+      | _SERVER_.xfrrej          |            |         1 |         3 |
+      | example.org..notifyoutv6 |          1 |           |           |
+      | example.org..xfrrej      |            |         1 |         3 |
+    # Note: The above rejection counters might sometimes be increased
+    # up to 3. See this for details
+    # http://git.bind10.isc.org/~tester/builder/BIND10-lettuce/20120918210000-MacOS/logs/lettuce.out
 
     When I query statistics socket of bind10 module Xfrout with cmdctl port 47804
-    Then the statistics counter open should be between 0 and 1
-    Then the statistics counter openfail should be 0
-    Then the statistics counter close should be 0
-    Then the statistics counter bindfail should be 0
-    Then the statistics counter acceptfail should be 0
-    Then the statistics counter accept should be 0
-    Then the statistics counter senderr should be 0
-    Then the statistics counter recverr should be 0
+    The statistics counters are 0 in category .Xfrout.socket.unixdomain except for the following items
+      | item_name | item_value |
+      | open      |          1 |
+      | accept    |          1 |
+
+    #
+    # Test4 for Xfrin statistics
+    #
+    # check statistics change
+    #
+
+    # wait until the last stats requesting is finished
+    When I query statistics zones of bind10 module Xfrin with cmdctl
+    wait for new bind10 stderr message XFRIN_RECEIVED_COMMAND
+    last bindctl output should not contain "error"
+
+    When I query statistics zones of bind10 module Xfrin with cmdctl
+    The statistics counters are 0 in category .Xfrin.zones except for the following items
+      | item_name              | item_value |
+      | _SERVER_.soaoutv6      |          1 |
+      | _SERVER_.axfrreqv6     |          1 |
+      | _SERVER_.xfrfail       |          1 |
+      | example.org..soaoutv6  |          1 |
+      | example.org..axfrreqv6 |          1 |
+      | example.org..xfrfail   |          1 |
+
+    #
+    # Test for Xfr request rejected in IPv4
+    #
+    Scenario: Handle incoming notify (XFR request rejected in IPv4)
+    Given I have bind10 running with configuration xfrin/retransfer_master_v4.conf with cmdctl port 47804 as master
+    And wait for master stderr message BIND10_STARTED_CC
+    And wait for master stderr message CMDCTL_STARTED
+    And wait for master stderr message AUTH_SERVER_STARTED
+    And wait for master stderr message XFROUT_STARTED
+    And wait for master stderr message ZONEMGR_STARTED
+    And wait for master stderr message STATS_STARTING
+
+    And I have bind10 running with configuration xfrin/retransfer_slave_notify_v4.conf
+    And wait for bind10 stderr message BIND10_STARTED_CC
+    And wait for bind10 stderr message CMDCTL_STARTED
+    And wait for bind10 stderr message AUTH_SERVER_STARTED
+    And wait for bind10 stderr message XFRIN_STARTED
+    And wait for bind10 stderr message ZONEMGR_STARTED
+
+    A query for www.example.org to 127.0.0.1:47806 should have rcode NXDOMAIN
+
+    #
+    # Test1 for Xfrout statistics
+    #
+    check initial statistics not containing example.org for Xfrout with cmdctl port 47804 except for the following items
+      | item_name                | item_max | item_min |
+      | socket.unixdomain.open   |        1 |        0 |
+    # Note: See above about .Xfrout.socket.unixdomain.open.
+
+    #
+    # Test2 for Xfrin statistics
+    #
+    check initial statistics not containing example.org for Xfrin
 
     #
     # set transfer_acl rejection
@@ -154,7 +359,7 @@ Feature: Xfrin incoming notify handling
     #
     When I send bind10 the following commands with cmdctl port 47804
     """
-    config set Xfrout/zone_config[0]/transfer_acl [{"action":  "REJECT", "from": "::1"}]
+    config set Xfrout/zone_config[0]/transfer_acl [{"action":  "REJECT", "from": "127.0.0.1"}]
     config commit
     """
     last bindctl output should not contain Error
@@ -169,39 +374,56 @@ Feature: Xfrin incoming notify handling
     Then wait for bind10 stderr message ZONEMGR_RECEIVE_XFRIN_FAILED not ZONEMGR_RECEIVE_XFRIN_SUCCESS
     Then wait for master stderr message NOTIFY_OUT_REPLY_RECEIVED
 
-    A query for www.example.org to [::1]:47806 should have rcode NXDOMAIN
+    A query for www.example.org to 127.0.0.1:47806 should have rcode NXDOMAIN
 
     #
-    # Test2 for statistics
+    # Test3 for Xfrout statistics
     #
-    # check for statistics change
+    # check statistics change
     #
 
+    When I query statistics zones of bind10 module Xfrout with cmdctl port 47804
     # wait until stats request at least after NOTIFY_OUT_REPLY_RECEIVED
     wait for new master stderr message XFROUT_RECEIVED_GETSTATS_COMMAND
+    last bindctl output should not contain "error"
 
     When I query statistics zones of bind10 module Xfrout with cmdctl port 47804
-    last bindctl output should not contain "error"
-    Then the statistics counter notifyoutv4 for the zone _SERVER_ should be 0
-    Then the statistics counter notifyoutv4 for the zone example.org. should be 0
-    Then the statistics counter notifyoutv6 for the zone _SERVER_ should be 1
-    Then the statistics counter notifyoutv6 for the zone example.org. should be 1
-    # The counts of rejection would be between 1 and 2. They are not
-    # fixed. It would depend on timing or the platform.
-    Then the statistics counter xfrrej for the zone _SERVER_ should be greater than 0
-    Then the statistics counter xfrrej for the zone example.org. should be greater than 0
-    Then the statistics counter xfrreqdone for the zone _SERVER_ should be 0
-    Then the statistics counter xfrreqdone for the zone example.org. should be 0
+    The statistics counters are 0 in category .Xfrout.zones except for the following items
+      | item_name                | item_value | min_value | max_value |
+      | _SERVER_.notifyoutv4     |          1 |           |           |
+      | _SERVER_.xfrrej          |            |         1 |         3 |
+      | example.org..notifyoutv4 |          1 |           |           |
+      | example.org..xfrrej      |            |         1 |         3 |
+    # Note: The above rejection counters might sometimes be increased
+    # up to 3. See this for details
+    # http://git.bind10.isc.org/~tester/builder/BIND10-lettuce/20120918210000-MacOS/logs/lettuce.out
 
     When I query statistics socket of bind10 module Xfrout with cmdctl port 47804
-    Then the statistics counter open should be 1
-    Then the statistics counter openfail should be 0
-    Then the statistics counter close should be 0
-    Then the statistics counter bindfail should be 0
-    Then the statistics counter acceptfail should be 0
-    Then the statistics counter accept should be 1
-    Then the statistics counter senderr should be 0
-    Then the statistics counter recverr should be 0
+    The statistics counters are 0 in category .Xfrout.socket.unixdomain except for the following items
+      | item_name | item_value |
+      | open      |          1 |
+      | accept    |          1 |
+
+    #
+    # Test4 for Xfrin statistics
+    #
+    # check statistics change
+    #
+
+    # wait until the last stats requesting is finished
+    When I query statistics zones of bind10 module Xfrin with cmdctl
+    wait for new bind10 stderr message XFRIN_RECEIVED_COMMAND
+    last bindctl output should not contain "error"
+
+    When I query statistics zones of bind10 module Xfrin with cmdctl
+    The statistics counters are 0 in category .Xfrin.zones except for the following items
+      | item_name              | item_value |
+      | _SERVER_.soaoutv4      |          1 |
+      | _SERVER_.axfrreqv4     |          1 |
+      | _SERVER_.xfrfail       |          1 |
+      | example.org..soaoutv4  |          1 |
+      | example.org..axfrreqv4 |          1 |
+      | example.org..xfrfail   |          1 |
 
     #
     # Test for unreachable slave
@@ -226,29 +448,21 @@ Feature: Xfrin incoming notify handling
     # check statistics change
     #
 
+    When I query statistics zones of bind10 module Xfrout with cmdctl port 47804
     # wait until stats request at least after NOTIFY_OUT_TIMEOUT
     wait for new master stderr message XFROUT_RECEIVED_GETSTATS_COMMAND
+    last bindctl output should not contain "error"
 
     When I query statistics zones of bind10 module Xfrout with cmdctl port 47804
-    last bindctl output should not contain "error"
-    Then the statistics counter notifyoutv4 for the zone _SERVER_ should be 0
-    Then the statistics counter notifyoutv4 for the zone example.org. should be 0
-    Then the statistics counter notifyoutv6 for the zone _SERVER_ should be greater than 0
-    Then the statistics counter notifyoutv6 for the zone example.org. should be greater than 0
-    Then the statistics counter xfrrej for the zone _SERVER_ should be 0
-    Then the statistics counter xfrrej for the zone example.org. should be 0
-    Then the statistics counter xfrreqdone for the zone _SERVER_ should be 0
-    Then the statistics counter xfrreqdone for the zone example.org. should be 0
+    The statistics counters are 0 in category .Xfrout.zones except for the following items
+      | item_name                | min_value | max_value |
+      | _SERVER_.notifyoutv6     |         1 |	       5 |
+      | example.org..notifyoutv6 |         1 |	       5 |
 
     When I query statistics socket of bind10 module Xfrout with cmdctl port 47804
-    Then the statistics counter open should be 1
-    Then the statistics counter openfail should be 0
-    Then the statistics counter close should be 0
-    Then the statistics counter bindfail should be 0
-    Then the statistics counter acceptfail should be 0
-    Then the statistics counter accept should be 0
-    Then the statistics counter senderr should be 0
-    Then the statistics counter recverr should be 0
+    The statistics counters are 0 in category .Xfrout.socket.unixdomain except for the following items
+      | item_name | item_value |
+      | open      |          1 |
 
     #
     # Test for NOTIFY that would result in NOTAUTH



More information about the bind10-changes mailing list